# Ahsan Mahmood — full text > Full-stack software developer with 8+ years' experience. One React and TypeScript codebase becomes a web app, an Android app and a browser extension. 23 live products, 25 npm packages. Available for client work. ## Ahsan Mahmood — full-stack developer, Lahore. 23 products shipped. URL: https://aoneahsan.com Full-stack software developer with 8+ years' experience. One React and TypeScript codebase becomes a web app, an Android app and a browser extension. 23 live products, 25 npm packages. Available for client work. Download the full CV Experience Eight years, six roles Mar 2026 — now Mar 2026 Building and maintaining my own products full time — 23 live across the web, Google Play and the extension stores, 25 packages published to npm, four of them open source. Available for client projects and contracts. Open Source & Community Projects Developer From pixel-perfect UI work in January 2018 to owning delivery end to end — and, since March 2026, running my own products full time and taking on client projects. Jan 2018 — May 2018 Jan 2018 Where it started — pixel-perfect interfaces in HTML5, CSS3 and JavaScript across multiple web projects. Frontend Developer Jun 2018 — Sep 2018 Jun 2018 Custom themes and plugins, advanced custom fields, and site maintenance for a range of clients. WordPress Developer Jul 2022 — Feb 2026 Jul 2022 Owned delivery of SaaS features across web and mobile with React, Node.js, TypeScript and Apollo GraphQL. Built the Slack and Microsoft Teams integrations. Led implementation planning and code review. Senior MERN Stack Developer Oct 2018 — Feb 2020 Oct 2018 Built a custom management system on Laravel and Angular, added PWA capability, and delivered responsive UI against design specifications. Web App Developer Mar 2020 — Jun 2022 Mar 2020 Delivered client products on MERN and MEAN, native and hybrid mobile apps, and a production Manifest V3 browser extension. Owned execution from requirements to deployment. Team Lead, SaaS App Developer Certifications Virtual University of Pakistan · 2018–2022 BSc Software Engineering Govt. Shalimar Graduate College, Lahore · 2016–2017 FSc Pre-Engineering Education and certifications Education Your existing web app packaged, hardened and taken through Play Console review. Android apps with Capacitor Data model, security rules, interface, mobile packaging, store submission, and the documentation that has to exist afterwards. A product, end to end Manifest V3, one repository packaged for Chrome, Firefox and Edge, including the store submissions. Browser extensions Regular review on your team's pull requests, or one-to-one work with a developer. Code review and mentoring Audit first — code quality, security, performance — then a prioritised roadmap and the work itself. Taking over an existing codebase React and TypeScript front ends, from a marketing site to a multi-tenant admin panel. Web app development Pricing scales with scope, complexity and timeline. You get a firm quote after the free consultation, once the deliverables and integrations are scoped. What does a project cost? Small features ship in 1–2 weeks, MVPs in 4–8 weeks, full production apps in 8–16 weeks. Most projects start within 48 hours of acceptance, with weekly progress demos and milestone-based delivery. How long does a project take? Questions I get asked Yes. An NDA is signed before any sensitive details are shared, and full IP ownership of delivered code transfers to you on final payment. Contracts cover confidentiality, deliverables, payment terms and post-launch support. Do you sign NDAs and transfer IP? WhatsApp, usually within hours on a working day. Email is monitored too and answered within one business day. What is the fastest way to reach you? Yes, and it is a common engagement. It starts with an audit covering code quality, security and performance, and produces a prioritised roadmap before any code is written. Can you take over an existing codebase? Pakistan Standard Time, UTC+5. Meetings are routinely scheduled across US, UK, EU and Australian working hours. What time zone do you work in? Full-stack software developer in Lahore, Pakistan. Building web apps, Android apps, browser extensions and npm packages — usually from one codebase. Elsewhere Site Available for client work Download CV Not taking new work See the work Start a project Full-stack developer. I finish things, and finished means it runs without me. English — full professional proficiency · Urdu — native Languages Open site I don’t do design-only engagements, native iOS (no Apple Developer account yet), blockchain work, or projects where the budget assumes an offshore rate for onshore expectations. Saying this now saves us both a call. What I don’t do The rest are client or commercial work, so their code stays private — I would rather say that plainly than imply otherwise. The same idea, for Linux Reclaim disk space on macOS from the command line One storage API across eight backends, zero dependencies Inspect what a machine is actually running Open source Two of the twenty-five are load-bearing Most of my packages exist because a project needed them twice. These two are used across every app I ship, which is the only endorsement I can honestly give them. Read the docs Over-the-air updates for Capacitor apps — bundle signing, staged rollout, and automatic rollback when a bundle fails to boot twice. View on npm One storage API across localStorage, IndexedDB, cookies, the URL, native Keychain and Keystore, SQLite and the filesystem. Zero dependencies. A free 30-minute call first, to scope what you actually need. If I am not the right person for it, I will tell you on that call. Weekly progress demos against milestones, not a status report. You see the thing running. An NDA before any sensitive detail is shared, and full IP ownership of delivered code transfers to you on final payment. Small features ship in 1–2 weeks, an MVP in 4–8, a full production app in 8–16. Most projects start within 48 hours of acceptance. You get the repository, the deployment, the store listings and the documentation. Nothing stays on my machine. How I work Docs sites one per product that needs one Extension stores Chrome, Firefox, Edge How the range is possible One codebase, five places people can install it This is the thing that lets one person keep twenty-three products alive. The web app, the Android build and the extension are the same React and TypeScript source, packaged three ways. Each ribbon below is scaled to the real number of live listings on that surface. npm packages four of them open source Google Play published Android listings ONE CODEBASE Live listings Note Surface Total Web apps deployed and reachable Services What you can hire me for See every service Android Chrome Identifier Live Stack Surfaces Web Open the live site Web PWA, published Android app, and a Chrome extension One React + TypeScript codebase behind all three A BYOK “Growth Suite” tier and a white-label licence system categories surfaces tools Also When the target genuinely suits them better Backend Only where a server is genuinely required Default technology choices by layer Default Layer Why Data Free tier that survives a real launch Extensions Three stores from one build What I actually build with Interface One component layer that ships to three targets Not a badge wall. This is the set I reach for by default, and what each one is for. Mobile Native packaging without a second codebase Based in Lahore, Pakistan · UTC+5 The first call is 30 minutes and free. Bring what you are trying to build, roughly when you need it, and a budget range — I will tell you honestly whether I am the right person for it. Contact form Email me Let’s build it Response time Hours on WhatsApp, one business day by email WhatsApp Working with Clients worldwide, remote Not a client list — these are the products I designed, built and shipped myself, across the web, Google Play and the Chrome, Firefox and Edge extension stores. Free-tier Firebase or Supabase, processing done in the browser wherever the work can happen there, and no paid service in the critical path. That is how twenty-three products stay live without a monthly bill behind them. A constraint I work under on purpose A product should cost nothing to run until it earns predictable running costs, no surprise invoice, an app that survives being ignored for a year Rules in architectures that only make sense at scale you do not have yet — and I will say so before we start Rules out ## Sitemap — every route on this site, searchable · Ahsan Mahmood URL: https://aoneahsan.com/sitemap All 69 routes in nine categories — public pages, account flows, payment surfaces, legal documents, the admin workspace and system states. Search by title, description, tag, keyword or path. Sign-in required Admin only account admin dynamic public static system All categories Browse by category Not built yet Open page If something you expected is missing from this list, it does not exist on the site — and that is worth telling me about. The directory is generated from the route tree, so it cannot quietly fall behind it. Still cannot find it? Back to the home page Tell me what is missing Filter routes by category Including the parts you cannot open — the admin workspace, the signed-in account pages and the error states. A directory that lists only what you are allowed to see is not a directory, it is a menu. Directory Every page Route template — no single address New writing, in the order it was published, for a reader app that checks on your behalf instead of you remembering to. The machine-readable pair How to subscribe This page is the version for people. These two are the version for crawlers and readers — generated at build time, so they never disagree with what is actually deployed. RSS Every public URL with a last-modified date, so search engines know what changed and what to re-crawl. Referenced from robots.txt. XML Clear the search Find a page Search routes by title, description, tag, keyword or path Matches on titles, descriptions, tags, keywords, category names and route paths — best match first, with the score it earned. Dynamic templates Public Restricted Total routes Every page Ahsan Mahmood — full-stack developer, Lahore. 23 products shipped. — Full-stack software developer with 8+ years' experience. One React and TypeScript codebase becomes a web app, an Android app and a browser extension. 23 live products, 25 npm packages. Available for client work. Sitemap — every route on this site, searchable · Ahsan Mahmood — All 69 routes in nine categories — public pages, account flows, payment surfaces, legal documents, the admin workspace and system states. Search by title, description, tag, keyword or path. RSS feed — subscribe to new writing · Ahsan Mahmood — The RSS feed for new posts. Copy one address into any reader and new writing arrives without an account, an algorithm or an email. Projects — every product Ahsan Mahmood has shipped — The working archive: 69 projects across web apps, Android, browser extensions, APIs and npm packages. Filter by category, search by name or stack, and open a live preview of the ones that allow it. Services — Ahsan Mahmood. Nine ways to hire one developer. — Nine engineering services across web apps, Android, browser extensions, backends, APIs, security, AI, e-commerce and architecture. Scoped on a call first. Typical reply within one business day. Skills — what Ahsan Mahmood works with, and how deeply — Twenty-six tools across five groups, each with a stated depth and the year it entered the stack. No percentages, because nobody measured one. Read the matrix by group or by level. Plans and pricing — Ahsan Mahmood — Three account plans — Free, Client and Retainer — with every limit stated, and which engagement grants which plan. About — Ahsan Mahmood, full-stack developer in Lahore — Who I am, what I build, and how I work. Eight years shipping production software, twenty-three products live across web, Google Play and the extension stores, and every figure on this page carries its source. Biography — Ahsan Mahmood, eight years and six roles — The longer story: where it started in January 2018, the six roles since, the pattern I settled on, the constraint I work under, and what I deliberately do not claim. Curriculum vitae — Ahsan Mahmood — The full record: every role since January 2018, the numbered project list, education and course completions. Preview it here or download the PDF or Word file. Curriculum vitae — Ahsan Mahmood — The full record: every role since January 2018, the numbered project list, education and course completions. Preview it here or download the PDF or Word file. Testimonials — six LinkedIn recommendations, unedited and dated — Six recommendations written on LinkedIn between 2018 and 2020, carried word for word with the date and relationship attached. No star ratings, because LinkedIn recommendations do not carry any. Where I am — Ahsan Mahmood — Based in Lahore, Pakistan, working remote worldwide on Pakistan Standard Time. Working hours, the overlap window, and every way to reach me. Contact — Ahsan Mahmood, full-stack developer in Lahore — Start a conversation about a project, a contract or a question. Every message opens a thread you can read the reply in — not a form that disappears into an inbox. Submit a query — Ahsan Mahmood — Open a two-way thread about a project, a package or a piece of work. You read the reply here, in the same conversation, rather than waiting on an email. Feature requests — ask for what gets built next — The public request board. File what you want built, vote on what other people asked for, and watch a request cross the 1,000-vote line that puts it on the roadmap. Voter names are public; the reasons people give are not. Blog — working notes on shipping software alone · Ahsan Mahmood — Working notes from building and maintaining a catalogue of products alone — architecture, mobile, security and the trade-offs behind them. Filter by topic, search by title. Support the work — Ahsan Mahmood. Account details, nothing charged. — Send support directly by bank transfer, mobile wallet, Wise or Payoneer. Nothing is charged on this page — it hands over account details, states what each method costs, and lets you copy or share them. Payment options — Ahsan Mahmood. Bank transfer, mobile wallet, Wise or Payoneer. — The ways to send a payment or support directly: bank transfer, mobile wallet, Wise and Payoneer, with the trade-off stated for each. Nothing is charged on this page — it hands over account details. Privacy Policy · Ahsan Mahmood — How data is collected, used and protected across this website and the Android app. Terms & Conditions · Ahsan Mahmood — The rules for using this website, its content and the app built from it. Cookie Policy · Ahsan Mahmood — Every cookie and storage key this site sets, who sets it, and how to switch it off. Data Deletion & Account Removal · Ahsan Mahmood — How to have your data deleted, what goes automatically, and what is kept and why. Data Security · Ahsan Mahmood — The measures protecting data here, stated at the level they can actually be verified. Privacy Policy for Reflectify 3-3-1 · Ahsan Mahmood — The privacy policy for the Reflectify 3-3-1 Android app. Privacy Policy for Scan & Generate QR Codes · Ahsan Mahmood — The privacy policy for the Scan & Generate QR Codes Android app. Privacy Policy for Zaions Listing · Ahsan Mahmood — The privacy policy for the Zaions Listing Android app. Video Controls Plus — a project by Ahsan Mahmood — Playback speed, loops and shortcuts on any HTML5 video. Chrome, Firefox and Edge. ZTools — a project by Ahsan Mahmood — A privacy-first developer and power-user toolbox covering 20 categories, where the work happens in your browser rather than on someone's server. ClearHire — a project by Ahsan Mahmood — A hiring platform built around one idea: employment claims should be checkable. Resume builder, candidate and company workflows, a verified-employment… Trizlink — a project by Ahsan Mahmood — Branded short links, link-in-bio pages, click analytics and a suite of utilities. SMS Mobile App — a project by Ahsan Mahmood — Android-first SMS automation that dispatches campaigns through the phone itself. Perkforce — Employee Perks & Benefits SaaS — a project by Ahsan Mahmood — Multi-tenant employee-perks SaaS across web, iOS, Android, Slack & Teams. I built the mobile + workplace apps and led its TypeScript migration. PregnancyPal — a project by Ahsan Mahmood — Week-by-week maternal wellness, nutrition, vitals and prenatal exercise. Native Update — a project by Ahsan Mahmood — Over-the-air updates for Capacitor apps — bundle signing, staged rollout, and automatic rollback when a bundle fails to boot twice. TaxEase — a project by Ahsan Mahmood — Tax compliance workflows for filers, with an admin side for staff. FilesHub — a project by Ahsan Mahmood — Laravel platform pairing multi-tenant file storage with 72 developer APIs. LifeWell — a project by Ahsan Mahmood — Health and wellness tracking across web, Android and a browser extension. HabitForge — a project by Ahsan Mahmood — Habit tracking with visual strength scoring and offline-first sync. LabFlow — a project by Ahsan Mahmood — Multi-tenant laboratory information system across five production surfaces. Ahsan Mahmood Portfolio — a project by Ahsan Mahmood — Production personal-brand platform: projects, services, blog, testimonials, and contact/payment surfaces across web and Capacitor mobile, engineered to be… Zaions Portfolio — a project by Ahsan Mahmood — Company-brand portfolio + content platform: a Firestore-backed projects directory, blog, and 18-screen admin CMS on a strictly zero-cost React 19 +… ContentSynergy AI — a project by Ahsan Mahmood — Free four-surface AI content workspace (web + mobile + extension + desktop) that generates text, images, video, audio, 3D & surveys and schedules them… Nyuk.in Invoice Generator — a project by Ahsan Mahmood — Invoice generation and management application with modern UI LearnQuest AI — a project by Ahsan Mahmood — 750+ games and learning activities for kids ages 1-15 ExoHunter AI — a project by Ahsan Mahmood — AI-powered exoplanet discovery and analysis platform ShopNest — a project by Ahsan Mahmood — Modern e-commerce platform with comprehensive shopping features 2FA Studio — a project by Ahsan Mahmood — Two-factor authentication management studio for secure account management AI Personal Assistant — a project by Ahsan Mahmood — Comprehensive AI-powered personal assistant application Ubuntu Suspender — a project by Ahsan Mahmood — Battery management application for Ubuntu/Linux with auto-suspend and power profiles ForgeAI — a project by Ahsan Mahmood — Professional AI-powered tools suite for writing, design, development, and business Capacitor Biometric Authentication — a project by Ahsan Mahmood — Framework-agnostic, provider-less biometric authentication library: one TypeScript API for WebAuthn + native biometric sign-in across Web, iOS, Android,… Notification Kit — a project by Ahsan Mahmood — Zero-runtime-dependency TypeScript library that unifies push, local, and in-app notifications behind one provider-less API for React and Capacitor apps. Strata Storage — a project by Ahsan Mahmood — One storage API across localStorage, IndexedDB, cookies, the URL, native Keychain and Keystore, SQLite and the filesystem. Zero dependencies. Unified Error Handling — a project by Ahsan Mahmood — Zero-dependency TypeScript library with one consistent API to capture, enrich, and route errors to ten monitoring services, loaded on demand so you only… Unified Tracking — a project by Ahsan Mahmood — Zero-runtime-dependency TypeScript package giving web, React, and Capacitor apps one API for analytics, user identity, revenue, consent gating, and error… ShieldPro Ultimate Ad Blocker — a project by Ahsan Mahmood — Manifest V3 ad-blocking and privacy suite, ~499 blocking rules. PrepAI (Interview Preparation App) — a project by Ahsan Mahmood — Interview-prep platform pairing ~900 hand-authored, browser-runnable coding drills across six language tracks with mock interviews, peer reviews, and a… POS System — a project by Ahsan Mahmood — Production-ready Point of Sale system built with Laravel 12 and Nova 5 MotorHub Pro — a project by Ahsan Mahmood — The ultimate multi-vehicle dealership platform for car dealers Anonymous Chat AI (AChat) — a project by Ahsan Mahmood — No-signup chat. Lock a room with a password for real in-browser encryption. ChatExport — a project by Ahsan Mahmood — Free ChatGPT App (OpenAI Apps SDK / MCP) that exports ChatGPT conversations to Markdown, JSON, PDF, HTML, or text from inside chat, with zero content… ImtehanHub — a project by Ahsan Mahmood — Bilingual Urdu and English exam preparation, Class 5 to FA/FSc. Legal Eagle Law Firm — a project by Ahsan Mahmood — Legal-tech SaaS: an AI-prerendered marketing site, a ~15.7k-row advocate directory on Neon Postgres, Google Meet consultation booking, a free-first AI… pdf-data-extractor — a project by Ahsan Mahmood — Python CLI and library that lifts everything out of any PDF (text, tables, images, forms, signatures, metadata, and 23 regex-matched data types) into JSON,… SlackVault — a project by Ahsan Mahmood — Exports and archives Slack history so a workspace outgrowing its retention limit keeps its own record. SnapContact — a project by Ahsan Mahmood — Contact-intelligence suite that turns every chat, business card, or email signature into a structured, scored, searchable contact across web, Android, and… Vessl — a project by Ahsan Mahmood — Premium dark-first car marketplace + multi-tenant dealer-operations SaaS, with a 3D car viewer, financing calculator, leads CRM, and a 13-worker Cloudflare… Capacitor Auth Manager — a project by Ahsan Mahmood — Framework-agnostic TypeScript authentication library unifying 15 OAuth, passwordless, credential, and biometric sign-in flows behind one provider-less API. shared-features — a project by Ahsan Mahmood — NPM TypeScript/React library that centralizes feature flags, cross-promotion advertising, in-app broadcasts, and 9 profile-data domains, administered from… Growthify (All-in-One Shopify Suite) — a project by Ahsan Mahmood — Embedded Shopify app suite with a 50-block storefront theme extension and merchant growth tools. Empora for WooCommerce — a project by Ahsan Mahmood — Freemium WooCommerce suite — WP plugin with a 35+ module React admin, SaaS dashboard and Laravel backend. Aura CRM — a project by Ahsan Mahmood — A voice-first client operations engine for independent professionals who spend hours on post-session admin. Bazaaro — a project by Ahsan Mahmood — A trust-first classifieds marketplace (OLX alternative) with escrow, verified identity, and SafeMeet pickup zones for buyers and sellers. CallVault — a project by Ahsan Mahmood — Offline-first Android app that records your own calls and backs them up to storage and a database you control. Code Craft Studio — a project by Ahsan Mahmood — React library to scan and generate QR codes and barcodes, with optional Capacitor native support for Android and iOS. Corra — a project by Ahsan Mahmood — A unified SaaS for K-12 teachers that keeps planning, grading, SPED/IEP, comms, and behavior on one shared student record with a FERPA-safe AI copilot. DoorNest — a project by Ahsan Mahmood — A cross-platform Flutter app that matches roommates by lifestyle, then helps them split rent, rotate chores, and live well together. MRO Express — a project by Ahsan Mahmood — A B2B parts-sourcing and on-demand delivery platform for HVAC, plumbing, and electrical contractors. Nursana — a project by Ahsan Mahmood — Cross-platform SaaS for nurses bundling calculators, credential tracking, an on-device shift brain sheet, wellbeing, and more into one app. OrbitCubs CRM — a project by Ahsan Mahmood — A three-surface CRM (web, Android, browser extension) for tracking contacts, deals, and pipelines for solo operators and small teams. Polymath AI Workspace — a project by Ahsan Mahmood — A document-based AI workspace with four lenses — Study, Career, Knowledge, and Document-Pro — over one shared RAG core. Safar — a project by Ahsan Mahmood — A safety-first ride-hailing platform for riders, drivers, and fleet operators, built from one cross-platform codebase. SNGDC — a project by Ahsan Mahmood — A synthetic natural gas distribution utility platform: public corporate site, customer self-service portal, and a no-code admin studio. Trialith — a project by Ahsan Mahmood — Clinical-trial protocol intelligence SaaS for pharma sponsors and CROs — audit, simulate, and compile protocols into CDISC-ready EDC schemas. Whiteboard Video Maker — a project by Ahsan Mahmood — Client-side whiteboard-animation studio that draws, narrates, and exports WebM and GIF videos entirely in the browser, for educators and creators. WebAuthn Server BuildKit — a project by Ahsan Mahmood — Framework-independent TypeScript server library that verifies passkey registration and authentication ceremonies for Node.js backends. FourTools iOS Recovery — a project by Ahsan Mahmood — A cross-platform Tauri 2 desktop suite that helps owners recover access to their own iPhones, iPads, and iPods. Linux Cleanup — a project by Ahsan Mahmood — A modular Bash and Node.js CLI that safely reclaims regenerable cache and junk disk space for Linux developers. MacLeanup — a project by Ahsan Mahmood — A safe-by-default macOS cleanup CLI covering 28 cleanup sections with a real dry-run, per-section confirmations, and zero-install delivery via npx. SysScope — a project by Ahsan Mahmood — Read-only Bash CLI that audits your Mac or Linux machine and grades which local Ollama models it can actually run. Candy Rush — a project by Ahsan Mahmood — A match-3 puzzle game with 150 levels across 10 worlds, special candies, boosters, a daily challenge and offline play — live on the web. Ludo — Cross-Platform Multiplayer Board Game — a project by Ahsan Mahmood — Multiplayer Ludo — play online with friends, local pass-and-play, or vs AI bots, on web, Android & Chrome. One tap to start, no signup. Monopoly Game — a project by Ahsan Mahmood — A modern Monopoly rebuild for web + mobile — local pass-and-play, solo vs AI bots, or online private rooms with real-time sync. CRXForge (Extension Template) — a project by Ahsan Mahmood — Cross-browser (Chrome/Firefox/Edge) Manifest V3 extension starter on WXT, React 19, and TypeScript, store-compliant by default. LinkedIn Posts Automation — a project by Ahsan Mahmood — A Cloudflare Worker plus Supabase system that auto-publishes pre-written LinkedIn posts via the official API, with a React admin dashboard. What does zero-cost infrastructure cost you? — Nothing paid sits in the critical path, so the product costs nothing to run. What that takes off your bill, what it forbids, and where a free tier ends. Can I hire a full-stack developer remotely? — Yes. Pakistan Standard Time, UTC+5, asynchronous by default, with calls across US, UK, EU and Australian hours, and a first reply within one business day. How long does a web app, an MVP or a full product take? — Small features 1–2 weeks, an MVP 4–8, a full production app 8–16. Where those ranges come from, how each is delivered, and what makes one of them slip. How much does custom software cost, and what is in a quote? — $800 is the floor for a small, well-defined feature. Everything above it comes from a scoping call, in writing. What that quote contains, and what it omits. Can one codebase ship web, Android and a browser extension? — Yes. One React and TypeScript repository becomes a web app, a Capacitor Android app, and a Manifest V3 extension, packaged three times for three stores. What is actually changing in web development — Not predictions. Four shifts I have already had to design around: crawlers that do not run JavaScript, the platform absorbing dependencies, types as the… Pricing and plans without a payment processor — Why my products take payment through a redirect and an admin grant instead of a checkout integration, what that costs, and the entitlement bugs that bite… How this site is built, and what I rebuilt to fix — React 19, Vite, TanStack Router, Supabase and a click dummy that is the visual specification. Including the outlet bug that made nine service pages render… The JavaScript features I actually reach for now — Intl for anything a person reads, structuredClone, at(), Object.groupBy, and the platform APIs that replaced dependencies. Plus the number formatting that… Micro-animation: the 100ms rule, and everything else is… — Motion that earns its place versus motion that is decoration. Why acknowledgement beats a toast, why the browser gives you two free properties, and the… From junior to senior: what actually changed — Eight years from a frontend job in Lahore to running twenty-three products alone. The three shifts that mattered, and the one I was slowest to make. A feature-request board on Firestore, and the rules that… — Vote counts that cannot be forged, a status workflow that means something, and the security-rule subtlety that makes a list query fail while the… Reflectify 3-3-1: a journaling app that deliberately has… — Three gratitudes, three wins, one focus. Why the entries never leave the phone, why there is no model reading them, and what a constraint does to a product… PregnancyPal: building health software when the data is… — Week-by-week tracking, vitals, fertility and a community — on a free tier, with no cloud functions. What health data changes about every architectural… FilesHub: building the storage backend instead of paying… — A self-hosted Laravel platform that replaced Firebase Storage and every transactional-email vendor across an entire portfolio. What it took, and the… Trizlink: one React codebase as a web app and an Android app — Two surfaces from one repository, with a third planned. What actually got shared, what could not be, and the free-tier limits that shaped the data model. Refactoring a monolith you cannot stop shipping — The rewrite that loses a third of your features, the seams worth finding first, and why "delete the dead code" is the highest-value refactor nobody… Firebase Auth and social sign-in: the parts that are… — Social login is easy to add and easy to get subtly wrong. Custom claims that lie, rules that trust the client, and why an extension cannot use the Auth SDK… Accessible design systems: what React Aria gives you… — Moving from a styled component library to headless primitives plus Tailwind. What accessibility work is genuinely free, what is not, and the rich-text rule… Admin panels: four failures that ship looking complete — Every one of these was found in a live admin panel, and none is visible in a code review or a green build. Plus why the screen list should be derived from… One codebase, web and Android: what Capacitor actually costs — Shipping the same React app to the browser and to Google Play. The parts that are genuinely free, the parts that are not, and the config bug that ships… TypeScript patterns I actually use, and one that cost me… — Typed translation keys, satisfies, discriminated state and branded ids — plus the conditional type that took my typecheck from 14 seconds to 170 and how I… SEO for a single-page app, without pretending to have a… — AI crawlers do not run JavaScript. Neither, reliably, does anything else you care about. Here is how I get real content into static HTML without adopting… Why I reach for TanStack Router — Typed routes turn a whole category of navigation bug into a build error. Here is the one that shipped to production in my own app before I moved, and what… A zero-cost backend architecture that actually holds — Twenty-three products, no servers, no monthly bill. Here is what the constraint removes, what it forces you to design properly, and the day a free tier… Full-stack web apps — services — Ahsan Mahmood — React and TypeScript front to back, with a real data layer and real access control. Cross-platform mobile apps — services — Ahsan Mahmood — One React codebase shipped to the web and to Android through Capacitor. Browser-extension engineering — services — Ahsan Mahmood — Manifest V3 extensions for Chrome, Edge and Firefox, built to survive store review. Firebase backend platforms — services — Ahsan Mahmood — Firestore data models, security rules and the read budget that keeps them on the free tier. API and integration development — services — Ahsan Mahmood — Typed, versioned HTTP APIs and the third-party integrations around them. Security and auth products — services — Ahsan Mahmood — Authentication, authorisation and row-level access control that holds up to a real probe. AI product development — services — Ahsan Mahmood — Shipping AI features into real products — retrieval, agents and the plumbing that keeps them affordable. E-commerce and WordPress solutions — services — Ahsan Mahmood — Storefronts and content sites, including migrations off WordPress when that is the right answer. Platform architecture consulting — services — Ahsan Mahmood — Reviewing and reshaping an existing codebase so the next year of work is cheaper than the last. ## RSS feed — subscribe to new writing · Ahsan Mahmood URL: https://aoneahsan.com/feed The RSS feed for new posts. Copy one address into any reader and new writing arrives without an account, an algorithm or an email. Read the blog Everything in the feed is on the blog, permanently and in full. There is no newsletter and no notification prompt — this page and that one are the whole subscription story. Not a feed person? Browse every page One address, pasted into any reader. No account, no algorithm deciding what you see, and no email address handed over. If I stop writing, nothing arrives — which is the point. RSS The feed New writing, delivered by something you already control. Open feed.xml Read the blog instead In the feed right now Loading the latest entries… A feed reader is an inbox you own. It checks the sites you chose, on its own schedule, and shows you what is new in the order it was published. Nothing is ranked, nothing is promoted, and nobody learns what you read. If you have never used a reader Readers worth starting with: Feedly and Inoreader in a browser, NetNewsWire on Apple devices, or Slack — its /feed subscribe command takes the same address and posts new items into a channel. The file is regenerated on every deploy, so it is current whenever the site is. Most readers poll every hour or so; there is nothing to configure at this end. How often it updates Copied Copy the address Subscribing Copy this, then paste it into your reader’s “add feed” box. Blog posts only, newest first, with the full excerpt and a link to the post. Project additions and site changes are not in it — that would turn a reading feed into a changelog, and the two want different attention. What arrives In the feed right now What does zero-cost infrastructure cost you? — Nothing paid sits in the critical path, so the product costs nothing to run. What that takes off your bill, what it forbids, and where a free tier ends. Can I hire a full-stack developer remotely? — Yes. Pakistan Standard Time, UTC+5, asynchronous by default, with calls across US, UK, EU and Australian hours, and a first reply within one business day. How long does a web app, an MVP or a full product take? — Small features 1–2 weeks, an MVP 4–8, a full production app 8–16. Where those ranges come from, how each is delivered, and what makes one of them slip. How much does custom software cost, and what is in a quote? — $800 is the floor for a small, well-defined feature. Everything above it comes from a scoping call, in writing. What that quote contains, and what it omits. Can one codebase ship web, Android and a browser extension? — Yes. One React and TypeScript repository becomes a web app, a Capacitor Android app, and a Manifest V3 extension, packaged three times for three stores. What is actually changing in web development — Not predictions. Four shifts I have already had to design around: crawlers that do not run JavaScript, the platform absorbing dependencies, types as the… Pricing and plans without a payment processor — Why my products take payment through a redirect and an admin grant instead of a checkout integration, what that costs, and the entitlement bugs that bite… How this site is built, and what I rebuilt to fix — React 19, Vite, TanStack Router, Supabase and a click dummy that is the visual specification. Including the outlet bug that made nine service pages render… The JavaScript features I actually reach for now — Intl for anything a person reads, structuredClone, at(), Object.groupBy, and the platform APIs that replaced dependencies. Plus the number formatting that… Micro-animation: the 100ms rule, and everything else is… — Motion that earns its place versus motion that is decoration. Why acknowledgement beats a toast, why the browser gives you two free properties, and the… From junior to senior: what actually changed — Eight years from a frontend job in Lahore to running twenty-three products alone. The three shifts that mattered, and the one I was slowest to make. A feature-request board on Firestore, and the rules that… — Vote counts that cannot be forged, a status workflow that means something, and the security-rule subtlety that makes a list query fail while the… Reflectify 3-3-1: a journaling app that deliberately has… — Three gratitudes, three wins, one focus. Why the entries never leave the phone, why there is no model reading them, and what a constraint does to a product… PregnancyPal: building health software when the data is… — Week-by-week tracking, vitals, fertility and a community — on a free tier, with no cloud functions. What health data changes about every architectural… FilesHub: building the storage backend instead of paying… — A self-hosted Laravel platform that replaced Firebase Storage and every transactional-email vendor across an entire portfolio. What it took, and the… Trizlink: one React codebase as a web app and an Android app — Two surfaces from one repository, with a third planned. What actually got shared, what could not be, and the free-tier limits that shaped the data model. Refactoring a monolith you cannot stop shipping — The rewrite that loses a third of your features, the seams worth finding first, and why "delete the dead code" is the highest-value refactor nobody… Firebase Auth and social sign-in: the parts that are… — Social login is easy to add and easy to get subtly wrong. Custom claims that lie, rules that trust the client, and why an extension cannot use the Auth SDK… Accessible design systems: what React Aria gives you… — Moving from a styled component library to headless primitives plus Tailwind. What accessibility work is genuinely free, what is not, and the rich-text rule… Admin panels: four failures that ship looking complete — Every one of these was found in a live admin panel, and none is visible in a code review or a green build. Plus why the screen list should be derived from… One codebase, web and Android: what Capacitor actually costs — Shipping the same React app to the browser and to Google Play. The parts that are genuinely free, the parts that are not, and the config bug that ships… TypeScript patterns I actually use, and one that cost me… — Typed translation keys, satisfies, discriminated state and branded ids — plus the conditional type that took my typecheck from 14 seconds to 170 and how I… SEO for a single-page app, without pretending to have a… — AI crawlers do not run JavaScript. Neither, reliably, does anything else you care about. Here is how I get real content into static HTML without adopting… Why I reach for TanStack Router — Typed routes turn a whole category of navigation bug into a build error. Here is the one that shipped to production in my own app before I moved, and what… A zero-cost backend architecture that actually holds — Twenty-three products, no servers, no monthly bill. Here is what the constraint removes, what it forces you to design properly, and the day a free tier… ## Projects — every product Ahsan Mahmood has shipped URL: https://aoneahsan.com/projects The working archive: 69 projects across web apps, Android, browser extensions, APIs and npm packages. Filter by category, search by name or stack, and open a live preview of the ones that allow it. Browse the archive Category Details Preview Stack Updated APIs & libs Everything Extensions Full stack Mobile Other Web apps Extension Web app Projects Most of what is on this page started as somebody’s problem and a rough sketch. Bring me yours — the first call is thirty minutes and free, and I will tell you honestly whether I am the right person for it. Want one of these for your business? See what I take on Start a project About What this actually is All projects At a glance Back to the archive Browse all projects Built with This one was designed, built and shipped by one person. So is everything else in the archive. Want something like this? Each of these is a capability the stack genuinely provides. None of them is an outcome nobody measured — there is no "cut onboarding by 40%" on this page, because no one measured onboarding. Key features What it does, and what that costs to build Identifier Loading this project Each destination comes from the project record. This one has no public URL on file yet, so the buttons return to the index rather than inventing one. Back to all projects The still below is painted first, then the running site loads behind it — so the dialog is never an empty rectangle waiting on a network round trip. Where a site allows framing the live app takes over; where it refuses, the still stays and says so. Nothing is requested until you press. Not every project has a public web surface that can be framed — a package, a native app and a store-only extension have nothing to embed, and several sites refuse framing outright. The links above go to the real thing. Close A preview is wired per project, and this one has no framable public URL on record. Nothing to embed here Take a look before you go No embedded preview for this one Nothing is requested until you press. Open the live site More browser extensions Keep looking Featured More full-stack products More from the catalogue More libraries and APIs More mobile work More tools and experiments View project More web apps — a project by Ahsan Mahmood The short version These are designed frames, not pictures. Each one records what belongs in the slot and at what width; the capture itself is taken from the running app. Drawing a fake screenshot here would put an image on the site that no build has ever produced. Screenshot Screenshots Slot This frame is a specification, not a picture. It records what belongs in the slot, at what width. The real capture is taken from the running app — inventing one here would put a screenshot on the site that no build has ever produced. Last updated Started Status Archived No longer maintained. The code and any published listing stay up; nothing new ships. Timeline Built, shipped, and since archived Still being worked on Last change Work started What it is Worth knowing Elsewhere The public repositories, and the profile README that lists the rest. GitHub The same work, indexed by someone else Registries and stores keep their own record of what I publish. If you want a version of this list I cannot edit, start with one of these. Every package published under my name, with its real version and publish date. npm Built from the same codebase as the site you are reading. This portfolio, as an Android app Google Play The archive Start here Two I would show first. Filter projects by category Fine print How to read this list Everything I’ve shipped Shipped once, no longer maintained. Kept here because deleting it would make the archive dishonest. Public and usable, still changing shape. Beta The package or bundle id. It is the permanent name; the title above it is not. The identifier Built enough to show, not enough to hand over. In progress Running today at a public URL, and maintained. Live Opens the real site in a frame. Four of my own products refuse to be framed and say so instead of showing an empty box. Chrome Web Store Documentation Edge Add-ons Firefox Add-ons Source on GitHub Client work is not here. These are products I designed, built and shipped myself. No download, install or user counts. They fluctuate, nobody can check them, and a number I cannot source is not worth the sentence. What this page does not say No rankings and no “best of”. The order is mine, and the two at the top are the two I would open first in a call — not a measurement. Pagination Everything else Matching projects Search projects by name or stack Categories Archive summary Projects listed With a preview Projects Video Controls Plus — a project by Ahsan Mahmood — Playback speed, loops and shortcuts on any HTML5 video. Chrome, Firefox and Edge. ZTools — a project by Ahsan Mahmood — A privacy-first developer and power-user toolbox covering 20 categories, where the work happens in your browser rather than on someone's server. ClearHire — a project by Ahsan Mahmood — A hiring platform built around one idea: employment claims should be checkable. Resume builder, candidate and company workflows, a verified-employment… Trizlink — a project by Ahsan Mahmood — Branded short links, link-in-bio pages, click analytics and a suite of utilities. SMS Mobile App — a project by Ahsan Mahmood — Android-first SMS automation that dispatches campaigns through the phone itself. Perkforce — Employee Perks & Benefits SaaS — a project by Ahsan Mahmood — Multi-tenant employee-perks SaaS across web, iOS, Android, Slack & Teams. I built the mobile + workplace apps and led its TypeScript migration. PregnancyPal — a project by Ahsan Mahmood — Week-by-week maternal wellness, nutrition, vitals and prenatal exercise. Native Update — a project by Ahsan Mahmood — Over-the-air updates for Capacitor apps — bundle signing, staged rollout, and automatic rollback when a bundle fails to boot twice. TaxEase — a project by Ahsan Mahmood — Tax compliance workflows for filers, with an admin side for staff. FilesHub — a project by Ahsan Mahmood — Laravel platform pairing multi-tenant file storage with 72 developer APIs. LifeWell — a project by Ahsan Mahmood — Health and wellness tracking across web, Android and a browser extension. HabitForge — a project by Ahsan Mahmood — Habit tracking with visual strength scoring and offline-first sync. LabFlow — a project by Ahsan Mahmood — Multi-tenant laboratory information system across five production surfaces. Ahsan Mahmood Portfolio — a project by Ahsan Mahmood — Production personal-brand platform: projects, services, blog, testimonials, and contact/payment surfaces across web and Capacitor mobile, engineered to be… Zaions Portfolio — a project by Ahsan Mahmood — Company-brand portfolio + content platform: a Firestore-backed projects directory, blog, and 18-screen admin CMS on a strictly zero-cost React 19 +… ContentSynergy AI — a project by Ahsan Mahmood — Free four-surface AI content workspace (web + mobile + extension + desktop) that generates text, images, video, audio, 3D & surveys and schedules them… Nyuk.in Invoice Generator — a project by Ahsan Mahmood — Invoice generation and management application with modern UI LearnQuest AI — a project by Ahsan Mahmood — 750+ games and learning activities for kids ages 1-15 ExoHunter AI — a project by Ahsan Mahmood — AI-powered exoplanet discovery and analysis platform ShopNest — a project by Ahsan Mahmood — Modern e-commerce platform with comprehensive shopping features 2FA Studio — a project by Ahsan Mahmood — Two-factor authentication management studio for secure account management AI Personal Assistant — a project by Ahsan Mahmood — Comprehensive AI-powered personal assistant application Ubuntu Suspender — a project by Ahsan Mahmood — Battery management application for Ubuntu/Linux with auto-suspend and power profiles ForgeAI — a project by Ahsan Mahmood — Professional AI-powered tools suite for writing, design, development, and business Capacitor Biometric Authentication — a project by Ahsan Mahmood — Framework-agnostic, provider-less biometric authentication library: one TypeScript API for WebAuthn + native biometric sign-in across Web, iOS, Android,… Notification Kit — a project by Ahsan Mahmood — Zero-runtime-dependency TypeScript library that unifies push, local, and in-app notifications behind one provider-less API for React and Capacitor apps. Strata Storage — a project by Ahsan Mahmood — One storage API across localStorage, IndexedDB, cookies, the URL, native Keychain and Keystore, SQLite and the filesystem. Zero dependencies. Unified Error Handling — a project by Ahsan Mahmood — Zero-dependency TypeScript library with one consistent API to capture, enrich, and route errors to ten monitoring services, loaded on demand so you only… Unified Tracking — a project by Ahsan Mahmood — Zero-runtime-dependency TypeScript package giving web, React, and Capacitor apps one API for analytics, user identity, revenue, consent gating, and error… ShieldPro Ultimate Ad Blocker — a project by Ahsan Mahmood — Manifest V3 ad-blocking and privacy suite, ~499 blocking rules. PrepAI (Interview Preparation App) — a project by Ahsan Mahmood — Interview-prep platform pairing ~900 hand-authored, browser-runnable coding drills across six language tracks with mock interviews, peer reviews, and a… POS System — a project by Ahsan Mahmood — Production-ready Point of Sale system built with Laravel 12 and Nova 5 MotorHub Pro — a project by Ahsan Mahmood — The ultimate multi-vehicle dealership platform for car dealers Anonymous Chat AI (AChat) — a project by Ahsan Mahmood — No-signup chat. Lock a room with a password for real in-browser encryption. ChatExport — a project by Ahsan Mahmood — Free ChatGPT App (OpenAI Apps SDK / MCP) that exports ChatGPT conversations to Markdown, JSON, PDF, HTML, or text from inside chat, with zero content… ImtehanHub — a project by Ahsan Mahmood — Bilingual Urdu and English exam preparation, Class 5 to FA/FSc. Legal Eagle Law Firm — a project by Ahsan Mahmood — Legal-tech SaaS: an AI-prerendered marketing site, a ~15.7k-row advocate directory on Neon Postgres, Google Meet consultation booking, a free-first AI… pdf-data-extractor — a project by Ahsan Mahmood — Python CLI and library that lifts everything out of any PDF (text, tables, images, forms, signatures, metadata, and 23 regex-matched data types) into JSON,… SlackVault — a project by Ahsan Mahmood — Exports and archives Slack history so a workspace outgrowing its retention limit keeps its own record. SnapContact — a project by Ahsan Mahmood — Contact-intelligence suite that turns every chat, business card, or email signature into a structured, scored, searchable contact across web, Android, and… Vessl — a project by Ahsan Mahmood — Premium dark-first car marketplace + multi-tenant dealer-operations SaaS, with a 3D car viewer, financing calculator, leads CRM, and a 13-worker Cloudflare… Capacitor Auth Manager — a project by Ahsan Mahmood — Framework-agnostic TypeScript authentication library unifying 15 OAuth, passwordless, credential, and biometric sign-in flows behind one provider-less API. shared-features — a project by Ahsan Mahmood — NPM TypeScript/React library that centralizes feature flags, cross-promotion advertising, in-app broadcasts, and 9 profile-data domains, administered from… Growthify (All-in-One Shopify Suite) — a project by Ahsan Mahmood — Embedded Shopify app suite with a 50-block storefront theme extension and merchant growth tools. Empora for WooCommerce — a project by Ahsan Mahmood — Freemium WooCommerce suite — WP plugin with a 35+ module React admin, SaaS dashboard and Laravel backend. Aura CRM — a project by Ahsan Mahmood — A voice-first client operations engine for independent professionals who spend hours on post-session admin. Bazaaro — a project by Ahsan Mahmood — A trust-first classifieds marketplace (OLX alternative) with escrow, verified identity, and SafeMeet pickup zones for buyers and sellers. CallVault — a project by Ahsan Mahmood — Offline-first Android app that records your own calls and backs them up to storage and a database you control. Code Craft Studio — a project by Ahsan Mahmood — React library to scan and generate QR codes and barcodes, with optional Capacitor native support for Android and iOS. Corra — a project by Ahsan Mahmood — A unified SaaS for K-12 teachers that keeps planning, grading, SPED/IEP, comms, and behavior on one shared student record with a FERPA-safe AI copilot. DoorNest — a project by Ahsan Mahmood — A cross-platform Flutter app that matches roommates by lifestyle, then helps them split rent, rotate chores, and live well together. MRO Express — a project by Ahsan Mahmood — A B2B parts-sourcing and on-demand delivery platform for HVAC, plumbing, and electrical contractors. Nursana — a project by Ahsan Mahmood — Cross-platform SaaS for nurses bundling calculators, credential tracking, an on-device shift brain sheet, wellbeing, and more into one app. OrbitCubs CRM — a project by Ahsan Mahmood — A three-surface CRM (web, Android, browser extension) for tracking contacts, deals, and pipelines for solo operators and small teams. Polymath AI Workspace — a project by Ahsan Mahmood — A document-based AI workspace with four lenses — Study, Career, Knowledge, and Document-Pro — over one shared RAG core. Safar — a project by Ahsan Mahmood — A safety-first ride-hailing platform for riders, drivers, and fleet operators, built from one cross-platform codebase. SNGDC — a project by Ahsan Mahmood — A synthetic natural gas distribution utility platform: public corporate site, customer self-service portal, and a no-code admin studio. Trialith — a project by Ahsan Mahmood — Clinical-trial protocol intelligence SaaS for pharma sponsors and CROs — audit, simulate, and compile protocols into CDISC-ready EDC schemas. Whiteboard Video Maker — a project by Ahsan Mahmood — Client-side whiteboard-animation studio that draws, narrates, and exports WebM and GIF videos entirely in the browser, for educators and creators. WebAuthn Server BuildKit — a project by Ahsan Mahmood — Framework-independent TypeScript server library that verifies passkey registration and authentication ceremonies for Node.js backends. FourTools iOS Recovery — a project by Ahsan Mahmood — A cross-platform Tauri 2 desktop suite that helps owners recover access to their own iPhones, iPads, and iPods. Linux Cleanup — a project by Ahsan Mahmood — A modular Bash and Node.js CLI that safely reclaims regenerable cache and junk disk space for Linux developers. MacLeanup — a project by Ahsan Mahmood — A safe-by-default macOS cleanup CLI covering 28 cleanup sections with a real dry-run, per-section confirmations, and zero-install delivery via npx. SysScope — a project by Ahsan Mahmood — Read-only Bash CLI that audits your Mac or Linux machine and grades which local Ollama models it can actually run. Candy Rush — a project by Ahsan Mahmood — A match-3 puzzle game with 150 levels across 10 worlds, special candies, boosters, a daily challenge and offline play — live on the web. Ludo — Cross-Platform Multiplayer Board Game — a project by Ahsan Mahmood — Multiplayer Ludo — play online with friends, local pass-and-play, or vs AI bots, on web, Android & Chrome. One tap to start, no signup. Monopoly Game — a project by Ahsan Mahmood — A modern Monopoly rebuild for web + mobile — local pass-and-play, solo vs AI bots, or online private rooms with real-time sync. CRXForge (Extension Template) — a project by Ahsan Mahmood — Cross-browser (Chrome/Firefox/Edge) Manifest V3 extension starter on WXT, React 19, and TypeScript, store-compliant by default. LinkedIn Posts Automation — a project by Ahsan Mahmood — A Cloudflare Worker plus Supabase system that auto-publishes pre-written LinkedIn posts via the official API, with a React admin dashboard. ## Services — Ahsan Mahmood. Nine ways to hire one developer. URL: https://aoneahsan.com/services Nine engineering services across web apps, Android, browser extensions, backends, APIs, security, AI, e-commerce and architecture. Scoped on a call first. Typical reply within one business day. The deployment, and the pipeline that redeploys it Thirty days of bug fixes at no charge Included whichever service you pick A README a second developer can start from The repository, and its full history Where the scope is tight enough to hold one. You know the number before I start. Fixed price Part-time inside your team, on your tools, in your standups. Fractional Four ways to structure it Split across demos you sign off individually. You can stop after any one of them. Milestone A set number of days a month for ongoing work. Cancellable, not auto-renewing. Retainer Featured Price Quoted after a scoping call View details Everything I take on Loading services Backend Cloud Commerce Advisory Data DevOps Extension Mobile Other Security Design Web Describe your project Need something custom? See the work first About this service Scope is agreed on a call before anything is quoted, and what the work does not include is written down at the same time as what it does. If this is the wrong service for your problem, that is a sentence you get on the call rather than a discovery in week three. Or something adjacent All services Built with Compare the nine Tell me what you are building and roughly when you need it. The first call is thirty minutes, costs nothing, and ends with either a scope or an honest reason not to proceed. Ready to start? How long it takes Get a quote What’s included Every engagement of this shape covers Five deliverables, every time this service is bought. Anything outside this list is quoted separately rather than assumed. What is included Loading service Visual design as a standalone deliverable, native iOS — there is no Apple Developer account, so that platform is not offered — and the third-party subscriptions the finished product needs to run. The full list is on the services FAQ Not included, here or in any of the nine Frequently bought alongside this one, or a better fit if this is not quite it. Related services — services — Ahsan Mahmood After launch Thirty days of fixes included Category Engagement Fixed price, milestone, retainer or fractional How this one runs Reply time Within one business day Starts with A free 30-minute scoping call How this one goes What the work actually looks like The default set for this service. A project with a good reason to differ gets that reason written down before we start. Technologies and tools Four commitments that hold for this service the same way they hold for the other eight. Empty, loading, offline and error states are built with the feature rather than after it, because those are what a real user meets on a first run or a bad connection. The failure paths are designed, not discovered Repository, store listings and documentation are yours. Nothing needed to run or rebuild the product stays on my machine, and nothing is licensed back to you. Handed over completely Why choose this Typed end to end, structured so a second developer can find things, and commented where the reasoning is not obvious from the code. You are not buying a codebase only I can read. Built the way it will be maintained A working demo against the milestone, not a status update. If something slipped you hear it that week, while there is still time to change what we do about it. You see it running every week Services Thirty days of bug fixes at no charge. After that nothing is automatic: maintenance, new features and on-call are a retainer you opt into, and you can end it. I would rather you leave because you no longer need me than stay because cancelling was awkward. What happens after launch? The number comes from scope, integrations and timeline — and you get it in writing after the scoping call, never before it. I do not quote from a brief alone, because the quotes that come from briefs alone are the ones that get revised upward later. What does a project cost? Small features 1–2 weeks, an MVP 4–8 weeks, a full production app 8–16 weeks. Those are ranges I have hit on work of that shape before. Something I have genuinely not built before gets a range and a spike, not a confident date. How long does it take? Visual design as a standalone deliverable. Native iOS — I have no Apple Developer account, so I will not claim a platform I cannot ship to. Blockchain work. Copywriting and content. Third-party subscriptions and domain costs are yours, and any architecture that needs a paid service in the critical path gets flagged before it is built, not after. What is not included, whichever service I pick? Asked before every project Including the answers that lose work. Those are the ones worth reading first. Yes. The NDA is signed before any sensitive detail is shared, and full IP ownership of delivered code transfers to you on final payment. The contract covers confidentiality, deliverables, payment terms and post-launch support. Do you sign an NDA and transfer IP? Yes, and it starts with an audit rather than a feature. You get a written read on code quality, security and performance, and a prioritised list — which includes the parts I would leave alone. A rewrite is a recommendation I have to earn, not a default. Can you take over an existing codebase? I work Pakistan Standard Time, UTC+5, and routinely take meetings across US, UK, EU and Australian hours. Async is the default — written updates and recorded demos — so a call is something we schedule, not something the project depends on. Do you work in my time zone? Based in Lahore, Pakistan — UTC+5 Engagements Experience 8+ years At a glance Working with Clients worldwide, async by default One person accountable for all of them. End-to-end engineering across web, mobile, browser extensions, backends, APIs and platform work. Every engagement is scoped on a call before it is quoted — and if I am not the right person for it, you hear that on the call rather than in an invoice. Start a conversation Money, before you ask The one figure that is safe to publish: $800 USD is the floor for a small, well-defined feature. Everything above that comes from a scoping call, in writing, before any work starts. There is no price on these cards, and that is deliberate A number printed before I know the scope is a guess, and a guess that turns out low is worse for you than no number at all. Each card carries a price slot that fills in the moment a service has a rate I can stand behind; until then it says so plainly. Most engagements begin within 48 hours of an agreed scope. You get a timeline, a milestone plan, and a fixed price wherever the scope allows one. Starts are fast and dated Bug fixes are covered for thirty days at no charge. After that nothing renews on its own: maintenance and new work continue on a retainer only if you ask for one. Thirty days of fixes, then your call Typed code, a structure a second developer can navigate, and a README covering environment variables, deploy steps, and the two things I would improve next. The repository and the store listings are yours; where it is deployed and what it depends on is named in the handover. A hand-over someone else can use I tell you what the work does not include as plainly as what it does. If a request is wrong for your stage or your budget, you hear that instead of a quote — and usually a cheaper alternative with it. Scoping that is allowed to say no Service catalogue summary Typical reply within one business day Why teams hire me Four things I am explicit about up front Services Full-stack web apps — services — Ahsan Mahmood — React and TypeScript front to back, with a real data layer and real access control. Cross-platform mobile apps — services — Ahsan Mahmood — One React codebase shipped to the web and to Android through Capacitor. Browser-extension engineering — services — Ahsan Mahmood — Manifest V3 extensions for Chrome, Edge and Firefox, built to survive store review. Firebase backend platforms — services — Ahsan Mahmood — Firestore data models, security rules and the read budget that keeps them on the free tier. API and integration development — services — Ahsan Mahmood — Typed, versioned HTTP APIs and the third-party integrations around them. Security and auth products — services — Ahsan Mahmood — Authentication, authorisation and row-level access control that holds up to a real probe. AI product development — services — Ahsan Mahmood — Shipping AI features into real products — retrieval, agents and the plumbing that keeps them affordable. E-commerce and WordPress solutions — services — Ahsan Mahmood — Storefronts and content sites, including migrations off WordPress when that is the right answer. Platform architecture consulting — services — Ahsan Mahmood — Reviewing and reshaping an existing codebase so the next year of work is cheaper than the last. ## Skills — what Ahsan Mahmood works with, and how deeply URL: https://aoneahsan.com/skills Twenty-six tools across five groups, each with a stated depth and the year it entered the stack. No percentages, because nobody measured one. Read the matrix by group or by level. Skill list reloaded. Skills by category Backend Cloud Craft Database DevOps Frameworks Frontend Languages Mobile Other Platform Working practice Tools Loading… Everything Filter skills by group Never shipped, never studied seriously. There is no rung on this ladder low enough to make listing it honest. Blockchain and smart contracts Five course completions exist and they are on the CV, framed as what they are. They are not accreditations, so they do not raise a rung on this page. A count of certifications Design systems are on the list because I build and maintain them in code. Brand identity, illustration and art direction are not the same job and I do not take them. Visual design as a deliverable There is no Apple Developer account behind this work, so nothing has ever shipped to the App Store. Android and the web are where the store listings actually are. Native iOS and Swift It is real inside the Perkforce role and it stays on the CV there. It is not part of what I lead with any more, so it is not on this list dressed up as current. MongoDB as a headline What is deliberately absent A skills page that lists only strengths is a sales page. These are the gaps worth knowing before you scope anything with me. Three honest depths. Every tool below carries two things I can defend: a depth I chose in plain English, and the year it entered my stack. Read the matrix down a column to see where I am strongest, or across a row to see what I bring to one kind of problem. Read the matrix What the depths mean Grouped by where it sits in a stack, ordered the way I reach for it. Read this before the matrix Three words, each defined against something checkable If a definition does not match what you need, say so on a call. I would rather lose the work than have you find out in week three that “Working” meant what it says. Every skill, by category and by depth Group The capability matrix Depths are a claim. The products are the proof — every one of them shipped, most of them still running. Go and open a few, then tell me what you need building. Start a conversation A list is not evidence See what these built The number that is not here A depth is a category, so it is shown as one — a position in a matrix, not a length. A year is a number, so it is shown on a timeline, where length means elapsed time and nothing else. Never A percentage, a score, or a five-star rating Stated Depth, in one of three words The year it entered the stack This page used to say React 95%. Nobody measured that. It came from a lookup table that turned the word “expert” into a number so a progress bar would have something to fill. Skill summary Years of production work, including the parts that break. Deep I have debugged this under deadline pressure, know what it is bad at, and would argue with its documentation. If a project lives or dies on it, I do not need a second pair of hands. Shipped with it across more than one project, by design rather than by tutorial. Proficient I can choose the right shape up front instead of discovering it in review. I still reach for the documentation on the unusual paths, and I say so rather than guessing. Real shipped use, and a shallower floor than the two above it. Working It has done a job in production. I would still want review on anything load-bearing, and I would tell you that before quoting rather than after. Search skills by tool name The other honest number When each one entered the stack Years are a real quantity, so they get the encoding a real quantity deserves. Nothing leaves this list once it is on it — a tool I have stopped reaching for is recorded on the CV, not quietly deleted from here. What I work with ## Plans and pricing — Ahsan Mahmood URL: https://aoneahsan.com/pricing Three account plans — Free, Client and Retainer — with every limit stated, and which engagement grants which plan. Every limit, compared across the three plans Going down a plan never deletes anything. Whatever is over the smaller limit becomes read-only and stays readable, exportable and yours. Every limit, side by side Not a summary — this is the whole set. A limit that exists and is not on this table would be enforced without ever being stated, which is the one way a comparison table can lie. Limit What a downgrade does Everything you wrote stays. Threads, requests, votes and attachments are all still there and still readable. Nothing is deleted for not paying. Deleting somebody's only copy of their own writing is not a billing policy. Whatever is over the smaller limit becomes read-only, and the page says which items those are rather than hiding them. from $1,500 Codebase rescue or audit 1 week for the audit for the audit Security, performance, dependencies, type safety and CI/CD, with a prioritised roadmap and effort estimates. Two things worth saying first Every figure below is a floor . Compliance work, an urgent deadline or an unusual stack moves the quote up, and I say so before you commit to anything. The first thirty minutes are free — scoping, a recommendation, and an honest answer if the work is not something I should be doing. What the work costs, and which plan it puts you on These are the six ways I take on work. They are priced per engagement, not per month, and the account plan follows from the engagement rather than being a separate thing you buy. $60–80 Mentoring and code review booked in one-hour slots per hour $60 an hour for individuals, $80 for teams. One-to-one sessions, PR-level review and architecture office hours. from $3,000 MVP build 4–8 weeks Discovery, a production-grade app, the backend behind it and CI/CD. Typically $3,000–$8,000. per engagement from $8,000 Production app 8–16 weeks Full architecture, web and mobile, role-based access, audit logging, analytics and handoff. Typically $8,000–$25,000. Puts your account on from $2,400 Fractional retainer monthly, 30-day notice per month Around thirty hours a month, a monthly architecture review, priority bug fixes and direct access. from $800 Small feature or bug fix 1–2 weeks One feature, integration or fix on a codebase that already exists, with thirty days of bug fixes after delivery. Hosting, third-party services and store fees are yours and are paid to those vendors directly. For anything this page does not price, get in touch . Plans Ask me something else Because there is no processor here and no callback for anyone to forge. You pay through a method we agreed, I check that it arrived, and I set the plan with an end date. It costs a working day of latency and removes an entire class of fraud. Why is there no card form? Yes — PKR, EUR, GBP and AUD, converted at the rate on the day we agree the work. Invoices are written in USD so there is one number both of us are looking at. Can I pay in a currency other than USD? It falls back to the plan named on your card above, that day, without anything having to run. The expiry is worked out every time your plan is read rather than by a nightly job — a job that fails to run would quietly leave people on a plan they have stopped paying for. What happens the day my plan runs out? Questions people actually ask No, and it works the other way round. You hire me, and the account plan follows so that the thread we are working in stops being rationed. The plan is a consequence of the engagement, not a gate in front of it. Do I need a paid plan to hire you? One person with their own sign-in, their own threads and their own limits — not a shared login. Three is the minimum, because below three it is the Client plan wearing a different word. Every seat gets better limits than Client rather than Client's limits divided between people. What counts as a seat on Retainer? See what I take on Every number on this page comes out of the same table the site enforces, so a limit cannot be advertised here and be different in practice. The plans, and what each one is for. Reading this site needs no account and no payment. An account adds the two things that are yours — a thread for a question you asked, and a vote on what gets built next. The paid plans are what happens when I am working for you: the limits move, and your thread stops waiting behind everybody else's. Loading the plans… The app shows which plan you are on and whether a report is waiting, and stops there — no payment link, no upgrade button, no price. That is a Play Store rule about selling digital goods outside Play Billing, not a preference, and working around it is how an app gets pulled. In the Android app, this section is not here Every grant carries the date it runs out and the plan it falls back to. A plan that never ends is a free tier with extra steps, so this product does not have one. I set the plan, with an end date Bank transfer, Wise, or whatever we agreed. Every method, with its own fees stated, is on the payment page. Open the payment page Send it The form beside this records it against your account, so when the transfer lands I already know whose it is. It does not charge anything and it does not change your plan — I do that by hand once the money arrives. Tell me it is on the way Ways to send a payment Paying, and telling me about it There is no checkout on this site and no processor sitting in the middle. You send the money the way you already send money, and then you tell me — those are two separate steps because they happen in two different places. USD The plans Rendered from the plan table itself. Add a fourth tier to that table and it appears here, in rank order, with its real limits — which is the point of keeping tiers as rows rather than as a fixed list in the code. no payment, no card, no expiry What an account holds Cancelling stops the next renewal, not this one. You keep everything you have paid for until the end of the period you paid for, and it does not renew after that. Nothing is switched off early. The full versions are in the terms , and the data-use clause is also in the privacy policy under how the data is used — a practice that only appears in the terms is a practice nobody has disclosed. Three things about the paid plans Short, and here rather than only in the terms, because these are the three questions people ask after they have paid. Payments already made are not refunded. If something is wrong, tell me before you pay rather than after — the thirty-minute call exists partly for that. Free accounts help pay for themselves. I may use content from free accounts to improve and train the features I build here. Content in a paid plan is not used that way without your explicit consent. You can object at any time and I will exclude your account, and moving to a paid plan stops it outright. Three plans, and the middle one is an engagement Awaiting a grant Free Then returns to Runs until There is no checkout on this site. You pay the way we agreed, tell me on the pricing page, and I set the plan with the date it ends. Every grant carries that date — a plan that never ends is a free tier with extra steps. How a plan gets here Nothing is charged automatically and nothing renews on its own, so there is no card here to remove. Attachment size Standard Priority Replies Reply priority Feature requests Seats Question threads Open question threads Unlimited unlimited Plans attach to an account. Signing in with Google creates one — there is no password anywhere in this product, and no separate sign-up step. You are not signed in Payment reported recently A report is already open on this account. Already reported Ask about it Payment reported — I will set the plan once it lands Choose which plan the payment is for. Sign in before reporting a payment. This records a request. It does not take money, and it does not change your plan. Nothing changes on your account until I have checked the payment against what arrived — usually within a working day. There is nothing to report against just now. Recording… A payment reference Anything that lets me find the payment — a transaction id, the date and amount, or the subject line of the email. A grant nobody can trace back to a payment is the one that gets disputed. Sign in first — a report has to attach to an account, or there is nothing to put the plan on when the money arrives. Report the payment Which plan is it for? Withdraw it above if you sent the wrong reference. Payment report withdrawn Report a payment See every plan Withdraw the report Withdrawing… your current plan ## About — Ahsan Mahmood, full-stack developer in Lahore URL: https://aoneahsan.com/about Who I am, what I build, and how I work. Eight years shipping production software, twenty-three products live across web, Google Play and the extension stores, and every figure on this page carries its source. Biography I wire products up to models. I do not train them. Not a data scientist I build interfaces and I care about them. Brand design and illustration are somebody else's craft. Not a designer Worth being explicit about MongoDB and GraphQL. Both are real on my CV and both belong to the Perkforce years — I do not build new products on either. Not in my stack Yes. Since March 2026 I have been independent, working on my own products full time and taking client projects alongside them. Fixed price, milestone, retainer or fractional — whichever fits the shape of the work. Are you available right now? Because most of it is not mine to publish. The clients that are public — through my CV, npm and the app stores — appear on the projects page with the work attached. An anonymous “15+ happy clients” badge proves nothing, so it is not here. Why is there no client list on this page? Things people ask me first No. I have no Apple Developer account, so nothing I build has gone to the App Store. Android and the web are the surfaces I can genuinely take through to release. Do you ship to iPhone? Yes, and it is a common engagement. It starts with an audit — code quality, security rules, performance, dependency health — and produces a prioritised roadmap before a line is changed. Can you take over a codebase somebody else wrote? Both. I have led delivery for a small team as a project lead, and I have shipped entire products alone. On a team I am most useful owning a vertical slice end to end rather than a layer across everything. Do you work with teams, or only solo? Pakistan Standard Time, UTC+5. I work asynchronously by default and routinely schedule calls across US, UK, EU and Australian hours. What time zone are you in? Hi — I'm Ahsan Mahmood. I'm a full-stack software developer in Lahore, Pakistan. I have been shipping production software since January 2018 — five companies, and since March 2026 my own. I build the whole product: the data model, the security rules, the interface, the Android packaging, the store submission, and the documentation somebody has to read afterwards. See the work Start a conversation The person who modelled the data is the person who wrote the security rules, packaged the Android build and answered the store reviewer. Nothing is handed over a wall, so nothing falls off it. One person accountable for all of it Full IP transfer, documentation that matches the code, and a handover that assumes I am not available. If the project only runs while I am on it, I have not finished. Leave it maintainable by someone else How I work In the store listing, in the docs, and on this page. Every limitation you read here was cheaper to write down than to discover. Say what it does not do A green build is not evidence that a feature works. I exercise the actual flow as a real, non-admin user — which is how the interesting failures get found, because they are invisible to every static check. Run it before calling it done A fixed number attached to an unscoped brief is a number one of us is going to be unhappy about. The call is free and it is where I say whether I am the right person. Scoped on a call, before it is quoted Read the longer story The projects page has everything with a link that resolves. If you would rather just describe the problem, the contact form goes straight to me and I reply within one business day. Get in touch Have a look, or say hello See the projects Delivered since 2018 the numbered CV list Professional experience since January 2018 Four numbers, four sources These are the only figures I publish. Two of them are re-probed before every deploy, because they move — and a stale number is the easiest thing on a page to disprove. Live today web, Play and the extension stores Maintained on npm registry probe, 2026-08-01 Asynchronous by default Available for work Where I am and how I work Not taking new work right now Read the longer biography Google Play listings, all packaged with Capacitor from the same web codebase rather than rewritten. That covers the parts people forget: the merged manifest audit, the Data Safety form, the ten App-content declarations, and a listing written to pass review rather than to sell. Android There is no iOS half. I have no Apple Developer account, so nothing here has ever shipped to the App Store — and a portfolio that claims a platform it has not shipped to is the first thing a client finds out about. Documentation sites, one per product that needs one. They are not an afterthought here — a package nobody can work out how to install is a package nobody uses, and the docs site is usually the thing that decides which of those it becomes. Docs Browser-extension listings across the Chrome Web Store, Firefox Add-ons and Edge Add-ons — Manifest V3, packaged three times from one repository and submitted three times. No remote code, which is both the store rule and the reason these keep passing review. Extensions Where it ships The same codebase, packaged five ways. Counts come from the July 2026 probe and are re-verified before publish. Packages published to npm under the `aoneahsan` maintainer. One caveat I would rather state than have found: that count is what the registry returns for the maintainer, so it includes a package scoped to a client organisation and two legacy pairs that were renamed rather than replaced. npm Web applications live today — hiring and health platforms, a laboratory information system, a link-management SaaS, exam preparation, tax compliance, a Slack archiver and several developer toolboxes. React and TypeScript throughout, deployed to Firebase Hosting or Cloudflare Pages. Web The full list What I reach for Where the backend goes Where a product needs a server it is Laravel or Cloudflare Workers. Where it needs file storage or transactional email it is FilesHub, a platform I built and operate myself. Where it needs a database it is usually Firebase or Supabase on a free tier, with the query budget treated as a real constraint rather than a formality — an unbounded list read is a bug on the first day and an outage on the day the collection grows. The constraint I choose No iOS. There is no Apple Developer account, so I do not ship to the App Store and do not claim to. No feature that needs a paid API in the middle of it, unless you are paying for that API and know you are. No architecture that only works while traffic is small — the free tier is a budget, not an excuse. A product should cost nothing to run. Free-tier infrastructure, work done in the browser wherever the browser can do it, and no paid service in the critical path. That is not frugality for its own sake — it is what makes it possible for one person to keep twenty-three things alive at the same time without a monthly bill that grows faster than the products do. It rules things out, and I would rather say which: How an engagement usually starts With a call, before anything is quoted. I would rather tell you on that call that I am the wrong person for the work than discover it together in month two. When I am the right person, you get one accountable person — whoever wrote the security rules is also the one who answers when the store listing is rejected. The long-ish version What I actually do If you only read one section, read this one. Everything below it is evidence for what it claims. That is a claim about maintenance more than about speed. I still maintain products I built years ago, which is a very effective way of finding out which decisions were actually good. Most of what I ship follows one pattern: a single React and TypeScript codebase that becomes a web app, an Android app and a browser extension. Not three teams and three repositories that drift — one codebase, packaged three ways with Capacitor and Manifest V3, so a fix lands everywhere at once and a feature is not silently missing on the surface nobody checked. The parts that usually get skipped are the parts I care about: honest store listings, documentation somebody can follow, and links that resolve. One React and TypeScript codebase becomes a web app, an Android app and a browser extension. {{liveProjects}} of them are live right now. Based in Lahore, Pakistan — working with clients worldwide. Full-stack developer. I ship products, not prototypes. Lahore, Pakistan ## Biography — Ahsan Mahmood, eight years and six roles URL: https://aoneahsan.com/bio The longer story: where it started in January 2018, the six roles since, the pattern I settled on, the constraint I work under, and what I deliberately do not claim. The constraint I work under A product should cost nothing to run. Free-tier Firebase or Supabase, work done in the browser wherever the browser can do it, Cloudflare Workers or Laravel where a server is genuinely required, and no paid service sitting in the critical path. This started as a necessity and turned into a design tool. A constraint that hard forces the question does this feature need a server at all? — and the answer is no far more often than the industry’s defaults suggest. It is also the only reason one person can keep more than twenty products alive simultaneously without a bill that grows faster than the products do. Where it needs file storage or transactional email, that is FilesHub — a platform I built and operate, precisely so the free-tier rule did not have to bend the first time an app needed to send a password reset. It rules things out, which is the point. A constraint that never costs you anything was not a constraint. Flutter & Dart — The Complete Guide Udemy (Academind) Unity Game Tutorial: Board Game — Ludo 3D Udemy Four of the five are game development, which is not what I do for a living. They are here because they are true, not because they are relevant. React Native — The Practical Guide Complete C# Unity Game Developer 2D Udemy (GameDev.tv) Complete C# Unity Game Developer 3D BSc Software Engineering Virtual University of Pakistan FSc Pre-Engineering Govt. Shalimar Graduate College, Lahore Matriculation in Science Govt. Comprehensive Higher Secondary School Elsewhere Languages The same person, in the places where the work actually lives. I check these links on a weekly cadence, because profile links rot silently — one of mine was dead for months and nobody noticed, including me. Based in Experience At a glance Now Since Time zone Eight years, six roles, and one pattern that stuck. The short version is on the about page. This is the long one — where it started, what each role was actually for, the pattern I settled on, and the things I deliberately do not claim. The long-form biography The evidence for it is on the projects page, the full record is in the CV, and if you would rather skip both and just describe your problem, that works too. Get in touch Read the CV That's the story See the projects No client count and no testimonial averages that are not computed from testimonials you can read on this site yourself. No download or install counts, anywhere on this site. They fluctuate, nobody can verify them, and the number is never the interesting part. What I do not claim No iOS. There is no Apple Developer account and there never has been. Nothing I have built has shipped to the App Store. An earlier version of this page said I had deployed to the Apple App Store, that a browser extension of mine had thousands of users, and that I was an expert in several things. None of those were checkable and one of them was simply false. None of that makes the record smaller. It makes it checkable, which is worth more — and every figure on this site can be traced back to a probe, a payslip or a public registry. No skill percentages. The skills page gives a level and a year, because "React 95%" is a decimal place invented to look precise. So, plainly: No MongoDB or GraphQL in new work. Both are on my CV, both are real, and both belong to the Perkforce years. What it cost Breadth, honestly. I am a generalist by construction: I know enough of a database, a build system, a store review process and an accessibility tree to ship, and I am not the deepest person in the room on any single one of them. On a team I am most useful owning a vertical slice end to end rather than a horizontal layer across everything. The pattern I settled on Somewhere in the Zaions years I stopped starting from scratch. The shape that stuck is a single React and TypeScript codebase that becomes three products: a web app, an Android app packaged with Capacitor, and a Manifest V3 browser extension. One repository, one set of tests, three store listings. The argument for it is unglamorous. A fix lands on every surface at once, a feature cannot be quietly missing from the platform nobody opened this week, and — the part that actually decides it — one person can hold the whole thing in their head. Three codebases need three people or a lot of forgetting. The Perkforce years ran alongside that: four years of SaaS delivery across web and mobile, the Slack app, the Microsoft Teams app, and enough code review and delivery coordination to know which arguments about architecture are worth having. What I publish native-update — over-the-air web-bundle updates for Capacitor apps, with checksum and signature verification and automatic rollback when a bundle fails to boot. The work is also registered on ORCID, which is where the citable record of it lives: Twenty-five packages on npm under the aoneahsan maintainer, four of them open source. Two are worth naming because they are the ones I would defend in a code review: Alongside those, twenty-three products are live across the web, Google Play and the Chrome, Firefox and Edge extension stores, and sixteen documentation sites exist because a package nobody can work out how to install is a package nobody uses. strata-storage — one storage API spanning localStorage, IndexedDB, cookies, SQLite, Keychain and the filesystem, so a call is correct on web and on native without the caller knowing which it is. Last reconciled against the source record on 1 August 2026. Two of the figures on this page are re-probed before every deploy. Current The six roles, and what each was for Building and maintaining his own products full time — 23 live across the web, Google Play and the Chrome, Firefox and Edge extension stores. 25 packages published to npm, four of them open source. Available for client projects and contracts. Mar 2026 — Present Open Source & Community Projects Developer Pixel-perfect UIs for multiple web projects in HTML5, CSS3 and JavaScript, aligned to design specs. Consistent delivery in a fast-paced environment. Jan 2018 — May 2018 Frontend Developer | UI Developer Developed and maintained custom WordPress themes and plugins. Advanced custom fields and custom theme development for various clients. Jun 2018 — Sep 2018 WordPress Developer | Custom Theme Developer Five companies and then my own. Two of them overlap in what they taught me and I have not padded them out to look like six distinct chapters. Owned delivery of Perkforce SaaS features across web and mobile — React, Node.js, TypeScript, Apollo GraphQL client and server. Cross-platform mobile through Capacitor, with production store releases. Built the Slack app and the Microsoft Teams app integrations. Led implementation planning, code review and delivery coordination as team and project lead. Jul 2022 — Feb 2026 Senior MERN Stack Developer (Remote) Delivered a custom management system in Laravel and Angular for educational content and user management. Improved performance and reliability by optimising Angular workflows and adding PWA capabilities. Pixel-perfect responsive UI in HTML5, CSS3 and JavaScript. Oct 2018 — Feb 2020 Web App Developer | Laravel | Angular Delivered multiple full-stack client products — React, Angular, Node.js, Laravel, TypeScript-first. Built native and hybrid mobile apps on Node.js/Express with MongoDB and MySQL backends. Shipped a production Manifest V3 browser extension for Udemy playback controls. Owned end-to-end execution: requirements, architecture, implementation, deployment. Mar 2020 — Jun 2022 Team Lead | SaaS App Developer (MERN/MEAN) Where this started January 2018, as a frontend developer at NetRoots Technologies in Lahore, building pixel-accurate interfaces from design specs. It was a junior job and it was a real one — which matters, because the version of this story I used to tell said 2018 was when I was learning HTML and CSS as a hobby. That was flattering and it was wrong. The first line on the timeline below is a payslip, not a tutorial. What followed was four months of WordPress themes, then a year and a half at PNY Trainings on a Laravel and Angular management system, which is where I learned that most of the difficulty in software is not the framework. It is the part where somebody has to keep using the thing after you have finished being interested in it. Course completions Education What I studied, and what I didn’t The distinction matters and most portfolios blur it. The first list is education. The second is course completions — they are certificates of having finished a video series, not accreditations, and calling them anything grander would be the same kind of inflation as a fabricated client count. The longer version Title On this page Every date and role below is read from the same record the CV is built from, so the two cannot drift apart. Years shipping One React and TypeScript codebase becomes a web app, an Android app and a browser extension. {{liveProjects}} of them are live right now. Based in Lahore, Pakistan — working with clients worldwide. Full-stack developer. I ship products, not prototypes. Full-stack software developer Lahore, Pakistan ## Curriculum vitae — Ahsan Mahmood URL: https://aoneahsan.com/cv The full record: every role since January 2018, the numbered project list, education and course completions. Preview it here or download the PDF or Word file. Address About this file Formats PDF and Word PDF only Kept current by Hand, in the admin panel Last updated Length Size PDF Word Choose a document A document is a summary of things that already shipped. The projects are the things themselves — running, installable, and mostly with their source open. Start a conversation Read the work instead? See the projects The long record Curriculum vitae Everything, in order. Read this one if you are assessing depth — it carries the numbered project list the résumé has no room for. Documents Open in a new tab Download PDF The same three controls as the top of the page. Download Word Ask for a format PDF and Word are on this page. Plain text, LaTeX, or a version cut for one specific role are all fine to ask for — say which and it comes back the same day. Need it in another format? CV in a different format Use the contact form Written down so you can tell before downloading whether it answers your question. Every role since January 2018 The numbered project list Education and course completions Languages and contact channels What is in this document Current stack and headline The four most recent roles Key experience Contact channels A CV records what was true when it was written. The live record — current role, what is shipping now — is on the site itself, and the two can disagree for a few weeks after something changes. The file is a dated snapshot Loading document Same record, cut differently. Every version here is maintained by hand, not generated from the one above. Other versions Or ask for it by email A blocked frame reports nothing back to this page — there is no error to catch and no way to tell a refusal from a slow network. Extensions and strict corporate networks block it routinely. That is why the direct link sits under the frame before you press anything, and stays there afterwards. If the preview stays blank The preview renders in Google’s viewer, which means loading it tells Google you are here. Nothing is requested until you press — and the download links work whether or not you ever do. Read it before you download it Open the PDF directly The one-page version Résumé One page, current stack, four most recent roles. Read this one if you are screening quickly or forwarding it to someone who is. See the résumé I have been building software professionally since 2018, and this document is the long version: the roles, the stack, and the products I shipped end to end rather than contributed a layer to. If you would rather see the work than read about it, the projects are one click away. One React and TypeScript codebase becomes a web app, an Android app and a browser extension. {{liveProjects}} of them are live right now. Based in Lahore, Pakistan — working with clients worldwide. Full-stack software developer Lahore, Pakistan ## Curriculum vitae — Ahsan Mahmood URL: https://aoneahsan.com/resume The full record: every role since January 2018, the numbered project list, education and course completions. Preview it here or download the PDF or Word file. Résumé See the full CV Full-stack developer. I ship products, not prototypes. One React and TypeScript codebase becomes a web app, an Android app and a browser extension. {{liveProjects}} of them are live right now. Based in Lahore, Pakistan — working with clients worldwide. Full-stack software developer Lahore, Pakistan ## Testimonials — six LinkedIn recommendations, unedited and dated URL: https://aoneahsan.com/testimonials Six recommendations written on LinkedIn between 2018 and 2020, carried word for word with the date and relationship attached. No star ratings, because LinkedIn recommendations do not carry any. Published on npm under a public maintainer name, with their source, their issues and their release history in the open. Code is harder to overstate than a testimonial. See the stack Packages anyone can install Live on the web, on Google Play, and in the Chrome, Firefox and Edge extension stores. Open one, use it, and form your own view — that is a stronger signal than a sentence from 2019. Browse the archive Products that are running right now Five companies since January 2018, each with a start month, an end month and what the role actually involved. Verifiable by anyone who wants to pick up the phone. Read the CV An employment record with dates on it Only real ones, and only with the platform named on the card. Those platforms do issue star ratings, so an entry from one of them would carry its genuine rating — which is exactly why the LinkedIn entries carry none. Will you add reviews from Upwork or Fiverr? Can I speak to a former client directly? The recommendations section of a LinkedIn profile. Each writer chose to publish it under their own name and it stays visible there. This page is a copy, not the record. Where do these come from? All platforms Clear filters Filter by platform The honest gap What covers the years these do not What stands in their place Three kinds of evidence that are current, checkable, and did not need anyone to write a paragraph about me. No recommendations are published right now. Recommendations, older than the work. These were written on LinkedIn between 2018 and 2020 by teachers, colleagues and clients from early in my career. They are carried word for word, typos included, with the date and the writer’s relationship attached to each one. Nothing here has been tidied up. Six LinkedIn recommendations normally sit here, written between 2018 and 2020. While none is published, what covers the years since is further down this page: products you can open, packages you can install, and an employment record with dates on it. Why there are no stars Read them See the evidence instead Quoted as given, from the platform each was left on. The people above worked with me years ago and said so in public. What I would rather be judged on is what is running today — so tell me what you need built, and I will tell you honestly whether I am the right person for it. Start a conversation Old words, current work See the work first What is not on this page Everything that was given is still here: the words, the date, the writer, their role, and how they knew the work. Date, name, role, relationship The full text, unedited Star ratings and the 5.0 average This page used to show a five-star row on every card and a 5.0 average across the top. LinkedIn recommendations do not carry a rating — the number was assigned when the six were loaded into the database, then averaged back out as if it had been measured. Fiverr LinkedIn Other Upwork Archive summary Before you weigh them What a LinkedIn recommendation is, and what it is not Dated The most recent was written in April 2020. Nothing here covers the last several years of work. That gap is real and it is not hidden behind a sort order. For recent evidence the shipped products are the better answer — they are running, in public, and you can open them without asking anyone. They are old It is a colleague choosing to write something in public under their own name. That is worth something, and it is not the same thing as a review of delivered work. Every one of these is checkable: the writers are named and their profiles are public. Nothing here asks you to take an anonymous quote on trust. Verbatim Spelling, grammar and phrasing are exactly as written. One says “safistfied”. One ends in an emoticon. One is written in the formal register of a character reference rather than a review. Cleaning any of that up would make six different people sound like one copywriter, which is how fabricated testimonials read. They are unedited Removed LinkedIn recommendations are free text. There is no star field to fill in. The old page showed five stars on each card and a 5.0 average above them. That number entered the database at seed time and was then averaged back out, which made an invented value look measured. The same rule that deleted the skill percentages applies here. There are no ratings Newest Oldest Could not load Loading… What people have said Check them at the source Every recommendation on this page exists on a public LinkedIn profile, written by a named person. If one of them matters to your decision, read it where it was written. The archive Recommendations are entered in the admin panel one at a time. When there are none, this page says so rather than filling the space. The archive I hereby confirm that Mr. AHSAN TARIQ S/O TARIQ MAHMOOD is known to undersigned for the last 3 years he possess good moral character integrity and honesty. I strongly recommend him and wish him good prosperous future. Abid Mahmood — Computer Specialist — National Engineering Services Pakistan (NESPAK) linkedin · Ahsan's teacher · April 7, 2020 Ahsan is a great person, we both have been working on a project in which I found him loyal and hardworking. No matter how tense a project is, he always wear smile. As a team member, Ahsan Mahmood earns my highest recommendation. Abdul Hanan Latif — Google & Facebook Certified Digital Marketing Expert — CEO Ideoversity linkedin · Worked together · August 28, 2019 He's one of the best developer i have ever met, and work with. He's young, energetic and have lovely personality. While in working environment he's one of the best guider, developer and designer. I know Ahsan from past 6 months and i have found him, that he can make anyone to walk into his shoes. Dr Muhammad Dawood — Senior Associate | MP-II — Government Of Pakistan | Founder Intellisnc & RIAIC linkedin · Managed Ahsan directly · August 28, 2019 Ahsan Mahmood project manager of ONAJAH.com. Ahsan is highly skilled and cooperative person. One of talented person in our team (ONAJAH.com), we had the pleasure of working with Ahsan at ONAJAH project as our project manager. He is sharp and quick learner and a good team lead. Best of luck with your future. Onajah com — CEO — Onajah.com linkedin · Worked together · August 28, 2019 Ahsan developed my website on a WordPress. Ahsan is highly skilled and cooperative. Ahsan gave me excellent customer services. I am very safistfied with the good job he did for my organization. SIEPRA INSTITUTE — Autonomous Economic Policy Research Analysis think tank — SIEPRA INSTITUTE (Somalia) linkedin · Client · May 17, 2019 One of talented person whom I know, I had the pleasure of working with Ahsan form last year at the NetRoots Company, collaborating on several project teams. He is sharp and quick learner. Best of luck with your future 🙂 Asad Mahmood — Team Lead — Croyten | JavaScript Expert linkedin · Was senior to Ahsan · October 4, 2018 ## Where I am — Ahsan Mahmood URL: https://aoneahsan.com/address Based in Lahore, Pakistan, working remote worldwide on Pakistan Standard Time. Working hours, the overlap window, and every way to reach me. The client work on this site was built for people in other time zones who never met him. Tell me what you are building and roughly when you need it — the first call is thirty minutes and costs nothing. Distance has not been the hard part Start a conversation See the work first Contact details Email Availability City Country Timezone Open in Google Maps Loading location On a map Centred on the city, which is as precise as this page gets. Nothing is requested from Google until you press — and the link beside it works either way. Show the map The location above is still accurate; only the embedded map is hidden. The map is switched off The practical detail City, country, timezone and working hours Published Best for anything with detail or an attachment. Getting hold of me Four direct channels, no form in the way. Email is the one that always gets read; WhatsApp is the fastest during working hours. A phone number and an email address are how a freelance developer gets hired, so they are public and stay public. A street address is not, which is the whole distinction this page is built around. These are published on purpose Texas number Second US line, same person. Not set in the admin panel WhatsApp Fastest during Pakistan working hours. Work number US line. Voicemail is checked. Every channel in the next section, all of them direct Reachable anyway Remote worldwide Where to find me City only How much of this is public Address A city and a country are useful to you. A street and a door number are useful to nobody who is not already invited, so they are not on this page — and the admin panel is where that decision lives rather than being hard-coded. Street-level detail is withheld. Nothing below a city is stored on the public record, and the map above is centred on the city for the same reason. When I am awake Local time Usual overlap UTC-6 to UTC+5 Reply time Street, apartment, postal code and any landmark Withheld Working hours Lahore, Pakistan Lahore Pakistan Asia/Karachi Replies within one business day Elsewhere GitHub LinkedIn npm YouTube Email Website ## Contact — Ahsan Mahmood, full-stack developer in Lahore URL: https://aoneahsan.com/contact Start a conversation about a project, a contract or a question. Every message opens a thread you can read the reply in — not a form that disappears into an inbox. Collaboration Feedback General Hiring Other Project inquiry Technical help Open source, a talk, or building something together. Something on this site or in one of the products. Anything that does not fit the others. A role, a contract, or a fractional arrangement. Tell me in the message and I will sort it. A specific piece of work you want scoped. Email Best for anything with an attachment, and the one I keep a record of. Reach me directly Working hours No account needed for any of these. They land in the same place as the form does — the form just gives you somewhere to read the reply. Based in The two US numbers are reachable during Pakistan working hours, which is roughly overnight in North America. WhatsApp is the one that gets seen fastest. Work line A US number that rings here. Leave a message if the hour is awkward. Typical reply time Second line WhatsApp Fastest during Pakistan working hours. Fine for a one-line question. Where I am and when Remote worldwide I say so on the first reply, and where I can I say who might be. Discovering it in month two costs you far more than hearing it on day one costs either of us. What if the work is not right for you? Before you write No. There is no Apple Developer account here, so nothing I build has shipped to the App Store and I do not take work that needs it. Android and the web are the surfaces I can take through to release. Do you take on iOS work? Yes, and it is a reasonable thing to ask for. Send the document with your first message and I will read it before the call rather than during it. Will you sign an NDA before we talk? Not honestly. A fixed number attached to an unscoped brief is a number one of us ends up unhappy about, so the first call is about scope and it is free. Can you give a fixed price from a description? No. Email and WhatsApp work with no account at all. Signing in exists so the reply has somewhere to live that both of us can find again — it is a convenience, not a gate. Do I have to sign in to contact you? One business day for a first reply, and that is measured from when I read it rather than from when you sent it — Pakistan Standard Time, UTC+5, so a message sent in the North American afternoon is usually answered while you sleep. How quickly do you actually reply? Feature requests The board is public, other people vote on it, and I build from it in order rather than from whoever emailed most recently. A feature for one of my products Find the project The repository issue tracker is the right place — it is public, it is searchable, and the next person with the same bug finds it. A bug in a published package CV and résumé It is downloadable without asking me, in the formats a recruitment system will actually parse. You need the CV as a file There may be a faster door Four things arrive here often enough that they have somewhere better to go. Support options There is a page for it with the account details on it. Nothing is transacted on this site. You want to support the work What is it about? Write a message Subject, what it is about, and the message. That is the whole form — everything else I can ask you in the thread. Message At least ten characters. Rough is fine — I would rather have the real problem than a polished summary of it. Back to the channels Every other way to reach me is still above — the address, the hours and the social accounts, all unchanged. Use whichever suits you; they arrive in the same place the form would have. The form is off just now Thread opened — the reply lands here Ready for another message. Subject This becomes the thread title, so a specific one is easier to find later. Send and open the thread Opening the thread… Or reach me directly Contact Say what you need. I answer. One business day, usually less. If the work is not something I should take, I say so on the first reply rather than three weeks in — that is faster for both of us and it is the only part of this I can actually promise. What happens to your message Not a form submission into an inbox — a conversation with a URL, which you can reopen. It opens a thread You read it under My queries , and you can answer without starting again. I reply in that thread Both sides keep the whole exchange. Nothing depends on either of us finding an old email. It stays there Or have a look first Twenty-three products, every link resolving, and the ones that are open source say so. If something there answers your question, you have saved yourself a message. See the projects What I take on My threads Prefer not to sign in? The details above need no account. Email me instead Sign in to open a thread Sign in with Google A thread needs somewhere to send you back to. Signing in with Google gives your message a home you can return to — that is the whole reason for it. Without an account I have nowhere to put the reply except your email inbox, which is fine, and the address below does exactly that. It does not subscribe you to anything. There is no newsletter on this site to be added to. What signing in does not do Google is the only sign-in this site has. There is no password to create and none to forget. Email me. It reaches the same person, and I reply to it the same day. If you would rather not Your name, your email address and the messages in your own threads. Nothing else, and no third party is involved beyond Google's sign-in. What I store Contact form All my threads Write another Sent Your thread is open. Open the thread One business day. Longer only if the answer needs me to read something first, and I say so in the thread rather than going quiet. Usual reply time Thread aoneahsan@gmail.com Lahore Pakistan Replies within one business day Elsewhere GitHub LinkedIn npm YouTube Email Website ## Submit a query — Ahsan Mahmood URL: https://aoneahsan.com/submit-query Open a two-way thread about a project, a package or a piece of work. You read the reply here, in the same conversation, rather than waiting on an email. Open source, a talk, or building together. On this site or one of the products. Anything that does not fit the rest. A role, a contract, or fractional time. Say so in the message and I will route it. Work you want scoped and quoted. Something I published is not behaving. Continuing an existing one keeps the context with it. See them . Choose files Attachments Attachments are switched off on this deployment, so the control is absent rather than decorative. Cancel What do you need? Three fields. The category only decides how fast I get to it, so pick the nearest one and move on — I would rather have the message than the perfect label. At least ten characters. Versions and exact error text save us a round trip. Ready for another query. Remove Specific beats polite. This is what you will scan for in six weeks. Open the thread Opening the thread… Uploading… My existing threads Submit a query Open a thread, not a ticket. This is the same conversation on both sides. You write, I answer in the thread, and you answer back in the same place — no reference number, no "do not reply to this address", and no thread that quietly ends because somebody's mail client won. Start writing Sometimes that answer is "I need to read your code first, give me until Thursday" — but it arrives, because going quiet is the thing that actually wastes your week. A first answer inside one business day What happens after you press it If it affects other people it belongs somewhere public, and I will say so in the thread and link it rather than fixing it silently for you alone. A bug in something published usually becomes an issue Answered is not closed. You can come back to it a month later and the whole exchange is still there, in order. The thread stays open until you close it There is one person here. Nothing auto-replies, nothing assigns you a priority you cannot see, and the category you picked does not send you anywhere I am not. It is read, not triaged by a robot You already have one open. Close it and I will still see it, or move to a plan that allows more — the limit is about keeping the queue answerable, not about the message. This plan allows one open thread at a time. See what the plans allow Your open thread Ask a question The request board Contact Asking for a feature instead? A thread reaches me; the request board reaches everyone. If what you want is a change to one of the products, the board is the honest route — other people vote on it, and I build in that order rather than by who wrote most recently. My threads All my threads Open another That thread is live. Thread Where the reply appears In the thread itself, and in the list under My queries with an unread marker on it. What it costs you Your name and email address, which I need anyway to answer. Nothing is sold, shared or mailed to you unprompted. Sign in first Sign in with Google A thread belongs to somebody, so it needs to know who you are. Google is the only sign-in this site has — no password to invent, none to forget. It exists so the reply has a home you can come back to, and so nobody else can read your thread. Other ways to reach me Prefer not to sign in? The contact page lists an email address and a WhatsApp number that need no account at all. What signing in shares Who can read a thread You and me. Not other visitors, and not anyone signed in as somebody else. ## Feature requests — ask for what gets built next URL: https://aoneahsan.com/feature-requests The public request board. File what you want built, vote on what other people asked for, and watch a request cross the 1,000-vote line that puts it on the roadmap. Voter names are public; the reasons people give are not. Nothing is held for review. It is on the board with your name on it the moment you press the button, and you can cancel it just as fast. It appears immediately People vote, and they have to say why That is a commitment to build it, not a date for it. Anything under the line I still read — most of what has changed on this site came from requests that never got near the threshold. The slot comes back when you cancel Afterwards What happens to it A bug. That is faster by email — a bug does not need votes and I would rather fix it than watch it accumulate them. Anything private. This board is public, including the title you write. Your email address is not shown anywhere on it, but everything you type into this form is. Work you want doing for you. That is a project , and it starts with a conversation rather than a vote count. What not to file here Almost there Building Early Just started Browse the board Or just email me Email me Wanted something that is not here? File it. Even a request that never reaches the threshold tells me what people expected to find and did not — and that has changed more of this site than the votes have. Tell me anyway. The board is the fast way to say it and it is shut just now, so the slow way is the only one open — it reaches the same person and gets read the same way. Cancel this request File your own Yours to manage You filed this one. Cancelling takes it off the board and gives you the slot back — and you get a few seconds to undo it, which is why there is no dialog asking whether you are sure. Somebody else filed this, so it is theirs to cancel. If it is a duplicate of something you already asked for, say so in your vote and I will merge them. All requests Filed by Filed Not found File a new one That request is not on the board Either the link is wrong, or the person who filed it cancelled it. Cancelling frees their slot and takes the request off the board — the votes already cast stay attached to it and are not moved anywhere else. This one was approved without reaching the vote threshold. The votes below are the real count and are not a total it had to reach. Approved by the maintainer Progress to approval This request has crossed the line and is on the roadmap. That is not a date. Threshold met Needed Net votes Net votes are upvotes minus downvotes, recounted from the individual ballots every time this page renders. Nothing here is a stored total. There yet Voters Why it was declined Reference Back to the board Watch on YouTube Attached A recording of the problem Attached by the request author The person who filed this attached a video. It is hosted on YouTube, so nothing is requested from them until you press play — and the link below works whether or not you do. Build this Counts +1 toward the threshold. Voting is closed just now, along with the rest of the board. The count above is final for as long as it stays shut, every ballot already cast is still attached to this request, and nothing here has been recounted or removed. Your vote Say which way you are voting and why. The reason is the part I actually read — a tally tells me what is popular and nothing about what it would change for you. Add your weight to this Not this Counts −1. Net votes are the measure, so this genuinely moves the bar back. Voting needs an account — one ballot per person is the only thing that makes the count mean anything. Google is the only way in, and your name is what appears on the list below. Cast my vote Recording… Voted against One ballot per person, so this is counted. Your reason is below, and only you and I can read it. Voted for You have voted on this Which way? Against For Someone Visible to you as the owner, and to the person who wrote it This one is waiting for its first ballot. Yours would be the one that starts it. Nobody has voted yet Public record Who voted for this Reason kept private Your reason — visible to you and to Ahsan The board is empty. File the first request and it appears here straight away. Nothing has been asked for yet Clear filters Search requests Sort Filter requests by status Kind The board Ask for what gets built next The board is closed just now — nothing is being filed and nothing is being voted on. Everything already here is kept exactly as it was, votes attached, and the counts below are still the real ones. Email is the way through in the meantime. Feature requests Cancel Cancelled. The slot is free again. Your side of it Sign in with Google What you have filed What you filed is still yours. It is kept with its votes attached and comes back to this page the moment the board opens again — nothing was cancelled, nothing was recounted, and no slot was taken off you. What I store about you Restored. It is back on the board. Voting and filing both need an account, because a board where anyone can vote as many times as they like measures nothing. Signing in with Google is the only way in — there is no password to forget and I never see one. Undo New request Just browse the board File a request Why does it matter? The concrete version wins votes. What you tried, what happened, what you expected. Ask for it The request form File a feature request It goes straight onto the public board with your name on it, where anyone can vote it up or down. There is no triage queue and nothing waits for my approval to appear — the votes are the triage. Not just now — the board is closed, so there is nowhere public for a new request to land. Email reaches me directly in the meantime, and it reaches me faster. Reference links (optional) One per line. There is nowhere public for a new request to land, so this form is not taking any. Send it by email instead — it reaches the same person, and a request that arrives that way is still read and still counts. Email it to me The board is closed just now Nothing open yet. Filing your first one is what this page is for. Which one is it about? Not one in particular Or it is about this site. Something else — name it Portfolio aoneahsan.com itself. strata-storage The storage package. Trizlink Short links and link-in-bio. ZTools The browser toolbox. You are at the limit Sign in to file one First Your name goes on the request, and the three-request limit needs to know whose requests they are. Google is the only way in — there is no password, and I never see one. File this request Filing… What should be built? This is the line people vote on, so it does most of the work. “RSS with full post bodies” gets read; “better feeds” does not. What kind of thing is it? A feature Something one of the existing things should also do. A feature belongs to something that already exists, so it needs to say which. A whole product Something that does not exist yet and probably should. A video, if you have one Optional, and YouTube only. It appears on the request page behind a play button — nothing is requested from YouTube until somebody presses it. YouTube links only — that is the only player the request page can embed. Filing and voting are both paused. This is a queue that shuts rather than one that quietly swallows things while nobody is reading it — and every request already filed is kept, with its votes still attached to it, exactly where it was. Email me instead Back to the site The board stays a queue rather than a wishlist. Cancel one of yours to open another, or move to a plan that allows more. Request limit reached This account cannot change requests. That request no longer exists. Only a pending request can be cancelled. Once it is on the roadmap it is out of your hands — and mine. That request was filed by somebody else. Sign in to cancel a request. That request is not cancelled. This one was cancelled by an administrator, so it is not yours to put back. Restoring this would pass your open-request limit. Cancel another one first. Sign in to restore a request. That vote could not be recorded. Please try again. This account cannot vote. This request was declined, so voting is closed. A vote needs at least 100 characters of reasoning. Ten votes an hour is the limit. Try again shortly. Sign in to vote. Approval The rules How the board works Four rules, all of them enforced by the thing that stores the votes rather than by the page you are reading. Every number below is printed from the same constant the write path checks. One ballot per person per request. Changing your mind means the old ballot is replaced, never added to. One vote each A vote carries a reason Three open requests Closest to approval Newest Most votes Approved so far Requests filed Votes cast Everything Approved Cancelled In progress Pending Shipped Feature Product Cancelling a request hides it from the board and frees your slot. The votes already cast on it stay attached to it and are not recounted anywhere else. Your name and the request titles you file are public. Your email address is not shown anywhere on this board. Who can see what Your name is public when you vote. A request page lists everyone who voted on it, by name, so the count is something you can check rather than something you have to trust. The reason you give is not. Your comment is visible to you and to me, and to nobody else. It is where you say the awkward version, and it is only useful if you can. Change your vote Why? Argue against Where do you stand? Your name is public on this board. Your reasoning is not — I read it, nobody else does. Your vote is recorded. Retract Sign in to vote Cast your vote Support this You voted ## Blog — working notes on shipping software alone · Ahsan Mahmood URL: https://aoneahsan.com/blog Working notes from building and maintaining a catalogue of products alone — architecture, mobile, security and the trade-offs behind them. Filter by topic, search by title. All posts Newest first. Pick a topic, or search by title. Everything, by year The full run in one place — no filter, no pages. Writing Most of what is written here came out of a real project with a real deadline. If you are making the same decisions, the first call is 30 minutes and free. Email me Building something like this? Start a project Latest Read the post Working notes Not tutorials. These are the decisions I had to make anyway, written down while they were still fresh — what the zero-cost constraint removes, where a free tier stops being free, and the failures that passed every automated check on the way to production. Published Most recent Topics What building twenty-three products alone actually teaches you. Loading posts Next page Previous page Full-stack developer in Lahore, Pakistan. One React and TypeScript codebase becomes a web app, an Android app and a browser extension — twenty-three of them are live. Everything written here came out of building and maintaining them alone. GitHub Ahsan Mahmood About the work Copy this snippet Loading the post Read next Email Facebook Hacker News LinkedIn Reddit Telegram WhatsApp Close Copy the link to this post Share this post Found this useful? On this page Updated Search posts by title, summary or body Everything Filter by topic In the feed right now What does zero-cost infrastructure cost you? — Nothing paid sits in the critical path, so the product costs nothing to run. What that takes off your bill, what it forbids, and where a free tier ends. Can I hire a full-stack developer remotely? — Yes. Pakistan Standard Time, UTC+5, asynchronous by default, with calls across US, UK, EU and Australian hours, and a first reply within one business day. How long does a web app, an MVP or a full product take? — Small features 1–2 weeks, an MVP 4–8, a full production app 8–16. Where those ranges come from, how each is delivered, and what makes one of them slip. How much does custom software cost, and what is in a quote? — $800 is the floor for a small, well-defined feature. Everything above it comes from a scoping call, in writing. What that quote contains, and what it omits. Can one codebase ship web, Android and a browser extension? — Yes. One React and TypeScript repository becomes a web app, a Capacitor Android app, and a Manifest V3 extension, packaged three times for three stores. What is actually changing in web development — Not predictions. Four shifts I have already had to design around: crawlers that do not run JavaScript, the platform absorbing dependencies, types as the… Pricing and plans without a payment processor — Why my products take payment through a redirect and an admin grant instead of a checkout integration, what that costs, and the entitlement bugs that bite… How this site is built, and what I rebuilt to fix — React 19, Vite, TanStack Router, Supabase and a click dummy that is the visual specification. Including the outlet bug that made nine service pages render… The JavaScript features I actually reach for now — Intl for anything a person reads, structuredClone, at(), Object.groupBy, and the platform APIs that replaced dependencies. Plus the number formatting that… Micro-animation: the 100ms rule, and everything else is… — Motion that earns its place versus motion that is decoration. Why acknowledgement beats a toast, why the browser gives you two free properties, and the… From junior to senior: what actually changed — Eight years from a frontend job in Lahore to running twenty-three products alone. The three shifts that mattered, and the one I was slowest to make. A feature-request board on Firestore, and the rules that… — Vote counts that cannot be forged, a status workflow that means something, and the security-rule subtlety that makes a list query fail while the… Reflectify 3-3-1: a journaling app that deliberately has… — Three gratitudes, three wins, one focus. Why the entries never leave the phone, why there is no model reading them, and what a constraint does to a product… PregnancyPal: building health software when the data is… — Week-by-week tracking, vitals, fertility and a community — on a free tier, with no cloud functions. What health data changes about every architectural… FilesHub: building the storage backend instead of paying… — A self-hosted Laravel platform that replaced Firebase Storage and every transactional-email vendor across an entire portfolio. What it took, and the… Trizlink: one React codebase as a web app and an Android app — Two surfaces from one repository, with a third planned. What actually got shared, what could not be, and the free-tier limits that shaped the data model. Refactoring a monolith you cannot stop shipping — The rewrite that loses a third of your features, the seams worth finding first, and why "delete the dead code" is the highest-value refactor nobody… Firebase Auth and social sign-in: the parts that are… — Social login is easy to add and easy to get subtly wrong. Custom claims that lie, rules that trust the client, and why an extension cannot use the Auth SDK… Accessible design systems: what React Aria gives you… — Moving from a styled component library to headless primitives plus Tailwind. What accessibility work is genuinely free, what is not, and the rich-text rule… Admin panels: four failures that ship looking complete — Every one of these was found in a live admin panel, and none is visible in a code review or a green build. Plus why the screen list should be derived from… One codebase, web and Android: what Capacitor actually costs — Shipping the same React app to the browser and to Google Play. The parts that are genuinely free, the parts that are not, and the config bug that ships… TypeScript patterns I actually use, and one that cost me… — Typed translation keys, satisfies, discriminated state and branded ids — plus the conditional type that took my typecheck from 14 seconds to 170 and how I… SEO for a single-page app, without pretending to have a… — AI crawlers do not run JavaScript. Neither, reliably, does anything else you care about. Here is how I get real content into static HTML without adopting… Why I reach for TanStack Router — Typed routes turn a whole category of navigation bug into a build error. Here is the one that shipped to production in my own app before I moved, and what… A zero-cost backend architecture that actually holds — Twenty-three products, no servers, no monthly bill. Here is what the constraint removes, what it forces you to design properly, and the day a free tier… ## Support the work — Ahsan Mahmood. Account details, nothing charged. URL: https://aoneahsan.com/payment Send support directly by bank transfer, mobile wallet, Wise or Payoneer. Nothing is charged on this page — it hands over account details, states what each method costs, and lets you copy or share them. Also on this page If this is about paid work rather than support, start there instead. The same methods, without the referral layer — the version linked from the site footer. This page hands over account details. You send the transfer from your own bank or app. Before you go further There is no checkout, no amount to enter and no card form. Nothing is charged here. No receipt is generated automatically. Email me the reference and I will confirm it. Which one should you use Those are my own notes on each method, not quoted rates. The provider shows you its fee before you confirm, and that number is the one that counts. Copy all details Nothing published for this method yet, so there is nothing to copy. Ask by email and I will send it directly. The details Each method below is switched on from the admin panel and carries only the fields that have actually been published. Anything unset says so. Nothing is published here at the moment. That is a setting rather than an error, and the methods themselves are untouched. Not published yet Nothing to copy — this one is filled in from the admin panel and has not been set. Bank transfer Cryptocurrency Other methods PayPal Transfer platforms Mobile wallets Ask a question first See the details How this works Three steps, and the second one happens in your bank rather than on this page. Loading payment methods Payment details The account details for paying an invoice or supporting this work are on the website. Nothing is charged in the app, and nothing here is a purchase. Open it on aoneahsan.com The account details are not being published at the moment. Nothing has been closed and nothing has moved — email me and I will send whatever you need directly. Ask by email This page is switched off just now Payment options Seven rails, and the only real decision is which one costs you least. Nothing is charged on this page — it hands over the account details and you send the transfer from your own bank or app. Not just now — none of the rails is being published at the moment. Nothing has been closed and nothing has moved, and email reaches me directly until they are back. Support a specific project Ways to send a payment Use the contact form Questions about any of this If a method is missing, a detail looks wrong, or you want an invoice against a transfer, ask before you send anything. A wrong transfer is slow and expensive to unwind. App identifier: Dismiss the referral note Referral note dismissed Referred from a project You arrived from another project The referring product sent an app identifier but no readable name, so this is all I can tell you about where you came from. Choose where to send it, or copy the link. Close Share Sends a link to this page. The details themselves are not put in the message — they are read here, from the page, where they stay current. Copying announces itself and the button turns green for a moment. If the clipboard is blocked, the button says so instead of failing quietly. Sends the link to this page, keeping whichever product referred you. Send to Support Ahsan Mahmood’s work Share this page Every field below has its own copy button, and each one confirms that it copied. Fields the admin has not filled in say so rather than handing you a placeholder. Copy the details Your bank, wallet app, Wise or Payoneer does the transfer. This site never sees an amount, a card, or anything you type into them. Send it from your side There is no automatic receipt. Send the reference by email and you get a reply confirming it landed. Tell me it is on the way Support Everything here is a direct transfer to a person, not a checkout. Pick the rail that costs you least, copy the details, and send whatever it is worth to you. Not just now — the account details are not being published at the moment. Nothing has been closed and nothing has moved, and email reaches me directly until they are back. Support the work Nothing here is a purchase and nothing is unlocked by it. Whatever you send goes straight into keeping these products maintained and the next thing built — no processor sits in the middle and no platform takes a percentage. Thank you for supporting this work Account plans are a separate matter and are set out on the product’s own pricing page. Supporting the work here does not change one. Recorded against Bank Crypto Other Platform Wallet ## Payment options — Ahsan Mahmood. Bank transfer, mobile wallet, Wise or Payoneer. URL: https://aoneahsan.com/payment-options The ways to send a payment or support directly: bank transfer, mobile wallet, Wise and Payoneer, with the trade-off stated for each. Nothing is charged on this page — it hands over account details. Also on this page If this is about paid work rather than support, start there instead. The same methods, without the referral layer — the version linked from the site footer. This page hands over account details. You send the transfer from your own bank or app. Before you go further There is no checkout, no amount to enter and no card form. Nothing is charged here. No receipt is generated automatically. Email me the reference and I will confirm it. Which one should you use Those are my own notes on each method, not quoted rates. The provider shows you its fee before you confirm, and that number is the one that counts. Copy all details Nothing published for this method yet, so there is nothing to copy. Ask by email and I will send it directly. The details Each method below is switched on from the admin panel and carries only the fields that have actually been published. Anything unset says so. Nothing is published here at the moment. That is a setting rather than an error, and the methods themselves are untouched. Not published yet Nothing to copy — this one is filled in from the admin panel and has not been set. Bank transfer Cryptocurrency Other methods PayPal Transfer platforms Mobile wallets Ask a question first See the details How this works Three steps, and the second one happens in your bank rather than on this page. Loading payment methods Payment details The account details for paying an invoice or supporting this work are on the website. Nothing is charged in the app, and nothing here is a purchase. Open it on aoneahsan.com The account details are not being published at the moment. Nothing has been closed and nothing has moved — email me and I will send whatever you need directly. Ask by email This page is switched off just now Payment options Seven rails, and the only real decision is which one costs you least. Nothing is charged on this page — it hands over the account details and you send the transfer from your own bank or app. Not just now — none of the rails is being published at the moment. Nothing has been closed and nothing has moved, and email reaches me directly until they are back. Support a specific project Ways to send a payment Use the contact form Questions about any of this If a method is missing, a detail looks wrong, or you want an invoice against a transfer, ask before you send anything. A wrong transfer is slow and expensive to unwind. App identifier: Dismiss the referral note Referral note dismissed Referred from a project You arrived from another project The referring product sent an app identifier but no readable name, so this is all I can tell you about where you came from. Choose where to send it, or copy the link. Close Share Sends a link to this page. The details themselves are not put in the message — they are read here, from the page, where they stay current. Copying announces itself and the button turns green for a moment. If the clipboard is blocked, the button says so instead of failing quietly. Sends the link to this page, keeping whichever product referred you. Send to Support Ahsan Mahmood’s work Share this page Every field below has its own copy button, and each one confirms that it copied. Fields the admin has not filled in say so rather than handing you a placeholder. Copy the details Your bank, wallet app, Wise or Payoneer does the transfer. This site never sees an amount, a card, or anything you type into them. Send it from your side There is no automatic receipt. Send the reference by email and you get a reply confirming it landed. Tell me it is on the way Support Everything here is a direct transfer to a person, not a checkout. Pick the rail that costs you least, copy the details, and send whatever it is worth to you. Not just now — the account details are not being published at the moment. Nothing has been closed and nothing has moved, and email reaches me directly until they are back. Support the work Nothing here is a purchase and nothing is unlocked by it. Whatever you send goes straight into keeping these products maintained and the next thing built — no processor sits in the middle and no platform takes a percentage. Thank you for supporting this work Account plans are a separate matter and are set out on the product’s own pricing page. Supporting the work here does not change one. Recorded against Bank Crypto Other Platform Wallet ## Privacy Policy · Ahsan Mahmood URL: https://aoneahsan.com/privacy-policy How data is collected, used and protected across this website and the Android app. Introduction This policy explains how information is collected, used, disclosed and safeguarded when you visit this website or use the Android app built from it. If you do not agree with it, please do not use the site or the app. It may change at any time; changes are signalled by the Last updated date at the top of this page and nowhere else — there is no mailing list and you will not be notified individually. Information collected Analytics Three providers run on this site, each answering a different question: Google Analytics 4 — anonymous usage: page views, session duration, device type, browser, and location at country or city level. Microsoft Clarity — anonymised session recordings: pointer movement, clicks and scrolling, used to find layouts that do not work. Amplitude — interaction and funnel data: which features get used and in what order. Portfolio interaction Which projects are opened, and how long they are read Category filters and search queries used on the projects page Which payment methods are viewed Outbound clicks — GitHub, live demos, store listings CV and resume downloads Errors and notifications Sentry — JavaScript errors, performance problems and crash reports. OneSignal — only if you opt in to notifications: a device push token and a device identifier, held so the updates you asked for can be delivered. Collected automatically IP address (anonymised), browser and version, device and operating system, pages visited and time spent, referring site, country or city, screen resolution, and interactions such as clicks and scrolls. How the data is used Service improvement — understanding how the site is actually used. Performance — finding and fixing bugs, crashes and slow pages. Content — learning which projects and writing are worth keeping. Security — detecting abuse. Communication — replying to anything you send through a form. What is not done with it: it is not sold, not used for advertising, not shared with marketers, and not used to track you across other websites. Free accounts Free accounts help pay for themselves. We may use content from free accounts to improve and train the features built here — the writing you put into a question, a feature request or a vote comment. This is separate from everything in the paragraph above and does not change it: nothing is sold, nothing goes to advertisers, and nothing is shared with marketers. Content in a paid plan is not used this way without your explicit consent. You can object at any time by writing to the address at the end of this document and your account is excluded, and moving to a paid plan stops it outright. The same clause is in the Terms & Conditions under plans and payment. File storage Files are stored on FilesHub — profile images, project screenshots, CV and resume documents, and portfolio assets. Public files are served over a CDN and need no authentication. Files are kept for as long as they are referenced by something on the site. Android app permissions The app asks for the minimum needed for the features you choose to use, and nothing is collected without an action you started. It does not request camera, location or media-storage permissions. Picking an image Attaching an image to a conversation or setting a profile picture opens the standard Android system file picker. No Android permission is required for this, and it only happens when you tap an upload control. Notifications On Android 13 and later the app asks for notification permission the first time you enable a notification, never at launch. Declining it leaves every other feature working. Sharing and third parties Data reaches the providers named above and no one else. Most of them are processors acting on instruction; Microsoft Clarity is the one that also acts as a controller of the session recordings it holds, which is why it is named separately in the store data-safety declaration. Data is disclosed outside that set only where the law requires it. Security Traffic is encrypted in transit with TLS. Database access is governed by Postgres row-level security — public content is read-only and every write is scoped to the account that owns it. Administrative access is authenticated and limited to the site owner. No system is perfectly secure, and this one is not claimed to be. What is claimed is that the amount held is small enough that a breach could not expose much. Your rights Under the GDPR and comparable laws you may ask for: Access — a copy of what is held about you Rectification — correction of anything wrong Erasure — deletion, covered in its own document Restriction — a pause on processing Portability — your data in a machine-readable file Objection — to processing based on legitimate interest Requests go to the contact address at the end of this document and are answered within 30 days. There is no charge. Cookies and tracking Cookies, local storage and session storage are used for preferences and for the analytics described above. The full breakdown — every cookie, who sets it, and how to switch it off — is in the Cookie Policy . Children This site is not directed at children under 13 and no data is knowingly collected from them. If you believe a child has provided information here, write to the address below and it will be removed. Retention Analytics — retained on each provider’s own schedule, typically 14 months. Error reports — 90 days. Messages you send — until you ask for them to be deleted. Uploaded files — while something on the site still references them. Changes to this policy Changes are published on this page with a new Last updated date. Material changes to how data is handled will also be summarised at the top of this document rather than buried in a section. Contact Questions about this policy, or about anything held about you: Email — aoneahsan@gmail.com Phone — +1 713 913 4704 WhatsApp — +92 304 6619706 ## Terms & Conditions · Ahsan Mahmood URL: https://aoneahsan.com/terms The rules for using this website, its content and the app built from it. Acceptance of terms Using this site means accepting these terms. If you do not accept them, stop using the site. They apply to every visitor, with or without an account. Use licence You may view and download one copy of the material here for personal, non-commercial use. That licence is a grant of permission, not a transfer of title, and under it you may not: modify or copy the material; use it for any commercial purpose or public display; attempt to decompile or reverse-engineer any software here; remove any copyright or other proprietary notation; mirror the material on any other server. The licence ends automatically if you breach it. Disclaimer The material here is provided as is. No warranty is given — express or implied — of merchantability, fitness for a particular purpose, or non-infringement. Nothing here is professional advice. Code, patterns and opinions published on this site are what worked in a particular project, which is not the same as what will work in yours. Limitations of liability Neither the author nor his suppliers are liable for any damages arising out of the use of, or inability to use, the material on this site — including loss of data or profit, and including cases where the possibility of such damage was known. Accuracy of materials Material here may contain technical, typographical or photographic errors. Nothing is warranted to be accurate, complete or current, and content may be changed at any time without notice. There is no commitment to update it. Links to third-party sites This site links to sites it does not control — repositories, store listings, live demos and client work. Those links are not an endorsement, and their contents are their owners’ responsibility. Follow them at your own risk. Intellectual property The design, text and original code of this site belong to Ahsan Mahmood. Project names, logos and screenshots belonging to clients or to open-source projects remain theirs and appear here as reference. Published npm packages carry their own licences, which are stated in each repository and take precedence over this section. Plans and payment Some accounts are on a paid plan. Every plan and every limit it carries is set out on the pricing page , which is rendered from the same table the site enforces — so what is advertised there is what applies. Cancelling Cancelling stops the next renewal. You keep everything you have paid for until the end of the period you have already paid for, and it does not renew after that. Nothing is switched off early. Refunds Payments already made are not refunded. Going back down a plan Nothing is deleted when a plan ends. Anything over the smaller plan's limit becomes read-only and stays readable and exportable — deleting somebody's only copy of their own writing is not a billing policy. Free accounts and the content in them Free accounts help pay for themselves. We may use content from free accounts to improve and train the features built here. Content in a paid plan is not used that way without your explicit consent. You can object at any time by writing to the contact address below, and your account is excluded. Moving to a paid plan stops it outright. This is also stated in the Privacy Policy under how the data is used. Modifications to the service Any part of this site may be revised or withdrawn without notice. Public URLs are treated as a commitment and are kept working where a page is retired. Governing law These terms are governed by the laws of Pakistan, and you submit to the exclusive jurisdiction of its courts for any dispute arising out of them. Contact Questions about these terms: aoneahsan@gmail.com . ## Cookie Policy · Ahsan Mahmood URL: https://aoneahsan.com/cookie-policy Every cookie and storage key this site sets, who sets it, and how to switch it off. What cookies are Cookies are small text files placed on your device when you visit a site. They let it remember preferences and understand how it is being used. Four mechanisms are in use here, and “cookie” below is shorthand for all of them: browser cookies — small text files; local storage — client-side data that persists; session storage — the same, discarded when the tab closes; web beacons and pixels — tracking images. How they are used Essential functionality Theme preferences (appearance, accent, radius, density, type scale, typeface, surface, cursor, motion, sound), language, and session management. Analytics and performance Understanding how visitors move through the site, measuring page performance, and finding which content is worth keeping. Error tracking Capturing and diagnosing technical faults so they can be fixed. The cookies themselves Category Keys Can you disable it? Strictly necessary `theme_mode`, `theme_accent`, `theme_font_pack` No — the site cannot render without them Analytics `_ga`, `_gid`, `_clck`, `_clsk`, `amplitude_*` Yes Strictly necessary cookies hold nothing that identifies you — they hold the appearance settings you chose, so the site does not reset itself every visit. Third-party cookies Google Analytics 4 — Google LLC. Site analytics and behaviour. Microsoft Clarity — Microsoft Corporation. Session recordings and heatmaps. Amplitude — Amplitude Inc. Product analytics and funnels. Each operates under its own privacy policy, which governs what it does with what it collects. Managing cookies Browser settings Every major browser can block or delete cookies. Blocking the strictly necessary ones will reset your appearance settings on every visit; blocking the rest changes nothing you can see. Where to find the setting Chrome — Settings, Privacy and security, Third-party cookies Firefox — Settings, Privacy & Security, Cookies and Site Data Safari — Settings, Privacy Edge — Settings, Cookies and site permissions Analytics opt-out Google publishes a browser add-on that opts you out of Analytics everywhere. Browser “do not track” signals are also honoured. Contact Questions about cookies: aoneahsan@gmail.com . ## Data Deletion & Account Removal · Ahsan Mahmood URL: https://aoneahsan.com/data-deletion How to have your data deleted, what goes automatically, and what is kept and why. Overview You can have your data deleted. This page says how, what happens without you asking, and the narrow set of things that are kept anyway. Most visitors have nothing to delete — browsing this site anonymously leaves analytics events that were never tied to you in the first place. Automatic deletion Some data expires without any request: Analytics events — on each provider’s retention schedule, typically 14 months Error reports — 90 days Session recordings — 30 days Session storage — when you close the tab Account deletion If you signed in — the only reason to have done so is to send a message or vote on a feature request — deleting the account removes the profile, every message thread you started, your votes and comments, and any file you uploaded. It is immediate and it cannot be undone. Content that other people replied to is removed along with it rather than left orphaned under a deleted name. How to request deletion Email aoneahsan@gmail.com with the subject Data deletion request . Say which address or account the request covers. You get an acknowledgement within 48 hours. The deletion is completed within 30 days and confirmed in writing. No reason is required and none will be asked for. Third-party data Data held by the analytics providers is deleted through their own processes, which are started on your behalf as part of the request above. Their timelines are theirs, not this site’s — Google, Microsoft and Amplitude each publish their own. What is kept, and why A small set survives a deletion request: Records a law requires to be kept Anything needed to establish or defend a legal claim Aggregated statistics that no longer identify anyone Backups, until they rotate out on their own schedule One thing a deletion cannot reverse If content from a free account has already been used to improve or train a feature — the reservation set out in the Privacy Policy and the Terms — the content is deleted with everything else, and the improvement it already contributed to is not undone. That improvement identifies nobody and cannot be traced back to you, but saying a deletion reverses it would not be true. Objecting stops it going forward, at any time and without deleting anything. So does moving to a paid plan. Getting a copy first You can ask for an export before deleting. It arrives as JSON within 30 days and contains everything held under your account. Questions Anything about deletion: aoneahsan@gmail.com . ## Data Security · Ahsan Mahmood URL: https://aoneahsan.com/data-security The measures protecting data here, stated at the level they can actually be verified. Overview This page sets out the security measures in place for the website and the app. It is written to be checkable: every claim below is something an outside observer could confirm. The practices follow the GDPR and comparable regimes. Note the wording — follow , not *certified against*. No audit has been performed and none is claimed. Security measures Measure What it means Encryption in transit Everything between your device and the servers travels over HTTPS with TLS 1.3. Postgres row-level security Database access is governed by rules, not by application code. Public content is read-only; every write is scoped to its owner. Authenticated administration The admin panel is behind Supabase Auth (Google sign-in) and is reachable by two fixed addresses. Data minimisation Only what a feature needs is collected. No unnecessary personal information is requested anywhere on the site. Data protection Data at rest sits in managed Postgres hosted by Supabase and inherits its encryption; the static site itself is served from Firebase Hosting. Access is limited to the site owner. Every list query is bounded, so no single request can pull an entire table. Third-party security Security depends on the providers named in the privacy policy — Google, Microsoft, Amplitude, Sentry, OneSignal and FilesHub. Each publishes its own security documentation, and this site’s posture is bounded by theirs. Your side of it Keep the account you sign in with secure — this site never sees that password. Sign out on shared devices. Keep your browser and operating system current. Report anything that looks wrong. Incident response If a breach affecting personal data occurs, affected people are notified within 72 hours of it being discovered, along with what was exposed and what to do about it. Supervisory authorities are notified where the law requires. Compliance Practices are built around the GDPR, the CCPA and the Play Store data-safety requirements. No third-party certification is held, and none is claimed — a badge saying otherwise would be exactly the unverifiable claim this document is written to avoid. Reporting a security issue Email aoneahsan@gmail.com with the subject Security . Reports are read the same day. Please give details enough to reproduce the issue, and please do not publish it before it is fixed. ## Privacy Policy for Reflectify 3-3-1 · Ahsan Mahmood URL: https://aoneahsan.com/privacy-policy-of-reflectify The privacy policy for the Reflectify 3-3-1 Android app. Overview This policy applies to the Android app Reflectify 3-3-1 , provided by Ahsan Mahmood. It explains in plain terms how information is handled when you use the app. Information you provide The app is built to work with as little personal data as possible. Reflections, notes, journal entries and goals you enter are used only to provide the feature you asked for. No account is required, unless a future version introduces account-based features. How information is handled Your entries and your usage stay on your device, unless a future version adds optional cloud, backup, sync, analytics or sharing features. Your personal information is not sold. No sensitive personal information is deliberately collected beyond what you choose to type in yourself. Permissions Storage or local device access — only where it is needed to save or read your own content on your own device. Internet access — for normal app function, updates and future support features. If a future version requests more, this policy is updated first. Third-party services The app relies on the basic platform services provided by Android and Google Play. If analytics, ads, cloud sync, sign-in or other external services are added later, this policy will be updated before or when that change ships. Children’s privacy The app is not designed to collect personal information from children. If you believe a child has provided information through it, get in touch so it can be reviewed. Changes to this policy This policy may be updated from time to time. Changes are posted on this page with a new Last updated date. Contact Ahsan Mahmood Email — aoneahsan@gmail.com Work phone — +1 713 913 4704 WhatsApp — +92 304 6619706 The app does not currently have a website of its own; this page is its privacy address. ## Privacy Policy for Scan & Generate QR Codes · Ahsan Mahmood URL: https://aoneahsan.com/scan-generate-qr-codes-privacy-policy The privacy policy for the Scan & Generate QR Codes Android app. Overview This policy applies to the Android app Scan & Generate QR Codes , provided by Ahsan Mahmood. Information used The app is built to work with as little personal data as possible. The text or link you scan, and the content you turn into a code, are used only to produce the result you asked for. How information is handled Scans and generated codes stay on your device, unless a future version adds optional cloud, backup, sync, analytics or sharing features. Your personal information is not sold. Permissions Camera — used only while you are actively scanning a code. The camera is not used in the background and images are not retained. Storage or local device access — only to save or read a code on your own device. Internet access — for normal app function and updates. If a future version requests more, this policy is updated first. Third-party services The app relies on the basic platform services provided by Android and Google Play. Any later addition of analytics, ads, cloud sync or sign-in is documented here before it ships. Children’s privacy The app is not designed to collect personal information from children. If you believe a child has provided information through it, get in touch so it can be reviewed. Changes to this policy Changes are posted on this page with a new Last updated date. Contact Ahsan Mahmood Email — aoneahsan@gmail.com Work phone — +1 713 913 4704 WhatsApp — +92 304 6619706 ## Privacy Policy for Zaions Listing · Ahsan Mahmood URL: https://aoneahsan.com/zaions-listing-app-privacy-policy The privacy policy for the Zaions Listing Android app. Overview This policy applies to the Android app Zaions Listing , provided by Ahsan Mahmood. Information you provide Listings, descriptions, images and contact details you enter are used only to provide the listing feature you asked for. No account is required unless a future version introduces account-based features. Device features and permissions Photo selection — the standard Android picker, opened only when you attach an image to a listing. Storage or local device access — only to save or read your own listing content. Internet access — for normal app function and updates. How information is handled Your listing content stays on your device unless a future version adds optional cloud, backup, sync or sharing features. Your personal information is not sold. Third-party services The app relies on the basic platform services provided by Android and Google Play. Any later addition of analytics, ads, cloud sync or sign-in is documented here before it ships. Children’s privacy The app is not designed to collect personal information from children. If you believe a child has provided information through it, get in touch so it can be reviewed. Changes to this policy Changes are posted on this page with a new Last updated date. Contact Ahsan Mahmood Email — aoneahsan@gmail.com Work phone — +1 713 913 4704 WhatsApp — +92 304 6619706 ## Video Controls Plus — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.videocontrolsplus Playback speed, loops and shortcuts on any HTML5 video. Chrome, Firefox and Edge. A recorded lecture plays at the speed the lecturer talked. You want it faster through the explanation and slower through the part with the equations, and the player hands you a short menu of presets where the one you actually need sits between two of them. Then you want the same passage again. The scrubber is the only tool on offer, and it is blunt enough that landing on the same point twice becomes a small task in its own right. None of that is the site's fault. A video player is built to play a video once, in order, for somebody watching rather than somebody studying. An extension that fixes it lives under a rule most web applications never meet. It may load no remote code. Not a Firebase Auth SDK, not Firebase or Google Analytics, not a script pulled from a CDN and nothing constructed at runtime with eval. That rule is written down here because it was learned the expensive way: a Chrome Web Store rejection, and every extension since has been built to the rule it produced. Everything this one runs is in the bundle the reviewer downloaded. Speed runs from 0.1x to 16x rather than through a preset menu. A-B loops take exact points. Audio boosts to 400%, with an equaliser, a compressor and a limiter sitting behind that number so the boost is usable rather than merely loud. Visual filters, zoom and picture-in-picture. Screenshots in one click. Notes that carry a timestamp and a thumbnail of the frame they were taken on, which is the difference between a note you can find in a month and one you cannot. 40+ keyboard shortcuts are configurable and they sync across devices, so a setup built once follows you to the next machine. 460+ per-platform services handle the named sites individually. YouTube, Netflix, Udemy, LinkedIn Learning, Coursera and Vimeo each build their player differently, and any other HTML5 element falls back to a generic path that still gets speed, loops and shortcuts. One WXT codebase produces three builds: Chrome and Edge on Manifest V3, Firefox on Manifest V2. 985 automated tests hold the shared half of that together. The companion site at vcpeai.aoneahsan.com ships 413 prerendered routes, including roughly 75 long-form posts, so an AI crawler reads the content instead of an empty shell. Three stores means three review queues. Manifest V2 and Manifest V3 are genuinely different platforms, so a feature is not finished when it works in Chrome — it is finished when the Firefox build does the same thing through a different API. That gap is where most of the release effort goes. A per-site service is a promise about markup somebody else owns. When a video platform redesigns its player, one service breaks and the rest keep working, which is the tolerable version of that failure and still a failure I have to go and fix. There is no analytics vendor and no session recorder, so there is also no dashboard telling me which of these features anyone uses. Store reviews are the feedback channel. There is no Safari version and no iOS build, because there is no Apple Developer account behind any of this. The extension is on the Chrome Web Store, on Edge and on Firefox, and the site is at vcpeai.aoneahsan.com. Open any of the four and the claims on this page are either there or they are not. Open the live site Chrome Web Store Firefox Add-ons Edge Add-ons Documentation What it does, and what that costs to build Speed control 0.1x-16x with presets A-B loop with precise custom points 400% audio boost + EQ/compressor/limiter Visual filters, zoom, picture-in-picture One-click screenshots & timestamped notes 40+ configurable keyboard shortcuts Cross-device cloud sync of settings 460+ per-platform services (YouTube/Netflix/LinkedIn) Built with WXT React 19 TypeScript Radix UI TailwindCSS Zustand Firebase CapacitorJS Worth knowing video media controls extension wxt manifest-v3 Who built Video Controls Plus? Ahsan Mahmood built it on his own, and the companion site is at vcpeai.aoneahsan.com. One WXT codebase, one person through the store submissions, and the same person wrote the per-site services that make it behave correctly on each video platform. Which video sites does it work on? YouTube, Netflix, Udemy, LinkedIn Learning, Coursera and Vimeo have their own handling, and any other HTML5 player falls back to the generic path. The named ones get individual treatment because each site builds its player differently, and a control that assumes one shape breaks on the rest. Sites outside that list still get speed, loops and shortcuts wherever the page uses a standard video element. Does Video Controls Plus work on iPhone? No — it is a desktop browser extension, and there is no iOS build or Safari version because there is no Apple Developer account behind this work. Chrome, Edge and Firefox are the three browsers it ships to, and they are the three it is tested against. What does the extension collect about me? Nothing goes to an analytics vendor, because the extension loads no remote code at all — no Firebase Analytics, no session recorder and no script fetched from a CDN. That rule came out of a Chrome Web Store rejection and it is now the standard across every extension here. The practical consequence is that I have no dashboard showing which features people use. Do my shortcuts and notes follow me to another computer? Yes, settings sync across devices, so the shortcut map and preferences you set on one machine are there on the next one. Notes are timestamped and carry a thumbnail of the frame they were taken on, which is what makes them findable weeks later. ## ZTools — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.zaions.ztools A privacy-first developer and power-user toolbox covering 20 categories, where the work happens in your browser rather than on someone's server. Most online tools ask for the file before they do anything with it. You paste a document into a converter and it travels to a server you have never heard of, run by a company whose terms you did not read, and the copy left on their disk stops being your business the moment it lands. The file comes back. The transaction looks free. Then the next task needs a different site. By the end of an afternoon a working process has been assembled out of strangers, and not one of them told you what it kept. ZTools inverts that. The work happens on your device unless there is a real reason it cannot, so for most of the toolbox the file never leaves the machine you opened it on. That constraint costs something on the first day and pays for itself every day after. There is no upload endpoint to secure, no storage bill that grows with use, and no server that must be awake at three in the morning so that a JSON formatter works. One person can keep it running. 20 categories, covering text, image, PDF, code, data, SEO, conversion, generators, math, finance and health. One React 19 and Vite 8 codebase produces three shipped things: the web app, an Android build on Google Play and a browser extension. They ship at version parity 2.0.0, so a fix is a release on three surfaces rather than one. Above the free core sits a BYOK AI suite of 10 SEO, AEO and GEO tools. You bring your own OpenAI, Gemini or Anthropic key. The request runs through a Cloudflare Worker on the free tier, which keeps the running cost at nothing. A ZTools Pro tier is billed through Stripe. The whole product can be resold under another brand or licensed at source, with licence verification built into the application rather than promised in a contract. ZTools is also where the prerendering approach the rest of this practice now uses began. 603 static HTML pages are generated at build time, so a tool page answers a crawler with its actual content instead of an empty document that promises to fill itself in later. Then there are the costs, and they are the kind you only meet after launch. Client-side processing means the device does the work. A large PDF on an old phone is slower than the same job on somebody else's hardware, and that trade is what the privacy claim is actually made of. Version parity across three surfaces means three review paths for one fix. The extension goes through a store review. The Android build goes through another. The web deploys immediately, so for a while after a release the three are not honestly in step. Bring-your-own-key is the fair way to run AI tools and it is friction. Somebody who wants that suite has to go and get a key before anything happens at all, and that friction is accepted on purpose. There is no iOS build. There is no Apple Developer account behind this work, so nothing here has ever gone to the App Store and the page does not pretend otherwise. The tools that must reach a server say so where you use them. The rest do not need one, and you can check that from the network tab in about ten seconds. Open the live site Google Play Chrome Web Store Documentation What it does, and what that costs to build 529 tools across 20 categories (519 free) Mostly client-side processing (files stay on-device) BYOK AI Growth Suite (10 SEO/AEO/GEO tools) ZTools Pro tier + white-label license verification Theme customizer (26 accent colors) Published Android app + browser extension 603 prerendered static HTML pages for AI search Built with React 19 TypeScript Vite 8 Radix UI TailwindCSS Firebase TanStack Query CapacitorJS Cloudflare Workers Worth knowing tools utilities productivity react firebase saas white-label Who built ZTools? Ahsan Mahmood built it, working alone, and it is live at ztools.zaions.com. The same person modelled the data, wrote the client-side processing, packaged the Android build for Google Play and shipped the browser extension. There is nobody behind that name: no studio, no team, no subcontractor. Is ZTools free? The core toolbox is free to use, and above it sit a ZTools Pro tier billed through Stripe and a BYOK AI suite where you supply your own OpenAI, Gemini or Anthropic key. The whole product can also be resold under another brand or licensed at source, with licence verification built in. What sits on which side of that line is managed in the product rather than described here, because a pricing sentence typed into a portfolio goes stale the week after it is written. Does ZTools work on iPhone? No. ZTools ships on the web, on Android through Google Play and as a browser extension; there is no iOS build, because there is no Apple Developer account behind any of this work. The web app opens in Safari on an iPhone like any other site, and that is the whole of the iPhone story. Do my files get uploaded to a server? For most of the tools, no — the processing runs in your browser and the file never leaves the device you opened it on. The tools that genuinely need a network call are the ones that cannot work any other way, and the AI suite is the clearest case: it sends your prompt with your own key. You can confirm all of this yourself from the network tab, which is the only version of a privacy claim worth making. Can ZTools be white-labelled or bought outright? Yes. The product carries a white-label sales path and a source-licensing path, both backed by licence verification in the application itself. That was built as a first-class part of the product rather than bolted on for a single buyer. Terms are a conversation rather than a published number. ## ClearHire — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.clearhire A hiring platform built around one idea: employment claims should be checkable. Resume builder, candidate and company workflows, a verified-employment… Two years at a company that no longer exists is still a line on a CV. Nobody checks it. The referee's number rings out. The HR mailbox bounces back. The recruiter has a shortlist to close by Friday and a stack of documents that all describe themselves. So the claim goes through, and the candidate who wrote only the truth competes against one who did not. That asymmetry is the market. ClearHire is built on free-tier client-side infrastructure. Firebase and FilesHub, with no paid backend anywhere in the path. That is an architectural decision before it is a budget one. A hiring platform whose running cost climbs with every idle profile has to start charging before it has anybody worth charging, and the parts that get cut first are the slow, careful ones. Here an idle account costs nothing to keep. Job seekers build structured, ATS-friendly resumes and profiles instead of uploading a document nobody downstream can parse. Companies post roles and review candidates on the same platform. Verified-employment workflows sit between the two. A claim can be confirmed through a workflow rather than asserted in a file, and the profile carries a trust chip only where that has actually happened. Around that core sit 50+ career tools, an ATS checker and a job-description analyser among them. A WXT browser extension at version 1.1.0 pushes the product out to where people really apply. It reads a LinkedIn profile into a structured record and autofills applications on LinkedIn, Indeed and Glassdoor. The structured record comes first, and the document is rendered from it. That ordering is what lets the ATS checker read a real profile rather than the shape of a PDF somebody exported from a word processor. Capacitor packages the same codebase into the Android build on Google Play. The public half carries 121 prerendered pages, each with 1000+ words of its own content and per-route FAQPage and HowTo structured data, so a search crawler and an answer engine both receive what a person reads rather than an empty document. The whole platform is licensable: a managed white-label deployment, a non-exclusive source licence or an exclusive buyout, each priced against a catalogue of 136 features. Now the costs. Verification is only ever as strong as the other side's willingness to answer, which means the product's central feature depends on people who have no obligation to it. A trust chip that means nothing is worse than no chip at all. So the workflow is strict about what it will confirm, and strict is slower and less satisfying than a checkbox somebody ticks about themselves. Autofill is a promise about a form somebody else owns. When a job board changes its markup the fill degrades quietly, and quiet is the worst way for that to fail, because the person only finds out after they submitted. Gamification, messaging and community exist because a hiring platform nobody comes back to is a directory. They are also the surfaces that need watching, and that work never ends. No iOS build exists. There is no Apple Developer account behind this work, so the web and the Play listing are the only two places it has genuinely shipped. A resume builder is the easy half. The employment claim sitting behind it is why this needed a platform. Open the live site Google Play Documentation What it does, and what that costs to build Multi-template ATS-friendly resume builder Verified-employment workflows + trust chips 50+ career tools (ATS checker, JD analyzer, etc.) LinkedIn extraction + job-application autofill extension Gamification, messaging, community B2B white-label licensing (136-feature priced catalog) Full Radix theme customizer for all users Built with React 19 TypeScript Vite 8 TanStack Router TanStack Query Radix UI Firebase CapacitorJS WXT Worth knowing hr recruitment hiring saas white-label ats extension Who built ClearHire? Ahsan Mahmood built it alone, and it is live at clearhire.aoneahsan.com with an Android build on Google Play. The multi-sided data model, the verification workflows, the browser extension and the store submission were all one person's work. That is the honest answer to how a platform this size exists without a team behind it. What does verified employment mean on ClearHire? It means an employment claim was confirmed through a workflow inside the platform rather than typed into a form and taken on trust. A confirmed claim shows as a trust chip on the profile; an unconfirmed one simply does not. The distinction is the point of the product, and it is deliberately narrow — the workflow confirms what it can actually establish and leaves the rest unmarked. Does ClearHire work on iPhone? No. The platform runs on the web and ships an Android app through Google Play, and there is no iOS build because there is no Apple Developer account behind this work. An iPhone can use the web application in Safari, which is the only iPhone route there is. Can a company license ClearHire? Yes, in three forms: a managed white-label deployment, a non-exclusive source licence, or an exclusive buyout. Each is priced against a catalogue of 136 features rather than against a vague scope, so the thing being bought is itemised before anyone talks about a number. Which of the three fits depends on whether you want it run for you, run by you, or owned outright. What does the ClearHire browser extension do? It extracts a LinkedIn profile into a structured record and autofills job applications on LinkedIn, Indeed and Glassdoor. That removes the retyping. It is a WXT extension at version 1.1.0, built to the same no-remote-code rule every extension here follows. ## Trizlink — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.trizlink.app Branded short links, link-in-bio pages, click analytics and a suite of utilities. A short link is a redirect somebody else owns. That is fine right up until the somebody stops paying for it. Link shorteners were commoditised years ago. Anybody can build one in an afternoon, which is why so many have appeared and then quietly closed, taking every printed QR code and every business card still in circulation down with them. So the first constraint here is the one this whole practice works under. Free-tier infrastructure, no paid backend in the critical path: Firebase for auth, data and hosting, with FilesHub for uploads. A redirect has to stay cheap at any volume, because a redirect that becomes expensive is a redirect that eventually stops resolving. Analytics is where that gets sharp. A click is one write and a dashboard is a read of a great many of them, so every list read carries an explicit limit and the database does the counting rather than the browser. Get that wrong and the free tier is exhausted by the product's most popular link. Shortening comes with custom slugs and branded domains. A link-in-bio builder handles the ordering by drag and drop, so the page can be rearranged by the person who owns it rather than by whoever has the code open. Click analytics carries breakdowns. D3 and visx draw them. Custom domains, workspaces and team invites exist because a link representing a company should not live inside one employee's personal account, and because the day that employee leaves is a bad day to discover it does. Then a suite of 50+ utilities: QR and barcode generators, colour tools, PDF tools, text tools, SEO tools and code tools. The same React 19 codebase ships as the web application and as a Capacitor Android build on Google Play. A browser extension that shortens from whatever tab you are already in is planned, not shipped. 149 Vitest unit tests and a Cypress suite hold the shared half of that together. The stack stays current: React 19 with the Compiler, TypeScript 6, Vite 8. Prerendered FAQPage and SoftwareApplication structured data mean an answer engine reads the product instead of an empty document. The Radix theme customiser is user-facing rather than a developer setting, so the person using Trizlink chooses how it looks instead of inheriting whatever I happened to prefer on the day. The costs are all in the long tail. Custom domains are the feature that turns a product into a support desk. Somebody's DNS is somebody else's system, so when a record is wrong the link is dead and the product looks broken, whichever of the two of us made the mistake. A 50+ tool suite is 50+ surfaces that all have to keep working. Each one is small. Together they are most of the maintenance. That customiser only holds if every component reads a token. One hardcoded colour is invisible to it, and it stays invisible until somebody switches theme and finds the single panel that did not follow. Honest analytics is less flattering than the other kind. Click counts include whatever did the clicking, and telling a person from a crawler is a judgement rather than a fact, so the number is a signal and not a census. There is no iOS build here. There is no Apple Developer account behind this work, so Android and the web are where it has genuinely shipped. A short link is worth exactly as much as the promise that it will still resolve next year. Everything above is an argument that this one will. Open the live site Google Play What it does, and what that costs to build URL shortening with custom slugs & branded links Link-in-bio builder with drag-and-drop ordering Click analytics with breakdowns (D3 + visx) Custom domains, workspaces & team invites 51-page in-app tools suite (8 categories) Chrome/Firefox extension for shortening from any tab App-wide Radix theme customizer Built with React 19 TypeScript Vite 8 Radix Themes Zustand react-router Firebase CapacitorJS WXT Worth knowing saas url-shortener link-in-bio analytics react firebase extension Who built Trizlink? Ahsan Mahmood built it alone, and it is live at trizlink.com with an Android build on Google Play. The redirect path, the analytics, the link-in-bio builder, the Android build and the store submission are one person's work, which is also why the architecture avoids anything that would need a second person to keep running. Can I use my own domain with Trizlink? Yes, custom domains are supported, along with workspaces and team invites so a company's links do not end up living inside one employee's personal account. Branded slugs sit on top of that, so the link reads as yours rather than as a random string. Pointing a domain is a DNS change on your side, and that is the step that most often goes wrong. Does Trizlink work on iPhone? No. Trizlink runs on the web and ships an Android application through Google Play; there is no iOS build, because there is no Apple Developer account behind this work. Short links themselves resolve on any device, an iPhone included — it is the installed app that does not exist. What is in the Trizlink tools suite? 50+ utilities sit inside the product: QR and barcode generators, colour tools, PDF tools, text tools, SEO tools and code tools. They are there because the people who shorten a link for a campaign are usually the same people who need a QR code for it twenty minutes later. Each runs in the application rather than sending you somewhere else. Is there a Trizlink browser extension? Not yet. An extension that shortens the page you are on without leaving the tab is planned; today Trizlink is the web application and the Android app on Google Play, and a short link resolves on any device and in any browser. ## SMS Mobile App — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.smsapp Android-first SMS automation that dispatches campaigns through the phone itself. The phone in your pocket is already an SMS gateway. It has a SIM, a carrier agreement and an allowance somebody is paying for whether it gets used or not. What it does not have is a way for one person to hand it a list and a message and walk away. SMS Mobile App is that missing half. An admin imports recipients from a CSV or from the built-in persons directory of 15,851 rows, then assigns a send batch to volunteer devices that have opted in. Each device sends through its own SIM. That directory is a virtualised table, which is what a list of that size needs if the screen is to stay usable at all. Templates map fields into the send flow, so a message can carry a name without anybody editing them one at a time. The plumbing is a plugin. A custom in-tree Capacitor plugin called NativeSms wraps the Android send call, and a foreground service keeps a batch moving while the application is closed or the screen is locked. There is no SIP relay in the path and no per-send fee beyond what the carrier already charges. It is deliberately send-only. No inbox. No READ_SMS permission, ever. That is a product decision and a store decision at the same time. The message-reading permissions are among the ones a review examines hardest, and an application that never asks for one never has to explain why it wanted it. The batch cap is enforced where it cannot be argued with. Ten devices per batch, written as a Firestore rules invariant rather than as a check in the admin screen, with atomic parent-batch counters underneath so that two devices finishing together cannot both write the same total. A rule the client enforces is a rule the client can skip. That matters more here than in most products, because the clients are handsets belonging to volunteers and running wherever those volunteers happen to be. The only enforcement worth having is the kind that lives on the other side of the network. It runs on Firebase's free tier, with Cloudflare Workers doing the work that would otherwise want paid functions. That is the constraint the whole product is shaped by. No paid service in the critical path, and no server that has to be kept alive. That is what makes it possible for one person to keep a set of things running at the same time rather than one after another. It is documented properly. A public documentation site structured on Diátaxis carries 41 pages and roughly 33,000 words, which is the difference between a tool somebody else can operate and a tool only I can. What it cost is the phones. This design trades a paid gateway for a set of real devices that have to be switched on, charged and willing. A gateway scales by paying more. This scales by finding more volunteers, which is a harder thing to arrange on a Tuesday afternoon and a much cheaper one to run for a year. It is live at smsapp.aoneahsan.com, with an Android application on Google Play and the documentation at smsapp-docs.aoneahsan.com. Send-only was the first decision. Everything else rests on it. An inbox would have doubled the permission surface and changed what the application is for, and neither of those is a trade worth making for a feature nobody asked for. Open the live site Google Play Documentation What it does, and what that costs to build Native SIM-based SMS send (no paid gateway) Custom in-tree NativeSms plugin + foreground service 15,851-record persons directory (virtualized table) Admin CSV import + volunteer-device batch fan-out Template field-mapping into send flow Send-only design (SEND_SMS only, no inbox) Public Docusaurus documentation site Built with React 19 TypeScript Vite 8 CapacitorJS Firebase Radix Themes TanStack Query TanStack Table Worth knowing mobile sms automation android capacitor firebase Who built SMS Mobile App? Ahsan Mahmood built the whole of it, including the native plugin. It is live at smsapp.aoneahsan.com, ships an Android application through Google Play, and has a public documentation site at smsapp-docs.aoneahsan.com carrying 41 pages and roughly 33,000 words. The plugin that talks to the SIM, the rules that cap a batch, the admin screens and the documentation were written by the same person, which is why the documentation describes what the code does rather than what somebody hoped it did. How does a message actually leave the phone? Through the device SIM, using a custom in-tree Capacitor plugin called NativeSms that wraps the Android SmsManager send call. An Android foreground service keeps the batch moving while the application is closed or the screen is locked, which is what makes it usable rather than a thing you have to sit and watch. There is no SIP relay anywhere in the path and no per-send fee beyond whatever the carrier already charges for a message on that plan. Can it read the replies? No, and it never asks for the permission that would let it. The product is deliberately send-only: no inbox, no READ_SMS. That is a product decision and a store decision at once, because the message-reading permissions are among the most restricted a review looks at, and an application that never requests one never has to justify it. It also narrows what the app can be misused for, which is a reasonable thing to want from software that dispatches messages in volume. What stops a batch being handed to more devices than intended? A cap of 10 devices per batch, written as a Firestore rules invariant rather than as a check in the interface. A rule the client enforces is a rule the client can skip, and the client here is an application running on volunteer handsets outside anybody's control. Atomic parent-batch counters sit underneath it so two devices finishing at the same moment cannot both write the same total. That is the sort of limit worth putting where it cannot be argued with. Does SMS Mobile App work on iPhone? No. It is Android-only by construction, because the send path is an Android API reached through a plugin written for it, and there is no equivalent route on another platform. There is no iOS release behind any of my work either, because there is no Apple Developer account here and nothing I build has shipped to the App Store. The admin side runs on the web, so the part that imports recipients and assigns batches is reachable from any machine. ## Perkforce — Employee Perks & Benefits SaaS — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.perkforce.app Multi-tenant employee-perks SaaS across web, iOS, Android, Slack & Teams. I built the mobile + workplace apps and led its TypeScript migration. This one is not mine, and that shapes the whole page. Perkforce is a client's product, and it lives at app.perkforce.com. My part was four years of building on it, and the record is unambiguous about the boundary: I contributed these surfaces and I do not own the thing they belong to. So what follows is about the engineering, and the business it serves is deliberately absent. The work was the surfaces. An employee perks platform that only exists as a website reaches people at their desks and nowhere else, which is the wrong place, because the moment somebody wants to use a benefit is rarely the moment they are sitting in front of a browser tab. So the product lives in five places, and over roughly four years I built four of them: the Android application, the Apple one, the Slack application and the Microsoft Teams application. Five storefronts is not five times one. It is one product that has to behave the same way in five contexts with different release cycles, different review queues and different conventions for what an interaction even looks like. A chat application is not a small screen. It is a conversational surface where a benefit is claimed inside a thread rather than on a page, and designing for that is a different job from making a layout narrower. Synchronisation is the real cost. It is most of the difficulty and none of the credit. A feature is not shipped when it works somewhere. It is shipped when the same behaviour exists everywhere, and the gap between those two states is where multi-surface products quietly rot. Somebody has to hold all five in their head at once, and for more than a year that was one person. Then there was the migration, which occupied the other half of those four years and touched every file in a codebase that was shipping the entire time it was happening. The codebase moved from JavaScript to TypeScript across the frontend and the backend together. On a serverless architecture that is worth more than it sounds, because an untyped function fails in production rather than in front of whoever wrote it, and the feedback arrives from a log instead of from a compiler. Live is the hard part. There is no version of that migration which stops delivery while it happens. It goes in alongside the features, in pieces, each of which has to leave the product working, and the discipline is in choosing an order that never leaves the codebase half-typed in a way that helps nobody. There is one more thing worth saying about running a product alone for over a year, because it is the part that does not appear in a feature list. Solo means every decision is yours and every consequence arrives at your desk. Nobody catches a bad release. Nobody notices the thing you forgot. The compensating discipline is process rather than talent: cutting releases the same way each time, keeping the five surfaces in a known state, and writing down what a future reader will need, because for a while the future reader was also me. What it cost is the part I cannot show you. The interesting specifics of a client engagement are the client's, so this page names no customer, quotes no internal decision and describes no infrastructure that is not mine to describe. That makes for a thinner page than the work deserves. It is the correct trade, and a portfolio that made the other one would be a liability to the next client rather than evidence for them. Open the live site What it does, and what that costs to build Android + iOS apps Slack + Microsoft Teams apps JavaScript → TypeScript migration Ran the project solo 1+ year Five live storefronts Built with React 19 TypeScript Vite / Rolldown Capacitor 8 Firebase Firestore Cloud Functions Stripe Slack API Microsoft Teams Zustand TanStack Query Worth knowing saas hr employee-benefits serverless stripe slack teams Is Perkforce your product? No. It is a client's product and the record says so plainly: I contributed these surfaces and I do not own it. Over roughly four years I built the Android application, the Apple one, the Slack application and the Microsoft Teams application, and led the migration of the codebase from JavaScript to TypeScript across both halves. For more than a year I ran the project on my own. None of that makes it mine, and this page describes the work rather than the business. What does building on five storefronts actually involve? Keeping them in agreement. A feature is not finished when it works in one place; it is finished when the same behaviour exists on the web, on both mobile platforms and inside two chat products, each with its own release process, review timing and interaction conventions. Slack and Teams are not small screens, they are conversational surfaces with different affordances again. Most of the difficulty is not the feature. It is the synchronisation. What did the TypeScript migration change? It moved a class of failure from runtime to build time across the frontend and the backend together. On a serverless codebase that matters more than usual, because a function fails in production where nobody is watching rather than in front of the person who wrote it. Migrating an existing product rather than starting typed also means doing it without a rewrite and without stopping delivery, which is most of what makes it difficult. Did you build the Apple version too? Yes, and not under my name. The Apple application was released on the client's own developer account, because the product is theirs. Nothing I own has shipped to the App Store and there is no Apple Developer account behind my own work. That distinction matters on a portfolio page, where contributing a build and owning a listing are easy to blur and mean completely different things. Why is there so little detail about the product itself? Because it belongs to somebody else. A client's roadmap, their customers, their internal decisions and their numbers are theirs to publish, not mine, and a portfolio that treats a client's business as material is a portfolio that clients stop trusting. What is described here is the engineering I did and the shape of the problem it solved. Everything past that boundary is deliberately absent. ## PregnancyPal — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.pregnancypal Week-by-week maternal wellness, nutrition, vitals and prenatal exercise. PregnancyPal keeps a record, week by week. That is the plain description of it, and it is first on this page on purpose. Software in this area slides very easily into sounding like a clinician. This does not, and the line is drawn in what the application actually does rather than in a disclaimer at the bottom of a screen. It records, and it shows back what it recorded. Day to day, that is vitals, symptoms and mood. Around those sit week-by-week pregnancy tracking, appointment management, nutrition and prenatal exercise sections, a community module and a blog. Period and fertility tracking is there as well, covering ovulation, the fertile window and basal body temperature. Appointments are the part people underestimate. A pregnancy generates a run of them, each with its own date and place and its own reason for being booked. A diary sitting in the same application as the entries is easier to keep straight than one spread across a phone calendar and a paper card. None of it interprets anything. The application does not decide whether a reading is expected. It does not identify a symptom, and it does not tell you what any of the numbers mean. That reading belongs to the person looking after you. Which is what the export is for. A health report comes out as a PDF, so a record can arrive at an appointment as a document rather than as something recalled in the room, and the charts behind it are drawn with D3. The architecture is strictly zero-cost. Logic runs client-side. Firebase's free tier holds the data and uploads go through FilesHub, with no Cloud Functions and no Cloud Storage anywhere in the design. That constraint has a second effect worth naming. A product with no server code of its own has fewer places for anybody's records to sit, and on an application holding entries about a pregnancy that is worth more than the money it saves. Later work hardened the interface rather than lengthening the feature list. A theme customiser with a full dark mode is available signed in and signed out, and a boot loader removes the flash of the wrong theme before the first paint. Both are small changes, and both matter most on a screen somebody is reading in a dark room. Build-time prerendering arrived in the same pass. 14 routes and 10 blog posts emit crawler-facing static HTML with per-page JSON-LD, so a search engine or an answer engine reads real content without executing any JavaScript at all. A page a crawler reads as empty is a page nobody finds. There is a companion browser extension built with WXT, which extends access on the web. The record I write these pages from says that and no more. Neither does this page. What it cost is the ability to tell you anything. An application that interprets is a different product carrying a different set of obligations, and building one would mean making claims this work is not in a position to make. So it stays on the side of the line where it records, and the clinic stays on the other side of it. That is a limit. It is the correct one here. It is at pregnancypal.aoneahsan.com, with an Android build on Google Play and documentation at pregnancypal-docs.aoneahsan.com. There is no iOS build because there is no Apple Developer account behind any of this work, and this page is not going to be the thing that quietly implies otherwise. Open the live site Google Play Documentation What it does, and what that costs to build Week-by-week pregnancy tracking Period & fertility (ovulation, fertile window, BBT) Nutrition, exercise & daily vitals logging Doctor-friendly PDF health-report export Community module + full blog Full theme customizer + dark mode for all users Zero Cloud Functions / Cloud Storage (free tier) Built with React 19 TypeScript Vite 8 TanStack Router Radix UI Firebase CapacitorJS WXT D3.js Worth knowing health pregnancy femtech mobile fertility women-health Who built PregnancyPal? Ahsan Mahmood built it working alone, and it is at pregnancypal.aoneahsan.com with an Android build on Google Play and documentation at pregnancypal-docs.aoneahsan.com. The data model, the Firebase rules, the charting, the prerendered pages and the Android packaging are all one person's work. It is built with React and TypeScript on TanStack Router, with Radix UI on the interface, D3 for the charts, Firebase for storage, FilesHub for uploads and Capacitor for the Android build. Is PregnancyPal a substitute for antenatal care? No, and it is not built to behave like one. The application records what you enter and shows it back to you over time. It does not decide whether a reading is expected, it does not identify a symptom, and it does not tell you what anything you have logged means. Those readings belong to the midwife, doctor or clinic looking after you, and the health-report export exists so that they can be given a complete record instead of a remembered one. Everything on this page describes a record-keeping tool. What can I log in PregnancyPal? Daily vitals, symptoms and mood, alongside week-by-week pregnancy tracking. There is period and fertility tracking covering ovulation, the fertile window and basal body temperature, appointment management for what is booked and when, and nutrition and prenatal exercise sections. A community module and a blog sit beside the tracking rather than inside it. What goes in is what you choose to put in, and the application has nothing to say about entries you leave blank. Where does what I log actually go? Into Firebase's free tier, with any file uploads going through FilesHub. The application logic runs client-side in the browser or in the Android build, and the architecture carries no Cloud Functions and no Cloud Storage, so there is no server code of mine sitting between you and your entries. That is a cost decision first, because zero paid infrastructure is what makes it possible for one person to keep a product like this running. It also means there are fewer places for these records to be, which on this particular product is worth more than the saving. Does PregnancyPal work on iPhone? No, there is no iOS build. It ships an installed application on Android through Google Play, and it runs as a web application that an iPhone can open in a browser like any other device. The reason is straightforward: there is no Apple Developer account behind this work, so nothing here has ever been submitted to the App Store. It is stated here rather than left for somebody to discover after they have started using something. ## Native Update — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.nativeupdate Over-the-air updates for Capacitor apps — bundle signing, staged rollout, and automatic rollback when a bundle fails to boot twice. How long does a typo live in a shipped mobile app? As long as the review queue takes, plus however long your users take to update, and neither of those numbers belongs to you. Meanwhile the broken screen keeps opening. That is the problem an over-the-air channel exists to solve, and it brings a worse one with it. A path that can replace an app's code remotely is a path somebody else would like to use. So integrity comes first. Every bundle is signed and verified with SHA-256 and RSA-SHA256, and one that does not verify does not run. The second requirement is the one people skip. A bad bundle has to be able to undo itself with nobody in the loop, because the person who could intervene is asleep in a different timezone. Fail to boot twice and the native side rolls back on its own. native-update is a Capacitor plugin on npm, and around it sits everything an update channel actually needs rather than the plugin on its own. A release-management CLI handles bundles. It creates them, signs them and verifies them before any of them leaves the machine. A Laravel 11 and Nova 5 backend holds apps, keys and releases behind bearer-token authentication and role-based access control, so who may publish to a channel is a question with an answer rather than a convention. A React 19 dashboard sits on top of that, itself wrapped with Capacitor for the Play Store. Store update checks and throttle-aware review prompts live in the same plugin, because an app that needs an over-the-air path also needs to know when a real store update is waiting for it. Documentation runs to 58 public pages, with two runnable example apps beside it, because a plugin that changes how an app boots cannot honestly be explained in a README. That is the difference between a package you can adopt and a package you can read about. 198 tests hold it: 62 in TypeScript and 136 in Pest, with a Detox suite running the end-to-end path against a real build rather than against a mock of one. One release deliberately removed Firestore so the whole system runs through a single HTTP backend. Deleting a dependency is the change that never demonstrates well and always pays. The plugin makes one demand. It must confirm a healthy boot. An app that forgets rolls itself back after two cold starts and looks, from the outside, exactly like an update that was broken — which is the most expensive kind of bug, because it points at the wrong thing. Signing keys are the other cost. A key you lose is a channel you can no longer publish to, and there is no support desk here to reissue one for you. This is one of the few things here with a backend that has to stay awake, which is the shape of the problem rather than a slip: an update channel needs somewhere for the update to come from. Everything else I build is arranged so that no server has to be. This one could not be, and that is the honest price of a release path that does not queue behind a store reviewer. There is no iOS build here. There is no Apple Developer account behind any of this, so Android is where the update path has actually been exercised. A piece of infrastructure nobody can inspect is a piece of infrastructure nobody should install. The npm page, the 58 pages of documentation and the two runnable example apps are all open to anybody, and that is the whole of the argument. Open the live site npm What it does, and what that costs to build Live/OTA bundle updates (no store re-submission) App-store update checks (Play Core + StoreKit) In-app review prompts (throttle-aware) Signed bundles + crash-rollback safety Release-management CLI (bundle create/sign/verify) Laravel 11 + Nova 5 SaaS backend (Sanctum + RBAC) Public 58-page Docusaurus docs site Built with TypeScript CapacitorJS Kotlin Swift Node.js Laravel 11 Laravel Nova React 19 Worth knowing npm package capacitor ota-updates laravel plugin Who built native-update? Ahsan Mahmood built it alone: the plugin, the release CLI, the Laravel backend, the dashboard and the documentation site. It is published on npm as native-update and the dashboard is at nativeupdate.aoneahsan.com. The native Android implementation and the TypeScript surface were written by the same person, which is why they agree with each other. What happens if an over-the-air bundle breaks the app? The native side rolls back on its own after the app fails to boot twice, so a broken bundle recovers without anybody being woken up. That is why the plugin requires the app to confirm a healthy start rather than assuming one, because silence is treated as failure. Bundle integrity is checked before that point too, with SHA-256 and RSA-SHA256 signing, so a bundle that has been altered never gets far enough to crash. Does native-update work on iOS apps? There is no iOS build and no App Store presence behind this work, because there is no Apple Developer account here — so the honest answer is that the over-the-air path is proven on Android. Treat anything else as unverified until somebody with an Apple account has actually run it. Where do the bundles come from? From a Laravel 11 and Nova 5 backend that holds apps, keys and releases, with bearer-token authentication and role-based access control in front of them. A release-management CLI creates, signs and verifies a bundle before it is uploaded, so the signing step is part of the release rather than an optional extra. One release deliberately removed Firestore from the system so that everything runs through that single HTTP backend. How do I add native-update to a Capacitor app? Install it from npm as native-update, then wire the healthy-boot confirmation into your app's start-up path before anything else. The documentation site runs to 58 pages and two runnable example apps ship with the project, which between them cover the wiring the README cannot. Skipping the boot confirmation is the one mistake that turns a working install into an app that rolls itself back. ## TaxEase — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.taxease Tax compliance workflows for filers, with an admin side for staff. TaxEase does not prepare your return. That belongs at the top. Tax software usually means a calculator with a wizard in front of it, and this is a different kind of product entirely. What it manages is the work around the filing. Most of the effort inside a practice is not arithmetic. It is a request that arrived without the document it needed. A file waiting on a signature. A client writing for the third time to ask where things stand. None of that is visible to the person waiting, which is exactly why they keep asking. The constraint is the same one every product on this site is built to. No paid service in the critical path, no server that has to be kept alive, nothing whose price can move underneath a practice that budgets a year in advance. Documents make that harder than it sounds. A file somebody uploaded about their income is not a file that should be casually copied across three vendors, so the fewer places it lives the fewer places it can leak. React 19 and TypeScript on the web, Firebase behind it, Capacitor packaging the same codebase for Android. The request is the unit. It is raised, tracked and worked by somebody on the staff side, and both parties look at one record instead of at their own copies of it. Documents attach to the request rather than to an inbox thread, which is the difference between evidence somebody can find in a year and correspondence somebody has to remember to go searching for. Status changes in real time. The person waiting watches it happen. That removes the email that was only ever asking a database a question, and it removes the reply that had to be written by hand. Communication happens inside the file, so the conversation and the paperwork stay in the same place and stay together when somebody else picks the file up. The admin side is for staff. It is a working surface rather than a reporting one, because the person moving a stack of files this week needs a queue they can act on. Android comes from the same codebase, since a client chasing a document is rarely sitting at a desk. The costs are worth stating plainly. A status field is only ever as honest as the person who moves it. Software can make the current state visible and it cannot make anybody update it, so the real job is to make updating cheaper than answering the email would have been. Documents are the sensitive part, and they are also the part people will email anyway. A system is only as private as the least convenient path through it. Convenience is a security property here rather than a nicety, and that is not how it is usually budgeted. A shared inbox is easier to start with. By the third month it is worse, and by then the history of a file lives in somebody's mailbox rather than in the file. There is no iOS build. There is no Apple Developer account behind this work, so the web and Android are the two surfaces it reaches. Nothing here is tax advice and nothing here computes what you owe. It moves the requests, the documents and the answer to the question everybody keeps asking, which is the part that was quietly costing both sides their week. Open the live site Documentation What it does, and what that costs to build Service request management Document handling Real-time status updates Secure communications Mobile app support Built with React 19 TypeScript Firebase CapacitorJS TailwindCSS Worth knowing tax services document-management saas Who built TaxEase? Ahsan Mahmood built it alone, and it is live at taxease.aoneahsan.com. The request model, the document handling, the staff-side screens and the Android packaging are one person's work. The same person who decided how a document is stored is the person who answers for it. What does TaxEase actually do? It manages service requests between a filer and the staff working their file — raising a request, attaching documents to it, tracking its status in real time and keeping the conversation inside the record instead of in an email thread. The unit of work is the request rather than the message. That is the difference between a practice that can answer where a file has got to and one that has to go and look. Does TaxEase work on iPhone? No, there is no iOS build, because there is no Apple Developer account behind this work. TaxEase runs on the web and is packaged for Android with Capacitor from the same codebase. An iPhone can use the web application, and that is the extent of it. Does TaxEase give tax advice or prepare a return? No. It does not calculate a liability, does not file anything on your behalf and is not tax advice in any form. It is workflow software for the requests, documents and status that surround a filing, and the professional judgement stays entirely with the people doing the work. Who is the admin side for? For the staff working the files, and it is built as a working surface rather than a reporting one. Somebody moving a stack of files through a busy week needs a queue they can act on, not a chart summarising it. It is the half of the product that decides whether the client-facing half stays accurate. ## FilesHub — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.fileshub Laravel platform pairing multi-tenant file storage with 72 developer APIs. Uploads. Transactional email. A PDF, a QR code, a resized image. Every product needs some of that. Each one arrives as a separate vendor with a separate bill, a separate key and a separate dashboard that nobody ever logs into. None of them is expensive on its own. Together they are why a project starts costing money before it has a single user, and why the cost climbs on a curve that has nothing to do with whether anybody is using the thing. The rule underneath everything on this site is that no paid service sits in the critical path. Firebase Storage is on the wrong side of that line, so it was ruled out rather than budgeted for. That leaves two honest options: do without file storage and outbound email, or build the platform once and let every project use it. FilesHub is the second option. It is a Laravel 12 platform with a Laravel Nova admin panel, exposing 72 features over 181 versioned API routes. Object storage. Transactional email. Image processing, PDF, QR and barcode generation, URL shortening, and a deep catalogue of developer utilities: hashing, encoding, JWT, conversion, minification, language and spam detection. One API key reaches all of it, and per-key permissions decide which parts. Keys carry origin whitelisting and rate limiting, which is what separates a key that may ship inside a browser bundle from one that must never leave a server. That distinction is the thing integrators get wrong. An origin-restricted key can sit in client code; an unrestricted one cannot. A key listing will not reliably tell you which of the two you are holding, so the only trustworthy answer comes from probing the key itself. The web surface is Blade and Tailwind, with a public discovery layer of a sitemap, a blog and an RSS feed, and an AEO layer carrying llms.txt, a pricing document, JSON-LD and an allowlist for AI crawlers. It already runs in production as the upload backend behind the rest of this portfolio. That is the part worth checking rather than believing. The argument for one self-hosted platform over a stack of single-purpose vendors is not a projection here, because the products depending on it are the ones listed elsewhere on this site. The first cost is structural. A platform everything depends on is a single point of failure with a friendly name. One bad hour is every product's bad hour, and there is one person to answer for it. Per-key permissions are only ever as careful as the person minting the key. A key holding more permission than its job needs is one leaked bundle away from being the whole platform, and the model can make that avoidable without making it impossible. 72 features is 72 contracts that have to keep holding. A response shape changed carelessly breaks a consumer that will not find out until its next deploy, which may be months later and will not look like my fault. There is no iOS build. There is no Apple Developer account behind this work either, so nothing here has ever gone to the App Store. For an HTTP API, that limit costs less than it would elsewhere. Infrastructure is judged on the days nobody thinks about it. The products sitting above this one stopped having a storage question at all. Open the live site What it does, and what that costs to build Multi-tenant object storage (upload/download/delete) 72 features over 181 versioned REST endpoints Multi-provider transactional email Image processing, PDF, QR & barcode generation Per-key permissions, origin whitelisting, rate limiting Canonical upload backend for the whole ecosystem AEO-first discovery layer (llms.txt, pricing.md) Built with Laravel 12 Laravel Nova PHP Blade Vite 8 TailwindCSS MySQL Worth knowing api file-storage laravel self-hosted platform backend Who built FilesHub? Ahsan Mahmood built it alone, and it runs at fileshub.zaions.com. The Laravel platform, the Nova admin panel, the API surface and the key model are one person's work, and it is the backend the other products on this site upload through. Building infrastructure for yourself has one honest advantage: the first person to hit every rough edge is you. What can a single FilesHub API key do? As much as its permissions allow and no more. Each key carries its own granular permissions, an origin whitelist and a rate limit. That is what lets one key reach object storage, email, image processing and the utility endpoints without becoming a key that can do anything. Minting a key with more permission than its job needs is the mistake the model exists to make avoidable, and it is still possible. Is there an iPhone app for FilesHub? No, and it would not add much — FilesHub is an HTTP API, so anything that can make a request can already use it, an iPhone included. There is no iOS build here in any case, because there is no Apple Developer account behind this work. The web surface is a browser application rather than an installed one. Why build a file and email platform instead of paying for one? Because a paid service in the critical path is a dependency whose price somebody else controls, and this whole practice is arranged so that none exists. Firebase Storage sits on the paid side of that line, so it was ruled out rather than budgeted for. One platform doing storage, email, image work and document generation replaces several single-purpose vendors, and the saving is not theoretical here, because it is the arrangement the other products on this site already run on. What is FilesHub used for today? It is the upload backend behind the rest of this portfolio, in production rather than in a demo. That is also the strongest thing that can be said about it: the products listed on this site are its live proof, and each of them can be opened and checked. Anything I could add about performance would be a number nobody else can regenerate. ## LifeWell — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.lifewell Health and wellness tracking across web, Android and a browser extension. The year somebody in a house is pregnant is often the year somebody else in it is managing blood pressure. Those two facts sit in different applications built for different people, and neither one knows the other exists. One app assumes a woman in her twenties. Another assumes a new parent. A third is designed around somebody with a long-term condition and a clinician waiting for numbers. A household is all three at once. So the records scatter across accounts and phones, and the view that would actually help — everyone, over years, in one place — is the one nobody ends up with. LifeWell runs on a single Firestore backend on the free tier, with no server that has to be kept alive for it to work. That constraint matters more here than anywhere else in this portfolio. A health record you cannot open in three years is not a record, and a tracker that stops paying for itself stops existing, taking the history down with it. Keys never sit in the browser bundle. The calls that need one go through a Cloudflare Worker token proxy, so the secret stays on the server side of that line. 45+ health tools cover vitals, water, medications, exercise, sleep and nutrition. Periods and fertility live in the same product as baby milestones, because in a real household those are frequently the same year. Around them sit people, memories, notes and a community layer, so a reading has somebody attached to it instead of being a row in a table. One product, three surfaces. It runs as a PWA and ships as a Capacitor Android build on Google Play, with a WXT browser extension for the check-in you would otherwise put off until you found your phone. 200+ routes and 10 languages, Arabic with right-to-left layout among them. Per-route static HTML with JSON-LD makes the public pages readable to a crawler, and there is a public documentation site alongside the product. Two of the rules this whole practice now works to were paid for on this project. The first is the central logger. Every direct console call came out across 95 files and the linter refuses new ones, which is a refactor that produced no feature any user will ever see and closed a class of leak that only shows up in production. The second came from the Play Console. A camera permission the app did not need was in the merged manifest, and the submission was rejected for it. Every Android build since is audited against its merged manifest rather than against the one written in the repository, because those two are not the same document. Shared family records raise a question a single-user tracker never has to answer: who is allowed to see whose. That is a security-rules problem rather than a screen, and it is the part of this product that is easiest to get quietly wrong. Ten languages is ten times the surface every new string has to cross. And a tracker only ever knows what somebody typed into it. LifeWell keeps that record and shows it back over time. It does not interpret the record, gives no medical advice, and nothing in it is a diagnosis. There is no iOS half. There is no Apple Developer account behind this work, so Android and the web are the two surfaces it has genuinely reached. The history is the product. Everything above exists so that it is still openable in five years, on a phone I will never see, by somebody who has never heard of me. Open the live site Google Play Documentation What it does, and what that costs to build 45+ health tools (vitals, water, meds, exercise, sleep) Period & fertility + baby milestone tracking Full Radix theme customizer (7 options) 10 languages including RTL Arabic Browser extension (food-safety + reminders) Per-route static HTML + JSON-LD for AI search Public Docusaurus docs site Built with React 19 TypeScript Vite 8 Radix UI Themes TanStack Router Firebase Cloud Functions CapacitorJS WXT Cloudflare Workers Worth knowing health wellness family-health react firebase pwa capacitor Who built LifeWell? Ahsan Mahmood built it alone, and it is live at lifewell.aoneahsan.com with an Android build on Google Play. The data model, the Firestore rules holding one household's records apart from another's, the Android packaging and the store submission are all the same person's work. Does LifeWell give medical advice? No. It keeps a record of what you enter and shows it back to you over time; it does not interpret readings, does not diagnose anything and is not a substitute for a clinician. That boundary is deliberate and it is why the tools are described as tracking rather than as assessment. Does LifeWell work on iPhone? No, there is no iOS build — there is no Apple Developer account behind this work, so nothing here has ever gone to the App Store. LifeWell runs as a web application that an iPhone can open in Safari, and it ships a real installed app only on Android. What can a family track in one place? Vitals, water, medications, exercise, sleep and nutrition, plus periods and fertility, baby milestones, and the people, memories and notes that give a reading somebody it belongs to. The point of putting them in one product is that a household is rarely one demographic, and the same year often contains a pregnancy, an infant and somebody managing a long-term condition. Which languages does LifeWell support? Ten, including Arabic with full right-to-left layout. Right-to-left is the one that decides whether a translation layer was built properly or retrofitted, because it changes layout rather than only text. Adding an eleventh language is a catalogue file rather than a rebuild. ## HabitForge — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.habitforge Habit tracking with visual strength scoring and offline-first sync. You did the thing for weeks. Then a Tuesday went wrong. One missed day, and the counter holding all of those weeks reads zero. Nothing about the work you actually did has changed. The record of it has. That is a modelling decision, and most habit trackers make the brutal version of it because a binary streak is the easiest thing to draw and the easiest number to put in a notification. HabitForge runs client-side on the Firebase free tier, with FilesHub for uploads and no paid backend anywhere in it. It also has to work with no signal at all, because the moment somebody records a habit is very rarely a moment they have a good connection, and a tracker that refuses at the kerb is a tracker people stop opening. Those two constraints settle the data model before any screen exists. Every habit carries a visible strength score, modelled as threads forming a rope and drawn with D3. A missed day thins the rope. It does not cut it. Streaks are tracked at several cadences, so a habit meant for a few days a week is measured that way instead of registering as a run of missed days against a daily target it never had. Offline, data already loaded stays readable and new completions queue locally, syncing on reconnect through Firestore's persistent local cache. A WXT browser extension adds check-ins from whatever tab you are in, along with a Focus Mode that blocks the sites you decided in advance were not for this part of the day. The Capacitor build brings the same product to Android, with Play in-app updates, app shortcuts and badges. Cross-device sync means the phone and the browser hold one record between them rather than two versions of it. The theme customiser covers all seven axes and belongs to the person using the product rather than to me, and the search and answer-engine depth is generated at build time rather than managed through a CMS. So much for what it does. The rest of this page is what it cost. A strength score is a model, and a model is an opinion with arithmetic attached to it. Somebody will disagree with how quickly the rope thins. They will be right about their own habit and wrong about somebody else's, and no amount of tuning makes that disagreement go away. A drawing is a promise about what the data means. Threads invite a reader to look for fibres that are not being counted, so the picture has to stay simple enough to stay honest. Offline changes what honesty means on a screen. Data already loaded is readable; data that never arrived is not, and the interface has to say which of the two it is showing rather than presenting an empty list as if it were a fact about your week. Focus Mode blocks sites, which means it can block a site you turn out to need. So the control has to be easy to switch off, and easy to switch off is precisely what a blocker is not supposed to be. The app records what you did. It does not know whether it helped. No iOS build exists. There is no Apple Developer account behind this work, so the web and Android are where this actually exists. The rope is the whole argument. A record that survives a bad week is the only kind anybody keeps for long enough to be worth looking at. Open the live site Documentation What it does, and what that costs to build Visual habit-strength scoring (D3 thread/rope viz) Multi-cadence tracking (daily/weekly/custom) Resilient progress (missed day weakens, not breaks) Offline-first via Firestore IndexedDB cache Focus Mode site blocker in browser extension Google Play in-app updates, app shortcuts, badges Full 7/7 Radix theme customizer + cross-device sync Built with React 19 TypeScript Vite 8 TanStack Router TanStack Query Radix Themes Firebase CapacitorJS WXT D3.js Worth knowing productivity habits self-improvement mobile offline-first extension Who built HabitForge? Ahsan Mahmood built it alone, and it is live at habitforge.aoneahsan.com. The strength model, the D3 visualisation, the offline sync path, the browser extension and the Android packaging are all the same person's work. That is also why the extension and the app agree about what a completion is. What happens if I miss a day? Your habit weakens rather than resetting to zero — the strength score thins, and the record of the weeks behind it stays. Missing several days in a row weakens it further, so the model still registers that something changed. The design choice is deliberate: a counter that erases a month of work for one bad Tuesday is measuring the wrong thing. Does HabitForge work on iPhone? No. It runs on the web and ships an Android build, and there is no iOS version because there is no Apple Developer account behind this work. The web application works in Safari on an iPhone, which is as close as it gets. Does HabitForge work without a connection? Yes for the parts that matter. Data already loaded stays readable, and new completions queue locally and sync when the connection returns, through Firestore's persistent local cache. What it cannot do offline is show you something it never downloaded, and the interface says that rather than showing an empty list as though it were the truth. What does the HabitForge browser extension add? Check-ins from whatever tab you are already in, plus a Focus Mode that blocks distracting sites. It exists because the moment you remember a habit is usually a moment you are in a browser rather than holding your phone. It is a WXT extension built to the same no-remote-code rule as every other extension here. ## LabFlow — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.labflow Multi-tenant laboratory information system across five production surfaces. A tube arrives at the front desk with a name written on it. From that moment every step is a chance for the name and the tube to come apart. Accessioning. The run. The read. The signature. The release to whoever ordered it. A laboratory is a chain of custody before it is anything else. Software that treats a result as a row in a table has already dropped the part that matters, which is not the value in the field but the question of who is allowed to say that value is final. LabFlow runs on the Firebase free tier wherever Firestore security rules can enforce the policy, and reaches for Cloud Functions only where a rule genuinely cannot. That line is drawn on purpose. A rule runs on every read and costs nothing; a function is a bill and a cold start. Multi-tenancy makes the question sharper still. A list query has to prove its own tenant from its own filters, because a rule inspecting a document cannot rescue a query that asked for everything, and the difference between those two is invisible to a green build. Patient and order management, sample accessioning and tracking, results entry and validation, billing, inventory, quality control, equipment integration, EMR bridging. Results move Draft, then Reviewed, then Approved, then Released. A role gates every transition. Every patient record, order and result is tenant-scoped and audit-logged, so the question of who changed what has an answer that does not rely on anybody remembering. Five production surfaces share one tenant model: a React web application, a Capacitor mobile build, a WXT lab-assistant extension, a Chrome extension for EMR integration, and Firebase Functions. Underneath them sits one domain-service layer of 151 services, 28 Zustand stores and 60+ page modules. It exists so that five surfaces cannot drift into five different definitions of what approved means. The mobile build keeps core workflows usable offline through Dexie and IndexedDB, because a collection round does not stop where the signal does. Then the costs, which in this domain are mostly about restraint. A status machine that refuses is a status machine people will try to route around. So it has to be strict enough to be worth having and loose enough that the laboratory does not quietly start keeping a parallel spreadsheet, and that balance is a product judgement rather than a technical one. Tenant isolation fails silently. A correct rule and an over-broad query look identical from the outside, and the only defence is that every list read carries its own tenant filter and an explicit limit. That is checked by reading the queries, not by watching the build go green. No patient identifier reaches a log or an error report. That is right, and it means a production incident tells me less than it otherwise would, so reproducing one takes longer. Offline work means conflicts, and conflicts mean somebody has to decide which version of a result is the real one. No iOS. There is no Apple Developer account behind this work, so the web and Android are the two surfaces it actually reaches. The workflow is the product. Everything else listed above is scaffolding around one rule: a result does not leave the laboratory until a person with the authority to release it has said that it can. Open the live site What it does, and what that costs to build Full specimen lifecycle (accession to release) Role-gated Draft->Reviewed->Approved->Released workflow Multi-tenant isolation + audit trails Chrome EMR-integration extension Billing, inventory, quality control Offline-first mobile via Dexie/IndexedDB HIPAA-conscious data handling (no PHI in logs) Built with React 19 TypeScript Vite 8 TailwindCSS TanStack Query Firebase Cloud Functions CapacitorJS WXT Worth knowing healthcare lims multi-tenant medtech emr-integration react Who built LabFlow? Ahsan Mahmood built it alone, and it is live at labflow.aoneahsan.com. One person designed the tenant model, wrote the security rules that hold one laboratory's records away from another's, built all five production surfaces and wrote the domain-service layer they share. What does the LabFlow result workflow enforce? A result moves Draft, then Reviewed, then Approved, then Released, and each transition is gated by role rather than left to convention. Nothing reaches the ordering clinician until somebody with the authority to release it has done so, and every step is written to an audit trail. The order cannot be skipped, which is the entire point of encoding it in software instead of in a habit. Does LabFlow work on iPhone? No. LabFlow ships a web application and a Capacitor mobile build for Android, and there is no iOS build because there is no Apple Developer account behind this work. A laboratory running iPhones would use the web application, which is responsive, rather than an installed app. How is one laboratory's data kept separate from another's? Every patient, order and result is tenant-scoped, and the security rules enforce that on every read rather than the interface hiding what it should not show. The part that matters is that a list query has to prove its own tenant from its own filters — a rule that inspects a document cannot rescue a query that asked for everything, and that specific mistake is invisible to a passing build. Does LabFlow work without a network connection? The mobile build keeps core workflows usable offline through Dexie and IndexedDB, so collection rounds do not stop where the signal does. Anything that has not been loaded yet is not available while offline, and the interface says so rather than showing an empty screen. ## Ahsan Mahmood Portfolio — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.app Production personal-brand platform: projects, services, blog, testimonials, and contact/payment surfaces across web and Capacitor mobile, engineered to be… This is the site you are on, and that makes this page an unusual one to write. Every other entry in this catalogue asks you to take something on trust, or to follow a link and check. This one does not. If a sentence here is wrong, you can find out without leaving the page, which is a good discipline for a writer and a bad place to be vague. So what follows is only what this repository proves. It is a portfolio product rather than a portfolio page. That distinction is the whole design: projects, services, a blog, testimonials, feature requests, contact and payment surfaces and the legal pages are all content managed through an admin panel built into the application, not markup edited in a file and redeployed. The reason is unglamorous and it is the right reason. Content that requires a build to change is content that stops changing. It gets stale first and then quietly wrong. Eventually nobody edits it at all, because editing it is a deployment. Putting it behind an admin panel is what keeps it accurate a year later. The design came first. It was approved in full before any of this existed. Every page was built first as a static click dummy. Only then was it rebuilt as an application. The application is held to it by a test that compares approved copy unit by unit and fails the build when a sentence drifts. That is a strange amount of machinery for a personal site and it exists because approved words drift silently, which no ordinary gate can see. The other half is what a crawler gets. A single-page application renders nothing until its JavaScript runs, so anything that fetches the page without executing it sees an empty shell. Most assistants and several search crawlers do exactly that. So the build emits per-route static HTML carrying the real content with JSON-LD inline, and machine-readable files sit beside it for readers that want the structured version rather than the page. It runs on Firebase's free tier, and that is a constraint rather than a footnote. There is no server of mine to keep alive, no function billing by invocation, and no vendor whose pricing can move underneath this. Every list read carries an explicit limit and the database does the filtering and counting, which is the habit that a free-tier quota teaches once and permanently. A theme customiser is available to everyone, signed in or not. That one is a preference rather than an achievement, and it is here because a site telling people what I build should let them change how it looks while they read it. What it cost is the machinery. A personal site does not need a parity gate, an admin panel, a prerender step or an approval artefact. Most of the work here is not the pages; it is the apparatus that keeps the pages honest as they change. That is a disproportionate amount of engineering for one person's portfolio, and it is the same apparatus that makes the claims on it worth reading. There is one more thing the page should say about itself, since it is the only entry that can. The copy you are reading was drafted, reviewed and held before it was published. It was checked against the record it describes, and the claims that could not be sourced were removed rather than softened. That process is why this page is shorter than it could be and why the sentences in it are duller than a marketing page would allow. Which is the argument, really. Open the live site Google Play Documentation What it does, and what that costs to build Firestore-backed projects catalog + admin CMS Service detail pages with Service JSON-LD Public blog with BlogPosting schema Testimonials with AggregateRating schema 87 per-route static HTML pages for AI crawlers Agent-readable pricing.md / services.md / llms.txt Full Radix theme customizer for all visitors Built with React 19 TypeScript Vite 8 Radix UI Themes TanStack Router TanStack Query Firebase CapacitorJS Worth knowing portfolio personal showcase developer aeo react Who built this site? Ahsan Mahmood built it alone, and you are reading the result. The same person designed the pages, wrote the admin panel that manages their content, built the static-HTML generator that feeds crawlers, packaged the Android build and wrote the copy on this page. It is unusual to have a portfolio entry a reader can verify in a single click, and that is the point of naming it: every claim below is checkable from where you are already standing. Is any of this page written by hand or is it all from a database? Both, and the split is deliberate. The layout, the wording of every heading and the shape of every section were fixed in a static click dummy before a line of application code existed, and the application is checked against that dummy by a test that compares the approved text unit by unit. What comes from the database is the content: this description, the questions below it, the tags. The design is approved copy and the content is data. Why does it emit static HTML when it is a React application? Because the crawlers that matter most do not run JavaScript. A single-page application renders nothing until its bundle executes, so an assistant fetching the page sees an empty shell and reports that there is nothing here. The build emits a per-route static HTML page carrying the real content and inline JSON-LD, which is what a non-executing crawler reads. The alternative is a site that looks fine to a person and is invisible to everything else. What runs the admin side? An in-application admin panel, built as part of the product rather than bolted on. Projects, services, blog posts, testimonials and the site's own settings are managed there rather than by editing code and redeploying, which is the difference between a portfolio and a portfolio product. Editing content should not require a build, and anything that does require a build eventually stops being edited at all. Does this site have an iPhone app? No. It ships as a web application and as an Android build through Capacitor, and there is no iOS release, because there is no Apple Developer account here and nothing I build has shipped to the App Store. An iPhone opens the site in a browser and gets the whole thing, since everything here is a web page first. The Android build exists because packaging one was cheap once the web application was already correct. ## Zaions Portfolio — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.zaions-app Company-brand portfolio + content platform: a Firestore-backed projects directory, blog, and 18-screen admin CMS on a strictly zero-cost React 19 +… Two portfolios by one person is a decision that has to earn itself. The obvious version is one site with a different logo on it. That is cheaper, it is faster, and it produces a document that reads as neither a person nor a company, because the two answer different questions and a page trying to answer both answers each of them halfway. So this one picks a side. It is built as a company surface and it admits it. It carries a homepage, a searchable directory of work, a content hub, contact and payment routes, and the full legal pages a business has to have somewhere findable. Behind all of that sits a real content management system rather than a settings screen. That administration layer is where most of the work went. Posts, pages, portfolio items, taxonomies, media, payment methods, comments and users each get their own screens. Written out as a list it sounds like scaffolding. In practice it is the difference between a site somebody maintains and a site that quietly stops being true, because content edited in source is content shipped by deployment, and content shipped by deployment eventually is not edited at all. The architecture underneath is strictly zero cost, and that word is doing real work. Authentication, the database and hosting sit on free tiers. Uploads go to FilesHub rather than to cloud storage. Local state uses the device's own preferences store. There are no serverless functions anywhere in the design, which is the part that usually surprises people, because functions are the default answer to anything needing a moment of trusted compute. Removing them costs something real. Anything genuinely requiring a server that a client cannot be trusted with is off the table, and that is a category with real things in it. What is bought in exchange is a site with nothing to keep alive, no invocation billing, and no vendor whose pricing can move underneath it later. For a brand surface expected to sit there being correct for years, that trade is the right way round. Discovery was built rather than assumed. The build emits prerendered route shells with per-page metadata, social cards and structured data, alongside a machine-readable file for readers that want the data rather than the page. A single-page application shows a crawler an empty shell until its JavaScript runs, and a company site nobody can find is an expensive way of having no company site. It installs as a progressive web app. An offline shell comes with it. That is a small thing, and it is the honest form of a mobile presence here. No store, no review queue, no account with anybody, and an icon on a home screen for whoever wants one. One more property is worth naming, because it is the reason the directory is worth building at all. A searchable directory is a different object from a list of links. It has to hold a shape that new entries fit into without renegotiating, which means the taxonomy is the design decision and the search box is the easy part. Get the categories wrong and every item added afterwards either bends to fit them or quietly sits in the wrong place. What it cost is duplication. Every fix that matters to both sites has to be made twice, and the temptation to merge them will come back every time that happens. The answer is the same each time: they are two documents with two jobs, and the cost of keeping them apart is lower than the cost of a page that does not know who it is talking to. Open the live site What it does, and what that costs to build Searchable Firestore-backed projects directory 18-screen admin CMS (posts, pages, taxonomies, media) Full Radix theme customizer 44 prerendered SEO route shells 13+ JSON-LD schema types + llms.txt PWA install + offline shell Strictly zero-cost (no Functions, no Storage) Built with React 19 TypeScript Vite 8 Radix Themes react-router TanStack Query Firebase CapacitorJS Worth knowing portfolio company showcase cms react firebase Who built the Zaions site? Ahsan Mahmood built it, and it is at zaions.com. It is the company-brand counterpart to the personal site, which means one person built both and the second one had to justify existing separately rather than being a copy with a different logo. The admin surface, the content model behind it and the prerendering step are all the same person's work. Nothing here was assembled from a template or handed to anybody else to finish. Why does a company brand need its own site at all? Because the audiences are different and the content is different. A personal portfolio answers who somebody is and what they have shipped; a company surface carries a directory, a content hub, contact and payment routes and the full legal pages that a business needs to have somewhere. Merging them produces a page that reads as neither. Keeping them apart costs a second deployment and buys two documents that each know what they are for. What does the admin side actually manage? Posts, pages, portfolio items, taxonomies, media, payment methods, comments and users, across a set of dedicated administration screens. That is a content management system rather than a settings page, and building it was most of the work. The alternative is content edited in source and shipped by deployment, which works until the day somebody wants to correct a sentence and finds that correcting a sentence requires a build. Is it really running with no server code? Yes, and the constraint is deliberate. Authentication, the database and hosting all sit on free tiers, uploads go to FilesHub rather than to cloud storage, and local state uses the device's own preferences store. There are no serverless functions in the design. That rules out anything genuinely needing a trusted server, which is a real limit, and in exchange there is nothing to keep alive and no bill that grows with attention. Does the Zaions site work on iPhone? It opens in any browser, an iPhone's included, because it is a web application first and everything on it is a page. What does not exist is an iOS release: there is no Apple Developer account here and nothing I build has shipped to the App Store. It installs as a progressive web app instead, which puts an icon on a home screen and keeps a shell working offline without asking anybody's store for permission. ## ContentSynergy AI — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.contentsynergy Free four-surface AI content workspace (web + mobile + extension + desktop) that generates text, images, video, audio, 3D & surveys and schedules them… Four applications come out of one codebase here. A web workspace, an Android build, a browser extension and a desktop client. Somebody publishing across a handful of networks usually runs a set of tools that do not know about each other, with the copy in one, the image in another and the schedule in a third. That is the problem one shared codebase is answering. The web app is the canvas. It is where posts, images, video and reels, audio and music, 3D models and surveys get made, scheduled across 8 social networks, and measured afterwards. There is a template marketplace and an analytics side, plus the compliance tooling that any product holding other people's audiences ends up owing them. The other three surfaces are not ports of it. The extension is a writing, SEO and rephrasing assistant that works inside whatever page you already have open. The desktop client is Electron, and it is where the GPU-accelerated local engines are packaged. The Android build comes out of Capacitor. All four share the same core services and the same state. A 7-dimension theme customiser is one of the things they share, with settings syncing between devices through Firestore, so the workspace a person set up on a laptop is the workspace they meet inside a browser extension somewhere else. Collaboration is first-class rather than bolted on afterwards. Yjs handles it as a conflict-free replicated data type, which is the approach where two people editing the same thing at the same moment converge on one result instead of one of them silently losing an afternoon. Now the part an AI product usually leaves out. The models run client-side, through @xenova/transformers, wherever that is possible. Wherever it is not, the record I write these pages from does not say what happens, so this page does not say either. The hedge belongs to the source. An AI page is the easiest place in a portfolio to write a sentence nobody can check. So there is no accuracy figure here and no claim about model quality, and nothing on this page compares it to anything else. The constraint is the usual one, and it explains most of the design. There are no Firebase Cloud Functions anywhere in it. File storage goes through FilesHub, and the whole thing sits on free tiers. Client-side inference is that same constraint seen from the other end. A model running on the user's own machine costs nothing per call, which is the only way an image generator and an audio generator both fit inside a budget of zero. The bill moves onto the device rather than disappearing. Which is exactly what it cost. A model that runs in a browser tab is bounded by that tab, and a generator running on a laptop is bounded by that laptop. A hosted model can be larger than any machine a user owns, and this design gives that up deliberately. The second cost is four. Four surfaces means four release paths, four sets of platform rules and four places for one defect to look slightly different. Sharing the core services is what makes that survivable, and survivable is a very different word from cheap when the same person owns every one of the four. It is in progress rather than finished. It is at contentsynergy.aoneahsan.com. Eight networks means eight OAuth integrations that can each change without warning, and not one of them belongs to me. Open the live site What it does, and what that costs to build AI generation: text, image, video, audio, 3D, surveys 8-platform OAuth scheduling + autopilot Real-time collaborative editing (Yjs CRDT) Client-side AI via @xenova/transformers Four surfaces: web + mobile + extension + desktop 7-dimension Radix theme customizer Zero-cost (Firebase free tier, no Cloud Functions) Built with React 19 TypeScript Vite 8 Radix UI TanStack Query Yjs Three.js Firebase CapacitorJS Electron Worth knowing ai social-media content saas collaboration electron Who built ContentSynergy AI? Ahsan Mahmood built it alone, and it is at contentsynergy.aoneahsan.com. Four surfaces come out of one codebase and one person maintains all of them: the web workspace, an Android build through Capacitor, a WXT browser extension and an Electron desktop client. It is React and TypeScript on Vite, with Radix UI on the interface, TanStack Query over the data, Yjs for collaborative editing, Three.js for the 3D side and Firebase underneath, with file storage going through FilesHub. Is ContentSynergy AI free to use? Its own description says free, and the architecture is what makes that sustainable rather than generous. Models run client-side wherever that is possible, so generation does not put a per-call bill on anybody. There are no Firebase Cloud Functions in the design, file storage goes through FilesHub, and the platform sits on free tiers, which keeps its running cost close to nothing. This page names no plans, limits or prices, because the record these pages are written from names none, and inventing a figure for a portfolio page would be the wrong kind of specific. Where does the AI actually run? Client-side, through @xenova/transformers, wherever that is possible. The Electron desktop client is where GPU-accelerated local engines are packaged, which is the heavier end of the same idea. Where client-side is not possible, the record does not say what happens, so this page does not say either. That hedge belongs to the source rather than to me, and it is left in place on purpose: an AI page is the easiest place in a portfolio to write a sentence that sounds precise and cannot be checked by anyone. Can two people work on the same thing at once? Yes. Real-time collaboration is built in through Yjs, using conflict-free replicated data types rather than a locking scheme. The practical difference is what happens when two people edit the same content in the same moment: a CRDT converges on a single result, where a last-write-wins design quietly discards whichever edit arrived second. That matters more in a content tool than in most products, because the thing being edited is usually somebody's afternoon of work and the loss is invisible until it is published. Does ContentSynergy AI work on iPhone? No, there is no iOS build. The web workspace opens in a browser on any device including an iPhone, the packaged mobile build is Android through Capacitor, and the desktop client is Electron. There is no Apple Developer account behind this work, so nothing built here has been submitted to the App Store, and the row describing this product is wrong where it says otherwise. That correction is recorded on this page rather than left in place. ## Nyuk.in Invoice Generator — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.invoiceapp Invoice generation and management application with modern UI An invoice is a document, not a screen. That sentence decides most of what this tool is. A screen can reflow, adapt to a phone and be regenerated whenever somebody opens it. A document gets emailed, printed, filed, and opened a year later by an accountant who was not there when it was made, and by then the application that produced it is irrelevant. So the PDF is the product. Everything else exists to make the document correct before it leaves. Line items have to break across pages in a way a reader follows rather than wherever the renderer ran out of room. Totals have to land where somebody's eye expects them. The text has to stay selectable, so a number can be copied rather than retyped, which is the difference between a real PDF and a picture of a web page saved with the wrong extension. Around the document sit the parts that make it a tool rather than a template. Clients are managed, and payments are tracked against what was sent. That pairing is the actual job. Generating paperwork is the easy half, and the half that takes the time is knowing which invoice went to whom and whether the money ever arrived. The build is React on the front with Laravel behind it and MySQL underneath. That split follows the same reasoning as the document. Invoices, clients and payments have hard relationships between them: a payment belongs to an invoice, an invoice belongs to a client, and a total is a fact derived from line items rather than a number stored beside them. A relational database enforces those, so a bad write fails instead of being recorded and discovered at reconciliation. The record describes this as a second version, built to a supplied design. That is a different job from a first attempt and it is worth naming as one. The layout was decided before the work started, so the work was making somebody else's decisions real, including the ones that only become difficult when an invoice with fourteen line items and a long client address has to fit inside a layout drawn with three. There is a caveat about the address, and it belongs on the page. The registered address is https://nyuk.in and the apex answered three of five checks from this machine. The www form answered every time. That is not the same as a site being down and it is not the same as a site being fine, so the link quoted here is the www one and the measurement is recorded rather than smoothed away. A reader who follows a link and gets nothing concludes the product is dead, and being specific costs one sentence. There is a quieter reason a billing tool is worth building carefully. An invoice is the document a business is judged by before anyone has met it. It arrives in an inbox, it carries a number somebody has to agree with, and if it looks careless the conversation that follows is about the invoice rather than about the work. Software that produces that document is doing reputational work whether or not it was designed to. What it cost is scope. This is a focused tool rather than an accounting package. It does not do books, tax, or the rest of what a business eventually needs, and the tools that do are large, expensive and staffed. Being the thing that makes a correct invoice and remembers who owes what is a smaller claim, and it is one this can actually make. Open the live site What it does, and what that costs to build Invoice generation PDF export Client management Payment tracking Built with React Laravel TypeScript MySQL Worth knowing invoice billing business finance Who built the Nyuk.in invoice generator? Ahsan Mahmood built it, and it is at www.nyuk.in. One person wrote the React interface, the Laravel application behind it and the PDF generation in between. The record describes a second version built to a supplied design rather than a first attempt, which is a different job: the layout was decided elsewhere and the work was making it real, including the parts of a design that only reveal their difficulty once a real invoice has to fit inside them. Why is a PDF the hard part of an invoice tool? Because an invoice is a document rather than a screen. It has to survive being emailed, printed, filed and read a year later by somebody's accountant, which means the output cannot be a picture of a web page. Line items have to break across pages sensibly, totals have to land where a reader expects them, and the text has to stay selectable so the numbers can be copied. Getting that right is most of the work. What does it track besides generating the document? Clients and payments. An invoice on its own is a file; what makes the tool useful is knowing which client it went to and whether the money arrived, because chasing an unpaid invoice is the actual job and generating the paperwork is the easy half. The record lists client management and payment tracking beside the generation and the export, and those four together are what separates this from a template with a form on it. Is the address reliable? Use the www form. The apex answered three of five checks from this machine and the www subdomain answered every time, which is a difference worth knowing before somebody follows a link and concludes the product is gone. That measurement is recorded rather than smoothed over, because a link that works most of the time is a specific kind of problem and describing it as working would be inaccurate in a way a reader finds out for themselves. Does it work on iPhone? It opens in any browser, an iPhone's included, because it is a web application and there is nothing to install. There is no iOS release: no Apple Developer account exists here and nothing I build has shipped to the App Store. For a tool whose output is a PDF that gets emailed, a browser is the right shape anyway, since the document matters more than the application that produced it. ## LearnQuest AI — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.learnqai 750+ games and learning activities for kids ages 1-15 An audience that runs from age 1 to age 15 is not one audience. It is several, and they share very little. That range is the whole design problem. A child who cannot yet read needs an interface with no words in it. A child at the top of that range will abandon anything that looks as though it was made for the one at the bottom. An interface for the youngest end is mostly pictures, sound and very large targets. One for the oldest end is closer to what an adult expects, and covering both honestly is the part that is easy to promise and hard to build. LearnQuest AI carries 750+ games and learning activities across the whole span. The number is the easy part. 750+ is written with a plus, which is a hedge rather than a rounding. A library that grows has no stable count, and a page quoting an exact one would be stale by the next addition. Age-appropriate content answers the range. Progress tracking is what makes a second visit continue rather than start again, and personalised learning paths sit over the top of both. Those are the four the record names. How the matching is decided, which subjects are covered and what a path is composed of are not written down anywhere I can point at, so they are not written down here either. A children's product is a bad place to guess. Progress tracking is worth one more sentence than it usually gets. Anything that lets a child pick up where they stopped is keeping a record about a child, and a record about a child is a responsibility somebody owns whether or not the feature list mentions it. The person who owns it here is the same person who wrote everything else. The stack is ordinary and the record is short about it. React and TypeScript with Tailwind on the interface, Firebase underneath, and AI and ML listed beside them. Listed is the accurate word: the record names the category and does not name a model, a provider or a mechanism, so a page filling that in would be describing something it had never seen. A name is not evidence. Firebase carries the whole backend. There is no server here that has to be kept alive, which is the arrangement that lets one person keep a product like this running for years instead of for a launch week. What it cost is the claim nobody gets to make. There is nothing on this page about what a child learns, how quickly they learn it, or how any of it compares with a classroom or another application. Those are outcome claims, and an outcome claim about a child's education is the kind that ought to arrive with evidence attached. There is none here. So the page describes an activity library with age filtering and a progress record, which is what the thing actually is. The second cost is breadth. 750+ activities is 750+ things that have to work, read correctly on a small screen and stay appropriate as a child moves up through fifteen years of the range. Nothing about that count makes any one of them easier. The record marks it completed. It is at learnqai.aoneahsan.com. Age-appropriate is a claim about content and a promise about judgement, and one person made every part of it. Open the live site What it does, and what that costs to build 750+ learning games Age-appropriate content Progress tracking Personalized learning Built with React TypeScript Firebase AI/ML TailwindCSS Worth knowing education kids games learning ai Who built LearnQuest AI? Ahsan Mahmood built it working alone, and it is at learnqai.aoneahsan.com. The same person chose the age bands, built the activity library, wrote the progress tracking and set the rules holding one child's record apart from another's. It is React and TypeScript with Tailwind on the interface and Firebase underneath, with AI and ML listed in the stack. The record marks it completed, which is a description of where it stands today rather than a statement about what happens to it next. What ages is LearnQuest AI for? Children from age 1 to age 15, which is a range rather than an audience. A child who cannot yet read and a child preparing for secondary school need different interfaces, different pacing and different content, and they will not tolerate each other's. That span is the reason age-appropriate content is listed as a feature rather than as a setting: on a product covering fifteen years of development, matching content to the child is most of the work rather than a filter applied at the end. What is actually in LearnQuest AI? 750+ games and learning activities, with age-appropriate content, progress tracking and personalised learning paths over the top. Those four are what the record names, and the page you are reading names the same four. Which subjects are covered, how an activity is graded and what a learning path is composed of are not recorded anywhere I can point at, so no version of them appears here. A children's product is a poor place to fill a gap with something plausible. How does it decide what a child sees? The record says age-appropriate content and personalised learning paths, and it does not say how either one is decided. So this page reports the capability and stops, rather than describing a matching rule that nobody wrote down. That distinction matters more here than on most products: a claim about how a child's material is selected is a claim a parent might reasonably act on, and it should come from the product rather than from a portfolio page trying to sound complete. Does LearnQuest AI work on iPhone? No, there is no iOS build. LearnQuest AI is a web application, so an iPhone or an iPad opens it in a browser exactly as any other device does, which for an activity library is most of what an installed application would have offered anyway. There is no Apple Developer account behind this work, so nothing built here has been submitted to the App Store. That is true of all of this work, and it is stated rather than left to be discovered. ## ExoHunter AI — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.exohunterai AI-powered exoplanet discovery and analysis platform This page will not tell you how well ExoHunter AI works. No source says, and astronomy is a poor field to guess in. Software that claims to help find planets is making two claims at once. One is about the software and the other is about the science, and only the first of them belongs on a portfolio page. What the record says is short. It is a platform that uses AI to assist in exoplanet discovery and analysis, with data analysis, pattern recognition, visualisation and research tools named as what it offers. Assist is the load-bearing word in that sentence. A tool that assists sits beside the person doing the work and hands them something to look at. It does not replace the judgement, and on this subject the judgement is most of the job. Pattern recognition produces candidates. A candidate is a thing to check rather than a thing to announce, and every field that has ever run automated detection over a large dataset has learned that the same way. The interesting output of a tool like this is a shortlist and the reason it was shortlisted. Visualisation is the half a browser genuinely does well. Rendering a signal so that a person can see the shape of it is a task suited to a screen, and it is the part of the product where a web application has no disadvantage against anything heavier. Research tools is the other phrase in the record, and it is not expanded anywhere. So this page reports that they exist and does not describe them, which is a shorter sentence than the alternative and a truer one. The rest is where the honesty has to live. No dataset is named in the record. No model is named. No accuracy figure, no false-positive rate and no comparison against another method appears anywhere, so none of them appears here. That absence is the page. An AI product page is the easiest place in a portfolio to write a number nobody can check, and a science-adjacent one is where such a number would do the most damage. A reader who trusted a made-up precision figure would be worse off for having read this. So there is a gap, and the gap is labelled. Underneath it is the ordinary arrangement. React and TypeScript with Tailwind on the interface, Firebase behind it, and free tiers throughout, with no server that has to be kept alive for the site to work. That constraint is also a boundary. Serious numerical work over a large survey is not something a free tier and a browser tab do, and a product built inside those limits is a place to look at data and reason about it rather than a place to run the heavy pass. Saying which of the two this is costs nothing and prevents a misunderstanding. The record marks it in progress. That is a fact about where it stands, and it is not a promise about what arrives next. Nothing on this page describes a plan, because a plan written on a portfolio is a commitment made by the person least able to guarantee the calendar. The same person chose the charts, wrote the data model and paid for the hosting, which on a project of this shape is worth knowing before anything else is read. It is at exohunterai.aoneahsan.com. The most interesting thing about a detection tool is its error rate, and this one has not published one. Open the live site What it does, and what that costs to build Exoplanet data analysis AI pattern recognition Data visualization Research tools Built with React TypeScript Firebase AI/ML TailwindCSS Worth knowing ai science astronomy research Who built ExoHunter AI? Ahsan Mahmood built it working alone, and it is at exohunterai.aoneahsan.com. It is React and TypeScript with Tailwind on the interface and Firebase underneath, with AI and ML named in the stack. One person chose the visualisations, wrote the analysis surface and set up the hosting, which is the same arrangement behind all of this work. It is a working software project rather than a research collaboration, and nothing about it is presented as anything else. Does ExoHunter AI find exoplanets? It assists with discovery and analysis, which is the word the record uses and the word this page keeps. The tools it names are data analysis, pattern recognition and visualisation, and pattern recognition produces candidates rather than conclusions. A candidate is something to check, and the checking is not what this software does. No detection, confirmation or result of any kind is claimed here, and none is recorded anywhere I could cite if I wanted to claim one. What does this page deliberately leave out? Every number somebody would most want, because none of them exist in any source I can point at. No dataset is named, no model or provider is named, no accuracy or false-positive rate is given, and there is no comparison against any other tool or method. That is not modesty. A figure invented to make an astronomy page sound credible is the single worst thing that could appear on it, and the standing rule on this site is that a fact without a published source is omitted rather than guessed. Is ExoHunter AI finished? No. The record marks it in progress, and that is a fact about today rather than a promise about anything later. This page says what the product does now and does not say what it will do next, which is the rule this work is written to. A roadmap is the easiest thing in the world to write and the hardest to be held to by one person maintaining a long list of things at once. Does ExoHunter AI work on iPhone? No, there is no iOS build, and there is no installed application on any platform. It is a web application, so it opens in a browser on a phone, a tablet or a desktop machine without anything being installed first. There is no Apple Developer account behind this work and nothing built here has ever been submitted to the App Store. For a tool whose output is charts on a screen, the browser was always going to be the honest place for it. ## ShopNest — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.shopnest Modern e-commerce platform with comprehensive shopping features This is the shortest record in the catalogue, and the page is short to match. ShopNest is an e-commerce platform. Its record lists a product catalogue, a shopping cart, a checkout, order tracking and a payment integration, over React and TypeScript with Tailwind, Firebase underneath and Capacitor for a mobile build. That is the whole of what is written down. Five features and a stack is not a story, and pretending otherwise is how a portfolio starts lying quietly. So this page will do something slightly unusual, which is to be honest about its own thinness rather than dressing it. There is a version of this entry that reads well: a paragraph on cart abandonment, another on checkout friction, a confident sentence about payment providers. Every word of it would be generic e-commerce commentary rather than anything about this build, and a reader who has seen three such pages recognises the shape immediately. What can honestly be said is this. The feature list is a skeleton. It is the standard shape of a store, in the order a customer meets it. A catalogue to browse, a cart to hold what was chosen, a checkout to turn that into money, tracking so the buyer knows where the parcel is, and a payment integration underneath the whole sequence. Each of those is a real piece of work and none of them is unusual. The interesting constraint is the one the stack implies. Firebase on a free tier is a ceiling rather than the absence of one, and a product catalogue is the first surface in any store to meet it. Browsing is the highest-volume read a shop has, it happens before anybody has bought anything, and a catalogue query without an explicit limit becomes a bill drawn by people who are not customers yet. That is the constraint every list read in this codebase would carry. There is one more fact on the record and it carries more weight than the feature list does. Its status is in progress. An e-commerce build has a threshold in it that nothing else in software quite matches: the first time a real person's money moves correctly from their card to somebody's account. Everything before that is a demonstration and everything after it is a business, and a feature list gives no indication which side of the line a project sits on. Naming the status is the only way a reader can tell. This one is on the near side. That is stated on the record and it belongs on the page, because the difference between a store that has processed an order and a store that has been designed to matters enormously and is invisible from a feature list. Nothing here claims a launch, a merchant, or a completed checkout. There is also nowhere to send you. The links are empty in every slot, and the single candidate address that was probed returns a not-found. That is worth separating from a site being briefly down. A dead candidate means there is no deployment behind the name at all, and the honest response is to name no URL rather than to point at this portfolio's own project index and let a click resolve to something that looks like a product. The page ends where the record does. That is not a writing failure. It is the accurate size of what exists, and a longer page would be describing a different project than the one in the database. Documentation What it does, and what that costs to build Product catalog Shopping cart Checkout system Order tracking Payment integration Built with React TypeScript Firebase TailwindCSS CapacitorJS Worth knowing ecommerce shopping retail saas Who built ShopNest? Ahsan Mahmood built it, working alone, and it is the thinnest entry in this catalogue for a reason worth stating. Its record carries five features, a stack and nothing else. There is no live address, no listing and no repository, and the one candidate address that was checked returns a not-found rather than a page. So this entry describes what was built and declines to describe what nobody can verify, which is the only honest shape available to it. Can I look at ShopNest? No. There is no published address on the record and the one candidate link that was probed answers with a not-found. That is different from a site being temporarily down, and it is different again from a private deployment somebody could be invited to. The page names no URL at all rather than pointing at the portfolio's own project index and letting the click look like a product. An absent link is a fact, not a formatting problem. What state is it actually in? In progress, according to its own record, and that status is the most informative thing on it. The catalogue entry lists a product catalogue, a cart, checkout, order tracking and payment integration, which is the standard shape of an e-commerce build rather than an account of how far this one got. Nothing here claims a launch, a merchant or a completed checkout. What exists is the scaffolding of a store, described as scaffolding. Why does the record say so little? Because nobody wrote more into it, and this page will not invent the rest. A portfolio entry can be padded from a stack list until it reads like a case study, and the result is a page that sounds informed while resting on nothing. The alternative is a short page that says what the record says, names its absences, and stops. That is the version here, and its brevity is the accurate signal rather than a gap in the writing. Does ShopNest work on iPhone? There is no iOS build. The record lists Capacitor in the stack, which is the tool that would package the web application for a phone, but no store listing exists on either platform and no address exists to open in a browser either. There is no Apple Developer account here and nothing I build has shipped to the App Store. On this entry the platform question is academic until there is something running to ask it about. ## 2FA Studio — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.2fastudio Two-factor authentication management studio for secure account management Losing the phone is how most people discover where their second factor actually lived. A two-factor manager is a small piece of software holding something irreplaceable. Each of those factors was set up once, on a screen, months ago, and almost nobody keeps a written record of all of them anywhere else. Two-factor authentication is the step after the password, and what makes it work is that it lives somewhere the password does not. That is also what makes it fragile, because the somewhere is usually one phone. One phone, carried about all day. So the interesting questions about 2FA Studio are all questions about custody. It manages tokens across accounts, stores them, syncs them between platforms and restores them from a backup. That list is the whole product, and every item on it is a place where a mistake would be permanent rather than annoying. Multiple accounts is the ordinary case rather than the advanced one. Anyone with a bank, an email provider and a work login already has three. The moment there are three, the question stops being how to set one up and becomes how to move all of them at once. Backup and restore is the feature people skip and then need. A second factor with no recovery path turns a lost or broken handset into a set of accounts somebody else now controls the door to, and the way back in is measured in support tickets and photographs of documents. Making the codes portable is the reason to run a manager at all. Portability is the whole point. Sync is the same argument, made across devices instead of across time. The web application and the Android build come out of one Capacitor codebase, with Firebase carrying an account between them. A second factor you can only reach from one device is a second factor that will be in another room at the moment somebody needs it, which is a failure mode nobody plans for and most people meet eventually. Biometric authentication guards the application itself. It is a local check on the device rather than proof to a server somewhere that a particular person was present at a particular moment. Those two are easy to confuse and worth keeping apart, because only one of them is a claim anybody else can rely on. Then the part this page will not tell you. The record I write these pages from says secure storage, and it does not say how. So this page does not describe the storage format, the key handling or the encryption, because for a credential manager those are precisely the details that must not be filled in from an assumption. A gap beats a guess. What it cost is caution about convenience. Every convenience in a token manager is also a copy. Copies are the risk. Sync makes the codes portable and puts them somewhere other than the device. A backup makes recovery possible and creates a second thing worth stealing. A biometric gate is a lock on the front of an application whose data still lives wherever the platform puts it. None of those trades is avoidable, and pretending otherwise is how a security product ends up over-claiming. So this page does not. It is built with React and TypeScript on Capacitor, with Tailwind on the interface and Firebase behind the sync, and it is at 2fastudio.aoneahsan.com. Test the restore before you need it. That advice applies to every backup ever made, and it applies twice to the one holding the second factor for everything else you own. Do it on a quiet day. Open the live site What it does, and what that costs to build 2FA token management Secure storage Cross-platform sync Backup & restore Biometric authentication Built with React TypeScript Firebase CapacitorJS TailwindCSS Worth knowing security 2fa authentication mobile Who built 2FA Studio? Ahsan Mahmood built it, in React and TypeScript on Capacitor, with Firebase behind the sync and Tailwind on the interface. It is at 2fastudio.aoneahsan.com. Capacitor means the web application and the Android build come out of one codebase, which for a tool holding second factors matters more than it does for most products: a factor you can only reach from one particular device has a habit of being in another room on the day you need it. What happens if the phone breaks? You restore from a backup, which is the entire reason a manager exists rather than a default app you cannot get anything out of. A second factor with no recovery path turns a broken phone into a set of accounts you no longer control, and getting back into those is measured in support tickets and identity documents rather than in minutes. Backup and restore is the feature people skip when setting up and remember afterwards. Set it up on the first day rather than the worst one. Can I use it on a laptop and a phone at once? Yes, that is what the cross-platform sync is for. The same codebase produces the web application and the Android build, and Firebase carries an account between them so the tokens are wherever you happen to be working. Which is convenient and is also a decision with a cost, since anything synced is by definition somewhere other than the one device: that is the trade a token manager makes on your behalf, and it is worth understanding rather than discovering. What does the biometric check actually protect? The application on the device, and nothing beyond it. A biometric gate is a local check that the person holding the phone is the person enrolled on it, which is a different claim from proving anything to a server somewhere. It stops somebody holding your handset from reading what is in the application. It does not authenticate you to a website, and it is not what the one-time codes themselves are for. Those two things are easy to run together and worth keeping apart. Does 2FA Studio work on iPhone? No. There is no iOS half: no Apple Developer account exists here, so nothing I build has shipped to the App Store. 2FA Studio runs on the web and as an Android application, both from the same Capacitor codebase, and an iPhone can reach the web version in a browser like any other device. A code generator is exactly the kind of software where a missing platform is worth stating plainly, because finding out later means moving every token again. ## AI Personal Assistant — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.aipa Comprehensive AI-powered personal assistant application Conversation, task management, personalised recommendations and voice support. Those four capabilities are the whole of what the record carries for this product. Nothing is added to it here. Everything else a reader wants to know about an assistant is absent from the record, and the useful thing this page can do is say so clearly rather than fill the space. Conversation is what anybody will judge it on, because it is the part that answers back. The other three decide whether the same person is still using it a month later. Assistant is a difficult word to be honest around. The category sells itself on understanding, memory and learning, and all three are claims about an internal behaviour nobody outside the product is able to check. So none of them appears here. Personalised recommendations is a capability, not a mechanism. It says something is tailored and it says nothing about how, and the gap between those two is exactly where a product page usually starts inventing. The record names no model and no provider, so this page names neither. Task management is the one that has to be right. An assistant that loses a task is worse than having no assistant, because the person stopped keeping the list themselves the moment they trusted it. That is the first thing worth testing in anything calling itself an assistant. Voice support is listed and not described. Which languages, whether the processing happens on the device or elsewhere, whether anything is retained afterwards: none of it is written down, and on a feature that involves a microphone those are the questions that matter most. So the page stops. Stopping there is a decision rather than an oversight. A page that keeps going past its evidence leaves the next reader no way of telling which half of it was checked. Underneath sits the usual arrangement. React and TypeScript with Tailwind on the interface, Firebase behind it, free tiers throughout, and no server of mine anywhere in the path that has to be kept alive for it to work. AI and ML appear in the stack list. That entry names a category rather than a dependency, which is a distinction worth making on a product whose whole name is the category. A stack list is evidence of what was chosen and is not evidence of what it does. What it cost is checkability. This site exists so that a claim can be opened and verified, and a product with a short record gives a reader very little to open. The link works and the product runs, and beyond that the honest position is that there is not much written down. That is a cost rather than a mystery. The alternative was a page of confident sentences about conversational depth and personalisation quality, none of which would have survived a reader spending ten minutes with the product itself. One person wrote the interface, set up the data and pays the hosting bill. That is the one fact about this product nobody has to take on trust, because one person doing all of it is the arrangement this whole practice is built on, and the about page states it plainly. The record marks it in progress. That is a fact about today and not a plan for later, and no page here converts one into the other. It is at aipa.aoneahsan.com. An assistant is judged on the day it forgets something, and no page can tell you about that day in advance. Open the live site What it does, and what that costs to build AI conversations Task management Personalized recommendations Voice support Built with React TypeScript Firebase AI/ML TailwindCSS Worth knowing ai assistant productivity chatbot Who built AI Personal Assistant? Ahsan Mahmood built it working alone, and it is at aipa.aoneahsan.com. One person decided what it does, wrote the interface, set up the data and pays for the hosting, which is the arrangement this whole practice runs on. It also means there is exactly one person accountable for what this page claims, which is why the page claims very little: the record behind this product is short, and a page cannot honestly be more detailed than the record it is written from. What can AI Personal Assistant actually do? Four things are recorded: conversation, task management, personalised recommendations and voice support. That is the complete list, and nothing is added to it here. What conversation covers, how a recommendation is arrived at, and what voice support includes are not described in any source I can point at, so they are not described on this page either. An assistant is a category that invites large claims, and the honest version of this one is four capabilities and the name of the person who built them. Does it learn from me or understand me? No claim of that kind is made here, by me or by the record. Personalised recommendations is a listed capability and it is not a description of a mechanism, so nothing on this page says the product remembers you, models you, adapts to you or understands anything. Those are the four sentences an assistant product is most often sold with and the four that are hardest to check from outside. Where a source exists, this site quotes it; where none does, the claim is left out rather than softened into something vague. What is AI Personal Assistant built with? React and TypeScript, with Tailwind on the interface and Firebase underneath, and AI and ML listed in the stack. That last entry names a category and not a provider, a model or a mechanism, and the record goes no further, so neither does this page. It is a web product running on free tiers with no server of mine that has to be kept alive, which is the constraint every project here is built inside. The record marks it in progress rather than finished. Does AI Personal Assistant work on iPhone? No, there is no iOS build. It is a web application, so an iPhone opens it in a browser in exactly the way a laptop does, and nothing needs installing first. There is no Apple Developer account behind this work, so nothing built here has been submitted to the App Store, and no page here says otherwise. The limit belongs to the practice rather than to this one product, and it is stated up front rather than discovered afterwards. ## Ubuntu Suspender — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.ubuntususpender Battery management application for Ubuntu/Linux with auto-suspend and power profiles Ubuntu Suspender is a desktop application about one thing: how long a Linux laptop lasts between charges. Four capabilities, in one window. Auto-suspend, power profiles, system monitoring and battery health tracking. Auto-suspend is the one that saves the most and irritates the most. Suspending an idle machine is easy to describe and awkward to get right, because the rule that protects a battery on a laptop left open at lunch is the same rule that will put the machine to sleep in the middle of something you left running on purpose. Everyone who has been caught by that once configures it differently afterwards. Power profiles are the manual half of the same decision. You choose the trade you want between speed and endurance, and the machine stops guessing on your behalf. A profile is a statement of intent rather than a heuristic, which is the right shape for the times when you already know what the afternoon looks like. System monitoring is what makes the other two arguable instead of arbitrary. A number in front of you changes what you set. Without one, a power profile is a preference. With one, it is a measurement you can go back to and disagree with later. Battery health is the long game. A battery does not fail on a particular Tuesday. It loses capacity slowly across a couple of years, and the only way to see that curve is to have been recording it since before anybody cared about it. The application is built with React and TypeScript inside Electron, with Node.js underneath doing the reading. That last part is the reason Electron is here at all. The values this kind of tool needs come from the operating system rather than from a browser API, and Node is the half of the runtime that can ask for them and render them in the same process. It is a userspace application. Everything it does, it does through what the system already exposes, which is a limit worth knowing before expecting it to change how a machine sleeps at a level below that. What it cost is the obvious thing to notice. An application about power consumption ships a browser engine of its own. That is a genuine tension and I am not going to argue it away: Electron bought a familiar interface layer and a process with system access, and it charged for both in memory on a machine whose battery is the subject. The trade holds while the window is closed most of the time and the settings it wrote keep working. It holds less well if you leave it open all day watching a graph of the thing it is consuming. One person wrote all of it. The suspend logic, the interface, the packaging, and the decision about which of the four capabilities was worth building first. There is no public link here. The product is not publicly reachable, so there is nothing here for you to open and check, and that is the honest state of it rather than an omission. What is left is the description above. It is accurate about what the application does and silent about the things the record does not carry, which is the only version of this page worth publishing. What it does, and what that costs to build Auto-suspend Power profiles System monitoring Battery health tracking Built with React TypeScript Electron Node.js Worth knowing linux ubuntu battery desktop-app Who built Ubuntu Suspender? Ahsan Mahmood built it alone, as a desktop application in React and TypeScript on Electron, with Node.js underneath reading the system. There is no public link on this page. The product is not publicly reachable today, and a portfolio page that invents somewhere to send you is worse than one that says so plainly. What is on this page is a description of what the application does, which is the part I can stand behind without a link beside it. What does auto-suspend actually decide? When an idle machine should be put to sleep instead of left awake. That is easy to describe and awkward to tune, because the same rule that saves a battery on a laptop left open at lunch is the rule that will suspend a machine in the middle of something you deliberately left running. Ubuntu Suspender puts the rule in front of you rather than burying it, which is the difference between a setting you adjust once and a behaviour you eventually switch off in frustration. What is battery health tracking for? Seeing degradation before it becomes a surprise. A battery does not fail on a particular Tuesday: it loses capacity slowly over a couple of years, and the only way to see that curve is to have been recording it since before anyone cared. Tracking health turns a vague feeling that the laptop does not last as long as it used to into a series of readings, which is also the difference between replacing a battery on evidence and replacing it on a hunch. Does Ubuntu Suspender run on Windows or on a phone? No. It is built for Ubuntu and other Linux systems, and it is a desktop application rather than anything mobile. There is no iOS release behind any of my work, because there is no Apple Developer account here and nothing I build has shipped to the App Store. Power management is also close enough to a particular operating system that a build claiming to cover several would be making a promise about paths and interfaces it could not keep. Why is a battery tool an Electron application? Because the readings come from the operating system rather than from a browser API, and Node.js is the half of Electron that can ask for them. That choice buys a familiar interface layer and a process with system access in one package, and it charges for both in memory. It is a real tension in a tool about power consumption and it is worth stating rather than arguing away: the trade holds while the window is closed most of the time, and it holds less well if you leave a graph open all day. ## ForgeAI — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.forgeai Professional AI-powered tools suite for writing, design, development, and business Every call to a hosted model costs somebody money. A suite of 15 AI tools therefore has an economics problem before it has a first feature. ForgeAI answers that with a key. Bring your own key is the arrangement where the user supplies the provider credential and the usage lands on their account. The record puts it plainly: BYOK support for unlimited access. Read that sentence in the other direction. Unlimited with your own key means there is a ceiling without one. What that ceiling is, the record does not state, so no number for it is invented here. The tools themselves are a spread rather than a theme. Email optimisation, code review, colour palette generation, data visualisation, meeting notes extraction and resume matching are the six the record names. After those it says and more, and puts the total at 15. This page stops where the record stops. Naming six and inventing the remainder would be the kind of small dishonesty that costs nothing to write and cannot be defended once somebody opens the product and counts. Those six describe a working day rather than a job title. Somebody whose week contains email, code, a design decision, a spreadsheet, a meeting and a hiring shortlist is the person this suite is aimed at, and the list is the clearest statement of that anywhere in the record. Resume matching touches somebody's job. Nothing here claims an outcome for it, because a tool that ranks applications is a tool sitting next to somebody's employment, and a portfolio page is not the place to make a promise about that. The key pattern behind it is a standing decision rather than a one-product idea. A per-call cost is the only kind of expense that cannot be designed away by picking a cheaper host, because it grows with use instead of with uptime. So the standing arrangement for anything here that reaches a paid model is a route where a user's own key removes the ceiling entirely. That is the difference between a limit and a wall. A limit is a number somebody can plan around. A wall is a product that stops being useful on the day a person starts relying on it. Everything else runs on the usual arrangement. React and TypeScript with Tailwind on the interface, Firebase underneath, free tiers throughout, and no server of mine that has to be kept alive for any of it to work. What it cost is the specifics. There is no accuracy figure on this page and no model or provider named, because the record names none of them. An unsourced number would make this read better and would be worth less than the gap it filled. An AI page is where that temptation is strongest. The second cost is width, and every tool suite pays it. 15 tools is 15 interfaces, 15 sets of expectations, and 15 things that can quietly stop matching an API somebody else controls. 15 is also a promise about maintenance. Every one of those tools has to keep working while somebody else's format, interface or API moves underneath it. The record marks it completed. That is a status on a row. It describes today rather than promising anything about next month. It is at forgeai.aoneahsan.com. Bring your own key moves the bill to the person making the calls, which is the honest arrangement and also the one people remember at the first invoice. Open the live site What it does, and what that costs to build Email optimization Code review Color palette generation Data visualization Meeting notes extraction Resume matching BYOK support Built with React TypeScript Firebase AI/ML TailwindCSS Worth knowing ai tools productivity saas business Who built ForgeAI? Ahsan Mahmood built it working alone, and it is at forgeai.aoneahsan.com. One person decided what the 15 tools are, what each of them shows, how the bring-your-own-key path works and where the data sits, which is why the tools behave like one product rather than like 15 things sharing a menu. The record marks it completed, meaning the scope that was set has shipped. That is a fact about the product today and not a statement about what comes next. Which tools are in ForgeAI? The record names six of them: email optimisation, code review, colour palette generation, data visualisation, meeting notes extraction and resume matching. It then says and more, and puts the total at 15. This page stops where the record stops, because listing six and inventing the rest would be the sort of small dishonesty that is very easy to write and impossible to defend afterwards. Those six describe a working day rather than a job title, which is a reasonable guide to who the suite is for. What does bring your own key mean in ForgeAI? You supply your own provider credential, and the usage runs on your account rather than on the platform's. The record calls it BYOK support for unlimited access, which is a sentence worth reading in both directions: unlimited with your own key means there is a ceiling without one. What that ceiling is, the record does not state, so no figure for it appears here. The reason the pattern exists at all is that a per-call cost is the one expense that grows with use rather than with uptime. Which AI model does ForgeAI use? No model is named, and no provider is named either. The record describes what the tools do and lists AI and ML in the stack, and it stops there, so this page stops there as well. Nothing here reports an accuracy rate, a benchmark or a comparison against another tool, because a figure like that would have to be invented to appear at all. A product page is the last place an unverifiable number should be allowed to sit, and an AI product page is where it is most tempting. Does ForgeAI work on iPhone? No, there is no iOS build and no installed application on any platform. ForgeAI is a web product, so an iPhone reaches it in a browser the same way a laptop does. There is no Apple Developer account behind this work, so nothing built here has ever been submitted to the App Store, and that limit is stated plainly rather than left for somebody to run into later. ## Capacitor Biometric Authentication — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.capacitorbiometricauth Framework-agnostic, provider-less biometric authentication library: one TypeScript API for WebAuthn + native biometric sign-in across Web, iOS, Android,… A fingerprint check on a phone proves something to that phone. It proves nothing whatever to your server, and the distance between those two sentences is where most biometric login work quietly goes wrong. A screen decides the user is authenticated. The backend, which saw none of it, is then asked to agree on the strength of a message from the browser. That gap is the shape this package is built around. Before it, one feature meant three integrations. Android's BiometricPrompt has its own lifecycle. The browser has WebAuthn, which is a specification rather than a convenience API. Electron has its own arrangement again. Every application rebuilt all of it, in a different framework, usually under time pressure. This makes it one call. There is no provider to mount and no context to thread through a component tree. The same import works from React, Vue, Angular or plain JavaScript, and on a Capacitor build it becomes a native plugin without the calling code changing shape. On a pure web project the Capacitor core stays an optional peer, so a browser-only application carries none of the native surface. The convenience is not the interesting part. What the library returns is. A successful ceremony hands back a standard, server-verifiable WebAuthn response rather than a boolean saying yes. A boolean is something the client says about itself. A WebAuthn response is evidence a server can check against a credential it stored and a challenge it issued, which is a different kind of claim entirely, and it is the only kind worth putting behind a login. That distinction is why the package declines to look simpler than it is. Key material sits in hardware-backed storage wherever the platform provides it, so the private half never travels and never lands in a variable that some error tracker will helpfully serialise on the way to a dashboard. Then there is the half almost nobody ships. Verification belongs on a server. A client library that stopped at the browser would be an unfinished feature with a confident README, so a second package exists: webauthn-server-buildkit, which handles the relying-party side. Two packages, one author. That is the only reason the response one produces is the response the other expects. The cost was holding two codebases in agreement instead of shipping one quickly. 71 tests on this side and 311 on the server side, plus four build outputs so it drops into a bundler that already has opinions. There is a cost you inherit as well, and it is not a small one. A biometric credential is bound to a device. A user who loses that device has lost that credential, so a recovery path is your product's job, and this library does not pretend to offer one. Anybody who tells you a biometric library solved account recovery has not read what it returns. The native half is Android. There is no iOS release behind this work, because there is no Apple Developer account here. MIT, on npm as capacitor-biometric-authentication, which is the one link this project has. Read what it gives back before deciding it saves you time. If a boolean would have satisfied your product then the library was never the thing you were missing, and the honest saving here is measured in what your server can prove rather than in lines of code. npm What it does, and what that costs to build One API for WebAuthn + native biometrics Framework-agnostic (React/Vue/Angular/vanilla JS) Provider-less Zustand-style state layer Capacitor-optional (zero footprint on pure web) Server-verifiable WebAuthn response Paired webauthn-server-buildkit server package Hardware-backed secure storage Built with TypeScript CapacitorJS WebAuthn Swift Kotlin Rollup Worth knowing npm package biometric webauthn authentication capacitor Who built Capacitor Biometric Authentication? Ahsan Mahmood built both halves of it: this client library and the server package it pairs with. That pairing is the point rather than a coincidence, because the response the client produces is exactly the response the server verifier expects, and the same person wrote both contracts. It is published on npm as capacitor-biometric-authentication under MIT, and the npm page is the single public link this project has. There is no company behind it and no support desk; one maintainer is the whole picture, stated here rather than discovered later. Does a successful biometric check prove anything to my server? Not on its own, and that gap is what this library is built around. A biometric check satisfies the device it happened on. Your backend has seen none of it and has no reason to agree. So a successful ceremony here returns a standard WebAuthn response your server can verify against a challenge it issued and a credential it stored, instead of a boolean the client simply asserts. Verifying that response is a separate job on a separate machine, which is what the companion webauthn-server-buildkit package exists to do. Does Capacitor Biometric Authentication work on iPhone? The WebAuthn path is a browser API, so it does whatever the browser on that device already supports, and none of that is code I wrote or can vouch for on Apple hardware. The native half is Android, through BiometricPrompt. There is no iOS release behind this work, because there is no Apple Developer account here and nothing I build has shipped to the App Store. Read anything Apple-shaped inside the package as unexercised rather than proven, which is the honest state of it. Do I need Capacitor to use it? No. The Capacitor core is an optional peer, so a browser-only project installs the package, gets the WebAuthn path, and carries none of the native surface in its bundle. Adding Capacitor later does not change your calling code, because the same API becomes a native plugin on a mobile build. That second direction matters more than it sounds. A team that ships a web application first and wraps it for mobile in year two does not rewrite its authentication layer to do it. Where does the private key material live? In hardware-backed secure storage wherever the platform offers it, which means the private half of a credential never travels and never lands in a JavaScript variable. That is a property of the device rather than something this library invented, and saying so plainly matters. A package cannot make a phone secure; it can only avoid undoing what the phone already does. What the library owns is the discipline around that: not copying the material, not logging it, and not exposing it through an API that would let your own error tracker serialise it by accident. ## Notification Kit — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.notificationkit Zero-runtime-dependency TypeScript library that unifies push, local, and in-app notifications behind one provider-less API for React and Capacitor apps. Three separate things get called a notification and only one of them involves a server. A push arrives while the application is closed and travels through a vendor. A local notification is scheduled on the device itself and fires whether or not anything is reachable. An in-app message is a piece of interface, and calling it a notification at all is mostly habit. Three mechanisms wearing one word. Which is why this work is always larger than the ticket that requested it. A team wires Firebase for push, a Capacitor plugin for local, a toast component for in-app, then three separate permission stories, then a service worker whose lifecycle nobody enjoys learning. Notification Kit puts one API over all three. Schedule a local notification. Show an in-app message. Get a push token. The same shape each time, with optional React hooks for the places where a hook is genuinely easier than a function call. Zero required dependencies is the decision underneath that. The core works the moment it is installed. A provider SDK loads dynamically only when that provider is enabled, so a project that schedules local reminders and nothing else never downloads a push vendor, and adding push a year later is configuration rather than surgery. The details that make it usable are the ones nobody writes on a roadmap. Android notification channels, because a channel the user silenced swallows a notification without raising an error anywhere. Rich media. A fluent way to say every weekday at eight. Service-worker templates, since the worker is where most web push integrations quietly come apart. A service worker is not a background process you own. The browser starts it, stops it and starts it again on its own schedule, so anything written on the assumption that it stays alive between two events is a bug waiting for a slow morning and a cold cache. Then there is a boundary worth stating in a page like this. A reminder your product schedules for one user is a local notification. It is not a push. Treating it as one buys a vendor, a permission prompt and a delivery chain that the feature never needed, and that single confusion is what turns a two-day job into a fortnight. What it cost was 124 tests, a dual ESM and CommonJS build, full type declarations, a setup CLI and a written integration guide aimed at coding agents rather than people. An integration guide written for coding agents is a strange artefact to produce. It exists because the thing wiring a package into a codebase is increasingly not a person reading a README, so the guide is written for that reader rather than bolted on as decoration. The standing cost is delivery, and it is not mine. Push depends on a vendor, on a platform's power management and on a permission the user can revoke without telling anyone. An honest library reports what it knows and declines to claim what it cannot observe, which means there is no delivery guarantee here and there was never going to be one. There is no iOS release behind this work, because there is no Apple Developer account here. It is on npm as notification-kit, with the source at github.com/aoneahsan/notification-kit. Getting a token is easy. What your product does on the morning that token stops working is the actual feature, and it is the part a notification library can help with only if it was honest about the rest. npm Source on GitHub What it does, and what that costs to build One API for push, local & in-app notifications Zero runtime dependencies (all peers optional) Provider-less React hooks (no context wrapper) Fluent recurring local-notification scheduling Android notification channels + rich media Service-worker templates + setup CLI Dual ESM + CJS packaging Built with TypeScript Vite 8 React 19 CapacitorJS Firebase OneSignal Worth knowing npm package notifications react capacitor zero-dependency Who built Notification Kit? Ahsan Mahmood built and published it, including the setup CLI and the written integration guide for coding agents. It is on npm as notification-kit, with the source at github.com/aoneahsan/notification-kit. A package like this only exists because the same person had already wired push, local notifications and in-app messaging separately in several products, and the third time through is when a pattern becomes obvious enough to be worth extracting. The pieces that survived that extraction are the ones that had broken in real use rather than the ones that looked tidy. What does zero dependencies mean if it talks to Firebase and OneSignal? It means those vendor SDKs are not installed with the package and are not in your bundle until a provider is switched on. The core is self-contained and works the moment it is installed; a provider SDK loads dynamically at the point a project actually enables it. So a product that only schedules local reminders never downloads a push vendor, and a product that adds push a year later restructures nothing to get it. The peers stay optional in both directions, which is why the package can sit in a small project without dominating it. Does Notification Kit work on iPhone? No. There is no Apple Developer account behind this work, so nothing here has shipped to the App Store, and web and Android are the two surfaces the push path has actually been exercised on. The in-app half is a different matter, because in-app messaging is ordinary interface code and runs wherever your application runs, an iPhone browser included. What you should not read into that is any claim about push delivery on an Apple device, which is untested here and stays untested until an account exists. Does it handle the permission prompt and Android channels? Yes, and the channel half matters more than it looks. On Android every notification belongs to a channel, and a channel the user has silenced swallows the notification without raising an error anywhere, so a library that ignores channels hands you a bug your own logs cannot see. Permission is asked behind a user action rather than on first load, because a prompt fired at a stranger is a prompt denied permanently. Rich media and a fluent way to express a recurring local schedule live in the same layer. When should I use a local notification instead of push? Whenever the reminder belongs to one user and your server has nothing to add to it. A daily nudge, a timer, a scheduled check-in: each of those is a local notification, scheduled on the device, firing whether or not a network is there. Treating them as push buys a vendor, a delivery chain and a permission conversation none of them needed. Push earns its place when the event starts somewhere other than the device, which is a narrower set of cases than most product plans assume at the outset. ## Strata Storage — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.stratastorage One storage API across localStorage, IndexedDB, cookies, the URL, native Keychain and Keystore, SQLite and the filesystem. Zero dependencies. Nothing in my own projects calls localStorage directly. That rule is not fussiness. A value starts in localStorage because it is the shortest thing to type, then it has to survive a reinstall, then something native has to read it, and by the time all three are true the code touching it is spread across a dozen files nobody wants to open. Storage looks trivial. It is the layer you get to change least often. Each backend has its own grammar and none of them agree with the others. localStorage is synchronous and holds strings. IndexedDB is asynchronous and holds structures. Cookies carry expiry and scope. The URL is storage that people forget is storage, right up until a filter panel loses its state on a refresh. Strata Storage puts one grammar over all of it. Get, set, remove, query, subscribe. Five verbs, and the answer they give does not change depending on where the value happens to sit. Zero runtime dependencies was the second decision, and it is the one that cost the most to hold. React, Vue, Angular and the Capacitor core are optional peers. Your project installs them if it already has them, and the package brings none of them along. A library that arrives with its own opinion about your framework is a library you argue with eighteen months later, usually during an upgrade you did not plan. Everything expensive stays off. Encryption, compression, expiry, queries, integrity checksums, durable writes, mirroring and snapshots are enabled per call or per instance, never by a global switch inherited from somebody else's configuration. A default that encrypts everything makes every read slower for the sake of the one value that needed it. Two of the optional pieces earn their weight more often than I expected. A query engine over stored values means a lookup stops being a full read followed by a filter in JavaScript, and cross-tab observers mean two open tabs stop disagreeing about who is signed in. The native side is where a parity claim has to be earned rather than announced. A key-value store on a phone can be a preferences file, a SQLite table or a file on disk, and those three diverge exactly where a wrapper is tempted to paper over them: what happens to a half-finished write, and what a stored expiry means after a reboot. I wrote that part twice. A multi-store SQLite backend and a file-per-key filesystem adapter now answer the same call the same way, which is a smaller sentence than the work behind it. None of that was asked for. What it cost is a strict TypeScript build, a suite of 140 tests and a documentation site nobody asked for. A storage library whose semantics you cannot read is a storage library nobody should hand a session token to, so the documentation is part of the product rather than an afterthought attached to it. There is a second cost I did not choose. Every backend belongs to somebody else. A browser tightens its eviction policy, a platform revises what counts as durable, and the abstraction has to absorb that without moving the five verbs, because the whole promise is that the verbs do not move. Apache-2.0 on npm, source on GitHub, documentation at stratastorage-docs.aoneahsan.com. It is the storage layer inside my own products, which means it gets used badly and early, by me, in the places where a shortcut would have been easier. Open the live site npm Source on GitHub Documentation What it does, and what that costs to build Unified API across 8+ storage backends Zero runtime dependencies AES encryption, compression & TTL (opt-in) Query engine + cross-tab sync + observers Disaster recovery (checksums, durable writes, snapshots) Native SQLite multi-store + filesystem adapters Provider-free React/Vue/Angular bindings Built with TypeScript CapacitorJS Swift Java Vitest Worth knowing npm package storage capacitor cross-platform zero-dependency Who built Strata Storage? Ahsan Mahmood wrote all of it, from the browser tier down to the native adapters. That matters more in a storage library than it does in an application, because the person who decided what an expiry means in localStorage is the same person who implemented it in SQLite, so the two agree rather than drift. It is published on npm as strata-storage under Apache-2.0, the source is at github.com/aoneahsan/strata-storage, and the documentation site is at stratastorage-docs.aoneahsan.com. Every claim here is checkable from one of those. Why use this instead of calling localStorage directly? Because localStorage is the decision you are least able to reverse cheaply. It is synchronous, it holds strings, it is per-origin, and every one of those becomes a constraint the day the value has to survive a reinstall or be read from a native shell. Strata Storage gives you one set of verbs over eight or more backends, so changing where a value lives is a configuration change rather than a rewrite of every call site. If a value genuinely never leaves one browser tab, the plain API is fine and this is overhead. Does Strata Storage work on iPhone? The browser backends run wherever the browser runs, an iPhone included, because they are web APIs rather than anything I wrote for a platform. What does not exist is an iOS release: there is no Apple Developer account here, so nothing I build has shipped to the App Store, and Android is the native tier I can actually vouch for. Treat the mobile parity claim as proven on Android and unverified anywhere Apple has to approve it. What does adding it cost a bundle? The runtime ships zero dependencies, so the baseline cost is the package itself and nothing it drags in. React, Vue, Angular and the Capacitor core are optional peers, installed only if your project already uses them, which means a plain TypeScript project pays for none of that surface. The power features are the same shape: encryption, compression, expiry, queries, checksums, durable writes, mirroring and snapshots are all off until a call or an instance turns them on. Is stored data encrypted by default? No. Encryption is opt-in, per call or per instance, and that is a deliberate choice rather than an oversight. A library that encrypts everything by default makes every read slower for the sake of the one value that needed it, and it hides the question of which values those are. Deciding what deserves AES is a decision about your data, so the library asks you to make it. Compression, integrity checksums and durable writes follow the same rule and are switched on the same way. ## Unified Error Handling — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.unifiederrorhandling Zero-dependency TypeScript library with one consistent API to capture, enrich, and route errors to ten monitoring services, loaded on demand so you only… Changing crash reporters costs a fortnight in a codebase that never planned to. The SDK is never in one place. Its calls are spread through forty files, its types have leaked into your own function signatures, its breadcrumb API is attached to every interesting event, and by the time you have pulled all of that out you have changed behaviour you never meant to touch. So most teams never switch. They pay the renewal instead, which is lock-in doing its work quietly, bought at the moment of the first install rather than at the moment of the invoice. Unified Error Handling is a thin layer that keeps the decision open. Underneath it there is very little: a singleton error store, a BaseAdapter contract and adapters loaded by dynamic import. Sentry, Firebase Crashlytics, Bugsnag, Rollbar, Datadog, LogRocket, Raygun, AppCenter, a console adapter for development, and any adapter you write yourself against the same contract. Changing vendor becomes one line. The core carries zero runtime dependencies and sits at roughly 7 KB brotli. Because every adapter is a dynamic import, a project reporting to one provider never ships the other nine. That number is the point of the exercise. An abstraction that costs more than the SDK it wraps is an abstraction nobody keeps past the first performance review. An optional React entry point adds error boundaries, around twelve hooks and around nine higher-order components, all provider-less, so nothing needs wrapping in context before it can report a failure. Two behaviours matter more than the length of the provider list. The first is the offline queue. An error thrown on a train is the error most worth having, and it is exactly the one a direct SDK call drops, so errors are queued and flushed when the connection returns. The second is enrichment. A stack trace tells you what broke. Breadcrumbs and context tell you what the person was doing at the time, which is the difference between a bug you can reproduce and a ticket open for a month. There is a discipline this layer does not remove. An error payload is one of the places personal data escapes a product. Centralising the reporting path makes it possible to scrub in one place instead of ten, and it does not decide what counts as personal for your product. That judgement stays yours. What it cost was staying thin. Every provider has a feature that maps onto none of the others. Each of those is an argument for widening the contract until it becomes a union of ten SDKs, which is the failure mode of every abstraction shaped like this one, so the contract stayed small and the escape hatch is a custom adapter. There is a stated limit too. This is a routing layer rather than a monitoring product: it stores nothing, charts nothing, and does not replace the vendor whose dashboard you actually read on a bad morning. It runs in browsers, in Node.js and in React Native. There is no iOS release behind this work, because there is no Apple Developer account here. MIT, on npm as unified-error-handling, with the source at github.com/aoneahsan/unified-error-handling. Sentry is my own default. This package is the reason that choosing it once was never the same as choosing it for good, and reversibility is the only thing it is really selling. npm Source on GitHub Documentation What it does, and what that costs to build One API for 10 error-monitoring providers Zero runtime dependencies (~7 KB brotli core) Dynamic import() adapter loading Provider-less React error boundaries + hooks Offline queue (errors flush on reconnect) Context enrichment + breadcrumbs Dual ESM/CJS with conditional exports Built with TypeScript esbuild React 19 Vitest Worth knowing npm package error-handling observability typescript zero-dependency Who built Unified Error Handling? Ahsan Mahmood wrote it, including all ten adapters and the optional React entry point. One person could, because the layer is deliberately small: a singleton, an adapter contract and a set of dynamic imports, rather than a reimplementation of anybody else's SDK. It is on npm as unified-error-handling under MIT, with the source at github.com/aoneahsan/unified-error-handling. Sentry is the default in his own projects, and this package is the reason that choice never had to be a permanent one. Why an abstraction over Sentry instead of using Sentry directly? Because the second decision is the expensive one. Installing an SDK takes an afternoon; taking one out takes a fortnight, since its calls, its types and its breadcrumb API end up spread through the codebase. This layer keeps the vendor behind one contract, so a change of provider is an adapter swap rather than a migration. If you are certain you will never move, use the SDK directly and skip this. An abstraction nobody ever exercises is only a layer to read through at three in the morning. Does Unified Error Handling work on iPhone? It is a JavaScript library, so it runs wherever your JavaScript already runs, which includes a browser on an iPhone and a React Native build. What does not exist here is an iOS release of anything: there is no Apple Developer account behind this work, and nothing I build has shipped to the App Store. The practical answer is that it will report an error from a Safari session on an iPhone exactly as it reports one from Chrome, and none of that involves a platform I have shipped to. What happens to an error thrown while the device is offline? It goes into a queue and is sent when the connection comes back. That is the case worth building for, because an error thrown on a train or in a lift is the one a direct SDK call quietly drops, and those are disproportionately the interesting ones. The queue lives in the core rather than in a provider adapter, so it behaves the same whichever vendor is enabled. What it cannot do is survive the user closing the application before reconnecting, and no client-side reporter can. How much does it add to a bundle? Roughly 7 KB brotli for the core, with no runtime dependencies underneath it. Every adapter is a dynamic import, so a project reporting to one provider never ships the other nine, and the package is tree-shakeable with dual ESM and CommonJS builds behind conditional exports. That number is not decoration. An abstraction costing more than the SDK it wraps is one a team deletes during the first performance review, which is why staying thin was the constraint the whole design was written under. ## Unified Tracking — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.unifiedtracking Zero-runtime-dependency TypeScript package giving web, React, and Capacitor apps one API for analytics, user identity, revenue, consent gating, and error… The consent check happens before any provider sees an event. That reads like an implementation detail. It is the entire design. Most instrumentation puts consent at the edges: a banner sets a flag, each SDK is initialised or not, and every call site is trusted to have asked the question first. One forgotten call site and a vendor holds a record it should never have received. You cannot take it back. A deletion request to a third party is a slower, weaker and far less certain thing than never having sent the event, which is why the gate belongs somewhere a developer cannot walk past it. So it sits on the dispatch path itself. An event is checked, policy-excluded properties are stripped, and only then does anything reach a provider. A call site cannot skip that step, because there is no route to a vendor that goes around it. Sixteen providers sit behind the interface: eight for analytics and eight for error tracking. Initialisation, event tracking, user identity, screen views, revenue and error capture all arrive through one API rather than through eight vendor SDKs, each with its own vocabulary for the same three ideas. On the React side there is no context wrapper and no higher-order component. You import a hook and call it, which means instrumentation can go into one screen without touching anything above it in the tree. The second decision is a trade rather than a win. The package carries zero runtime dependencies. Each vendor SDK is fetched from that vendor's own CDN when the provider is switched on. The bundle stays small, and a product using two providers never downloads the other fourteen. That is right for a website and wrong for a browser extension. An extension may load no remote code at all, and a store treats a CDN script as grounds for rejection rather than as a review note. So an extension bundles its analytics or ships with none, and this package's central efficiency is the exact thing that disqualifies it there. I would rather write that sentence than a compatibility table that implies otherwise. Event buffering is the third piece, and it exists because of a failure that hides well. A vendor SDK's initialisation call returns before its destination plugins have attached, so events fired in that window are dropped with one console line nobody is reading at the time. That failure is intermittent. A clean test run proves nothing about a gap that opens on one load in many. So buffering removes the window instead of narrowing it, and the fix becomes something you verify by reading the code. What it cost was a dispatch path that has to stay boring. Every provider wants a special case, and each special case is another chance for the gate to be walked around by something that arrived looking like a convenience. One limit is worth naming. This package routes events; it does not tell you whether the numbers arriving on the other side are true. A key left blank in a deployed build, or an SDK that never took over its own global, produces a silence that looks exactly like a quiet week, and the only way to tell those two states apart is to go and confirm that an event genuinely landed on the other side. It runs in a browser and inside a Capacitor WebView. There is no iOS release behind this work, because there is no Apple Developer account here. MIT, on npm as unified-tracking, with the source at github.com/aoneahsan/unified-tracking. An analytics layer earns its place on the day you switch a provider off and nothing else breaks. npm Source on GitHub Documentation What it does, and what that costs to build One API for 16 analytics & error providers Zero runtime dependencies (CDN-loaded SDKs) Provider-free React hooks Consent gating + data minimization on dispatch Pre-init event buffering Revenue + screen + user-identity tracking Tree-shakeable ESM with .d.ts types Built with TypeScript React 19 CapacitorJS Vitest Worth knowing npm package analytics tracking react zero-dependency Who built Unified Tracking? Ahsan Mahmood wrote it, including the consent gate that sits in front of every provider. It is on npm as unified-tracking under MIT, with the source at github.com/aoneahsan/unified-tracking. The provider list runs long because his own projects report to several destinations at once rather than to a single vendor, so a layer treating sixteen of them as interchangeable was cheaper to build once than to rebuild per product. What stops an event reaching a provider the user did not consent to? The gate sits on the dispatch path, so nothing reaches a provider without passing through it. Consent is checked and policy-excluded properties are stripped in one place, before any vendor SDK is handed the event, rather than at each of the dozens of call sites that fire one. That distinction is the difference between a policy and a hope. A call site cannot skip the check by forgetting to ask, because there is no route to a provider that goes around the gate. Does Unified Tracking work on iPhone? It runs in any browser, an iPhone browser included, because the analytics half of this is web code rather than platform code. There is no iOS release behind this work: no Apple Developer account exists here, and nothing I build has shipped to the App Store. Inside a Capacitor shell the package runs in the WebView exactly as it runs on the open web, and Android is the shell that has actually been exercised. Nothing here should be read as a claim about an Apple build. What happens to events fired before the SDK finishes initialising? They are buffered and dispatched once initialisation genuinely completes. That matters more than it sounds, because a vendor SDK's init call returns before its destination plugins have attached, and events fired inside that window are dropped with a console line nobody reads. The failure is intermittent, so a clean test run proves nothing about it. Buffering removes the window rather than narrowing it, and that is the only version of the fix you can verify by reading code instead of by getting lucky. Where do the vendor SDKs come from? From each vendor's own CDN, fetched only when that provider is enabled, which is how the package holds zero runtime dependencies. The consequence deserves stating plainly: this design is right for a website and wrong for a browser extension. An extension may load no remote code, and a store treats a CDN script as grounds for rejection rather than as a review note, so an extension bundles its analytics or ships without any. If extensions are your target, this is the wrong package, and saying so is more useful than a compatibility table. ## ShieldPro Ultimate Ad Blocker — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.shieldpro Manifest V3 ad-blocking and privacy suite, ~499 blocking rules. Manifest V3 changed what a browser extension is allowed to do to a request. ShieldPro Ultimate Ad Blocker is built for that version of the platform rather than carried across into it, which turns out to be a rather different piece of work. Roughly 499 blocking rules ship with it. The tiers, the custom filter lists and the whitelist are the parts a person actually touches. An ad blocker is two things. A list of rules, and a set of ways for a person to disagree with that list. The second is where products in this category differ from each other, because the first is close to a commodity. A blocker is judged on two things, and only one of them is blocking. The other is what it does when it is wrong. Every rule set broad enough to be useful will eventually take out a checkout button, an embedded video or a login frame. The page gives no sign that an extension is the reason. It simply looks broken. Nothing tells the reader why. That is what the whitelist is for. One site, switched off, in less time than it takes to work out what went wrong. An escape hatch that is quick to reach is the difference between a tool somebody keeps and a tool they remove in irritation on a bad afternoon. Custom filter lists are the same idea pointed the other way. The shipped rules cover what is common to everyone. A custom list covers the handful of sites you personally use, which a general rule set has no reason to know anything about. The extension loads no remote code. Nothing is fetched from a content delivery network, no script arrives at runtime, and everything in the package is everything that runs. That rule has teeth. It is also the only honest position for a privacy tool, because an extension that can pull new behaviour after it is installed is an extension whose behaviour you have no way to check. It is TypeScript and React on the Chrome Extension API, with a progressive tier system over the feature set. What it cost is the list, and that cost never stops. A rule set is never finished. Sites change, an advertiser moves a domain, and a rule that worked in one quarter matches nothing in the next. A blocker is only as current as the last time somebody sat down with its rules, which means the maintenance is the actual product and the rule count is a snapshot of it. That is the job. The work of keeping one current is invisible from the outside, because a stale rule set and a fresh one look identical right up until somebody visits the site the stale one has quietly stopped covering. Rules are also the thing that cannot be tested the way code is tested. A rule set can be entirely valid and entirely out of date at the same moment, and no build will tell you which of the two it is looking at. There is a second cost, and it is the one a user feels. Blocking is a judgement made about somebody else's page, applied before anybody has looked at it and applied again on every page after that. Get it slightly wrong and a site is quietly broken with no explanation attached, which is why the escape hatch matters more than the number of rules does. The site is shieldpro.aoneahsan.com. The rule count is the easy number to print on a page. What actually decides whether an ad blocker stays installed is the day it is wrong about a site somebody needs, and how long that takes to put right. Open the live site What it does, and what that costs to build Ad blocking Progressive tiers Custom filters Whitelist support Built with TypeScript React Chrome Extension API Worth knowing extension ad-blocker chrome privacy Who built ShieldPro Ultimate Ad Blocker? Ahsan Mahmood built it, in TypeScript and React against the Chrome Extension API, for Manifest V3 rather than ported into it. The site is shieldpro.aoneahsan.com. The extension ships roughly 499 blocking rules, a progressive tier system, custom filter lists and whitelist support, which is a small enough surface that one person can hold all of it, and holding all of it is the only way a privacy tool gets to be honest about what it does. What happens when a rule breaks a site? You whitelist the site and the extension stops acting on it. That escape hatch matters more than the rule count does, because any filter list broad enough to be useful will eventually take out a checkout button, an embedded video or a login frame, and the page gives you no clue that an extension was the cause. The difference between a blocker somebody keeps and one they remove in irritation is how fast the exception can be made. Whitelisting is the feature that admits the tool can be wrong. Can I add rules of my own? Yes, through custom filter lists. The shipped rules cover what is common; a custom list covers the sites you personally use, which the general set has no reason to know anything about. It is the same idea as the whitelist pointed the other way: one is you telling the extension to stop, and the other is you telling it to start. Between them they are the reason a blocker can be adapted rather than merely tolerated. Does the extension fetch anything after it is installed? No code, no. An extension may load no remote code at all: no script from a content delivery network, no runtime fetch of executable content, nothing arriving after installation. That is a store rule with real consequences, and for a privacy tool it is also the only defensible position, because an extension able to pull new behaviour later is an extension whose behaviour nobody can verify. What you install is what runs on your machine. Does ShieldPro work on iPhone? No. It is a browser extension for a desktop browser, and phone browsers do not run desktop extensions. There is no iOS release behind any of my work either, because there is no Apple Developer account here and nothing I build has shipped to the App Store. Blocking on a phone is a genuinely different problem with a different mechanism behind it, and claiming this extension covers it would be the sort of thing a reader finds out in about a minute. ## PrepAI (Interview Preparation App) — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.interviewpai Interview-prep platform pairing ~900 hand-authored, browser-runnable coding drills across six language tracks with mock interviews, peer reviews, and a… A question dump is cheap to build. A table of 900 questions is an afternoon of work, and it teaches nobody anything. PrepAI is the expensive version of that idea. About 900 drills are hand-authored across six tracks: JavaScript, TypeScript, HTML, CSS, Node.js and Node microservices, and hand-authored is the word doing the work in that sentence. Each one carries a starter, a hidden test suite and three tiers of progressive hints. The hints are the part that took the time. A hint that gives the answer has removed the exercise, and a hint that says try again is not a hint at all. Three tiers exist because the useful help sits somewhere between those two, and exactly where depends on the drill. Then you press run. The code executes in a sandboxed Web Worker inside your own browser, through CodeMirror or Monaco, and the result comes back immediately. There is no execution backend behind any of it. Two editors ship rather than one. CodeMirror is the light one and Monaco is the editor most people already have muscle memory for, and asking a candidate to learn an unfamiliar editor while being timed is a strange thing to do to somebody. That constraint is visible in the syllabus. Every one of the six tracks runs in a browser. A drill in a language the browser cannot execute would need a server to execute it on, and there is no server here. The language list and the budget are the same decision. Around the drills sit the things that make preparation resemble the real thing. Mock interviews run three ways, with an AI interviewer, a peer or a mentor. Candidates exchange code reviews with each other, climb leaderboards and talk to a Gemini mentor running on Google's free tier. Peer review is the part candidates avoid and the part that earns its place. Reading somebody else's solution to a drill you have just failed is a different kind of correction from a hint, and it costs the platform nothing to arrange. Five roles share one product. Candidates, mentors, instructors, HR reviewers and admins each get a workspace scoped to what they are there to do, which is a considerably harder exercise than putting five menus behind a single login and hoping people ignore the four that are not theirs. Nothing underneath costs money to keep running. Firebase's free tier holds the data. The Gemini mentor runs on Google's free tier and uploads go through FilesHub, with no paid execution backend and no Stripe integration anywhere in the product. The plan set is Free, Pro at $8 and Ultimate at $14. Those are the only figures on this page. What it cost is the syllabus. Six tracks that all run in a browser is a real boundary, and somebody preparing for a Python or a Java interview is not served here. The alternative was a server willing to execute arbitrary code sent by strangers, which is a different product with a different bill and a much larger security surface. That trade was made deliberately. The second cost is authorship. About 900 drills, each with a hidden test suite and three tiers of hints, is not something anybody scrapes. It is in progress rather than finished. It is at interviewpai.aoneahsan.com, with a companion Chrome extension beside it. A hidden test suite is only ever as good as the person who wrote it, and there was one of those. Open the live site What it does, and what that costs to build ~900 browser-runnable coding drills (6 tracks) Sandboxed JS Web Worker execution (zero cost) Dual editor (CodeMirror + Monaco) AI/peer/mentor-led mock interviews Peer reviews, gamification & leaderboards Gemini AI mentor (free tier) Five roles in one product Built with React 19 TypeScript Vite 8 TanStack Router Radix Themes Firebase CapacitorJS CodeMirror Monaco Worth knowing education interview-prep coding career ai react Who built PrepAI? Ahsan Mahmood built it working alone, and it is at interviewpai.aoneahsan.com. The drills, the hidden test suites, the sandboxed execution, the five role-scoped workspaces, the Firestore rules and the companion Chrome extension are one person's work. It is React and TypeScript on Vite with the React Compiler, TanStack Router for navigation, Radix Themes on the interface, CodeMirror and Monaco as the two editors and Firebase underneath, packaged with Capacitor for mobile. What does PrepAI cost? There are three tiers: Free, Pro at $8 and Ultimate at $14. Those are the figures the record carries and this page adds nothing to them. The reason a free tier can be generous here is that the expensive part of an interview-preparation product usually is code execution, and that runs in the candidate's own browser rather than on hardware anybody is renting. Payment does not go through Stripe, and there is no paid execution backend anywhere in the design. What happens when I run a drill? It runs in your own browser, inside a sandboxed Web Worker, and returns a result immediately. There is no execution backend, no queue and no waiting for a container somewhere to start. The editor is CodeMirror or Monaco, so the drill is written and run in the same place. This is also why the tracks are what they are: every one of the six runs in a browser, and a drill in a language a browser cannot execute would need a server to execute it on. Why does PrepAI have five different roles? Because interview preparation involves five different jobs, and each of them needs a workspace scoped to it. Candidates work through drills and sit mock interviews. Mentors and instructors run sessions and review work. HR reviewers look at candidates rather than at code, and admins look after the platform. Putting five menus behind one login would have been easier and would have left every role reading past four sets of controls that were not theirs, which is the version most products ship. Does PrepAI work on iPhone? No, there is no iOS build. PrepAI is a web application, so an iPhone reaches it in a browser exactly as any laptop does, and since the drills execute inside the browser itself very little is lost by doing that. The packaged mobile build is Android through Capacitor, and the companion extension is Chrome. There is no Apple Developer account behind this work and nothing here has been submitted to the App Store. ## POS System — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.possystem Production-ready Point of Sale system built with Laravel 12 and Nova 5 Multi-location is where a point-of-sale system either works or quietly does not. One shop counts stock once. It can treat that number as a fact. Two shops turn the same number into a question about where, and every report written above it inherits the question. Sales figures, inventory levels and customer records all stop meaning anything on their own the moment there is more than one till. So location is in the data model rather than bolted on as a filter. That is the decision the rest of this follows from. Retrofitting a location column into a working system touches every query in it, and the ones it does not touch are the ones that go on quietly returning the wrong total for a fortnight. The system covers inventory, sales tracking, customer management, reporting and that multi-location layer underneath all four. It is built on Laravel with Nova as the administration layer, PHP and MySQL underneath, and Vue on the interface side. That combination is a deliberate answer to what this kind of software actually is. A point-of-sale system is not one clever feature. It is mostly plumbing. It is a large number of ordinary screens sitting over a strict relational schema, and most of the labour is in the ordinary screens rather than the clever part. Nova generates those directly from the models, which removes the repetitive half of the work and leaves the half worth thinking about. The relational database is the right shape here, and that is not always true. Stock, sales, customers and locations have real constraints between them. A sale cannot reference a product that does not exist, a stock movement cannot leave a location that never held it, and a database that enforces those is a database that stops a bad write instead of recording it. Software that discovers its own inconsistencies at reporting time has usually been inconsistent for weeks. Reporting sits above all of it. The point of a till is not the transaction. The transaction is the easy part, and it is over in a second. The value is in what the accumulated transactions tell somebody a month later about which lines move, which locations carry dead stock, and which customers come back, and none of that exists unless the schema underneath was strict enough to be trusted. What it cost is that there is nowhere to send you. The record carries no live URL, no listing and no repository. Software of this shape usually lives inside a business rather than on the open web, so that is less surprising than it would be for a consumer product. It is still an absence, and the page says so rather than pointing at something adjacent and hoping the distinction is not noticed. Two more things are worth naming, because both are easy to leave implied. The interface is Vue rather than a second full application, which keeps the till screen close to the models it is reading and avoids a whole parallel state layer for what is fundamentally a form over a database. And customer management is here because retail loyalty is not a feature you add later; it is a foreign key you either put in at the start or spend a migration adding once there are real rows depending on it. The record says completed. That means the work is finished, not that it is serving anybody today. Those are different claims and a portfolio is exactly the place where they get quietly merged, so this page keeps them apart. What it does, and what that costs to build Inventory management Sales tracking Customer management Reporting Multi-location support Built with Laravel 12 Nova 5 PHP MySQL Vue.js Worth knowing pos retail inventory laravel Who built the POS System? Ahsan Mahmood built it alone. The same person designed the schema, wrote the Laravel application behind it, configured the Nova administration layer and built the interface on top. It is recorded as completed rather than in progress, which on this catalogue means the work is done and not that it is running somewhere you can visit. There is no published address on the record, so this page names none, and that absence is the honest headline rather than a footnote. Where can I see it running? Nowhere public. The project record carries no live URL, no store listing and no repository, so there is nothing to open. A point-of-sale system is also the kind of software that lives inside a business rather than on the open web, so an absent public address is less surprising here than it would be on a consumer product. It is still an absence, and the page states it rather than pointing you at something adjacent. What does multi-location actually change? It changes what a stock number means. One shop can treat a count as a fact; two shops turn the same number into a question about where, and every report written above it has to carry the answer. Sales, inventory and customer records all inherit that, so the location is part of the data model rather than a filter added later. Retrofitting it afterwards is the kind of change that touches every query in a system. Why Laravel and Nova rather than a JavaScript backend? Because the administration side is most of the work. A point-of-sale system is a large number of ordinary screens over a strict relational schema, and Nova generates those screens directly from the models rather than requiring each to be built by hand. The relational database is the correct shape for stock, sales and customers, all of which have real constraints between them. Choosing the tool that removes the repetitive half is what makes a solo build of this size finishable. Does the POS System work on iPhone? No. It is a server-rendered web application, so an iPhone reaches it through a browser exactly as any other device does, and there is no iOS build to install. There is no Apple Developer account here and nothing I build has shipped to the App Store. A till running in a browser is the ordinary arrangement for software of this shape, so the absence of a native application is a smaller limit here than it would be elsewhere. ## MotorHub Pro — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.motorhub The ultimate multi-vehicle dealership platform for car dealers A dealership's stock is not a product catalogue. Every vehicle is one of one. It carries a mileage reading, a registration, a service history and a condition somebody assessed by walking around it, and the price moves with all four. Sell it and the row does not decrement. It disappears. None of that fits the shape of software written for a shop that sells forty of the same thing. MotorHub Pro is built for the other shape. It is a multi-vehicle dealership platform, which in practice means four surfaces that have to keep agreeing with each other: vehicle inventory, a customer relationship record, sales tracking, and a reporting dashboard sitting over all three. The dashboard is the part that decides whether the rest is worth maintaining. A dealer who cannot see what is ageing on the forecourt finds out at the point of a discount instead. So the reporting is not a decorative panel added at the end of the build. It is the reason anybody keeps the other three accurate, and a platform whose reports are an afterthought teaches its users that the data underneath them is optional. The customer record is the half that usually gets built badly. A buyer looking at a car is not a row in a contacts table. They are a conversation. They arrived asking about one vehicle, they will be shown two others, and the useful thing to know six weeks later is which of the three they kept coming back to. So the customer side and the sales tracking are the same subject seen twice rather than two features that happen to sit in one menu, and the reporting reads across both. The stack is React and TypeScript with Tailwind over the top, Firebase underneath, and Capacitor for the mobile build. That last choice matters more here than in most products, because the person entering a vehicle is usually standing next to it. Registration, mileage, photographs and the walk-around condition are recorded where the car is rather than where the desk is, and a system that assumes a desk gets a forecourt's worth of data entered twice. The Firebase choice carries a constraint worth naming. A free tier is a ceiling rather than the absence of one, so every list read carries an explicit limit and the database does the filtering and the counting rather than the browser. Four hundred vehicles is not a large collection by any measure, and it is comfortably large enough to make an unbounded query into a bill somebody has to explain. What it cost is that this one is not finished, and there is nowhere to send you. The project record carries no live address, no store listing and no repository. Its status is in progress. Both of those facts belong on the page rather than in a footnote, because the alternative is a portfolio entry that reads like a product and resolves to nothing when a reader goes looking. That is a real limit and it is worth stating plainly rather than dressing up. A dealership platform is a category with capable incumbents in it, and an unfinished one has no argument to make against them yet. What it has is a data model built for vehicles instead of adapted to them, which is the part that is hard to retrofit and the part worth getting right before anything else is built on top. The rest is written down. It is not yet built. What it does, and what that costs to build Vehicle inventory management Customer CRM Sales tracking Reporting dashboard Built with React TypeScript Firebase TailwindCSS CapacitorJS Worth knowing automotive dealership crm inventory Who built MotorHub Pro? Ahsan Mahmood built it, working alone. The same person modelled the vehicle record, wrote the customer side, built the reporting dashboard over both and configured the mobile build. There is no team behind that name and no agency. What the record does not contain is a link, because MotorHub Pro has no published address, and this page will not point at something a reader cannot open. The honest position is that it exists, it is unfinished, and there is nowhere to send you yet. Can I try MotorHub Pro? Not today. The project record carries no live URL, no store listing and no repository, so there is nothing to open and nothing to install. Its status is in progress rather than finished, and a dealership platform half way through is not a thing to hand a dealer. When there is an address worth visiting it will be on the record first and on this page second, which is the order that keeps a portfolio honest rather than aspirational. What makes a dealership different from an ordinary shop? Every vehicle is one of one. A shop sells forty identical items and decrements a number; a forecourt sells a specific car with its own mileage, registration, service history and condition, and when it goes the row does not decrease, it disappears. Pricing moves with all four of those facts rather than sitting in a column. That difference is why general inventory software fits a dealership badly, and it is the reason this exists at all. Does MotorHub Pro work on iPhone? No, there is no iOS build. The interface is a web application and the packaged build is Android through Capacitor, because there is no Apple Developer account here and nothing I build has shipped to the App Store. A phone browser will open the web side when there is a web side to open. Until then the platform question is the second question, and the first one is that there is no published address at all. What does it cost to use? The record does not say, so neither does this page. There is no pricing statement, no tier and no trial recorded anywhere in the project's own data, and inventing one for a portfolio page would put a number in front of a reader that nobody has committed to. A price that appears here before it appears in the product is a promise made by the wrong document. When there is one, it will come from the product. ## Anonymous Chat AI (AChat) — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.achat No-signup chat. Lock a room with a password for real in-browser encryption. Open the page, copy the URL, send it. That is the whole setup. AChat asks for no email, no account and no install. You pick a chat identifier or let the page generate one, and whoever opens the link you send them is in the room with you. That is the easy half. The difficult half is the word private, which most chat products use to mean that somebody somewhere has written a policy about it. Here it means a key that never leaves your browser. Add a password and the room changes. The password derives a key with PBKDF2 and messages are sealed with AES-GCM through the Web Crypto API. The server holds ciphertext it has no way to open. It also means there is no recovery. Lose the password and the conversation is gone, because any route back into it would be a copy of the key held by somebody who is not in the conversation. That trade is the feature working. The room dies with it. Rooms expire on their own as well. A Firestore time-to-live policy deletes the whole thing after 10 days, which is deletion on a timer rather than an archive nobody reads. Very little is left to protect by the eleventh day. Under a deliberately plain surface there is more product than the first screen suggests. The composer is TipTap, carrying slash commands, mentions, emoji, reactions, quote-reply, voice memos and code highlighting. Files ride through FilesHub at up to 10 MB each. They go through a multi-file upload queue with retry, so one bad connection loses one file rather than the batch. Search across a room is fuzzy, because nobody recalls the exact words they used two hours ago. There is a lightbox for images and an embeddable iframe widget for putting a room inside another site, which turns a support conversation into a few lines of markup on somebody else's page. A PWA service worker is registered, and the whole interface sits behind a Radix theme customiser with accessibility modes. Those modes are not decoration on a product whose entire job is a conversation somebody needs to read. None of that is visible on the first screen, and that is intentional. A product whose selling point is that it asks you for nothing should not open by showing you everything it can do. The constraint behind all of it is Firebase's free Spark plan. There is no paid backend anywhere in the room, which is why a room can cost nothing to leave running and nobody has to justify keeping it alive. Zero infrastructure cost is what lets one person keep a long list of things running at the same time. That constraint also shapes the cryptography. A server that cannot read a message is a server that needs no expensive protection around what it stores, and the cheapest data to secure is data you never held in the clear. What it cost is retrieval. Everything convenient about a normal chat product comes from the server being able to read the messages: search across the whole history, account recovery, moderation, an export somebody can be sent months later, and a password-protected AChat room gives every one of them up, because its plaintext exists only in the browsers of the people in it. That is the bill. It is also in progress rather than finished, which belongs on the page and not in a footnote. It is at achat.aoneahsan.com, and the Android build is on Google Play. There is no way to get a room back once the password is gone, and that is the feature rather than a defect in it. Open the live site Google Play What it does, and what that costs to build No-signup, share-a-link chat Optional in-browser E2E encryption (Web Crypto) 10-day auto-deletion via Firestore TTL TipTap composer (slash commands, @mentions, reactions) Multi-file upload queue + image lightbox + voice memos Embeddable iframe chat widget Full Radix theme customizer + accessibility modes Built with React 19 TypeScript Vite 8 TanStack Router TanStack Query Zustand TipTap Firebase CapacitorJS Worth knowing chat privacy encryption ephemeral react firebase pwa Who built Anonymous Chat AI? Ahsan Mahmood built it, working alone, and it is at achat.aoneahsan.com with an Android build on Google Play. The room model, the browser-side cryptography, the composer, the upload queue and the Android packaging are one person's work, which is why the encrypted path and the file path agree about what a room is. It runs on React and TypeScript with TanStack Router and Query, Zustand for state, TipTap for the composer and Firebase underneath, packaged for Android with Capacitor from the same codebase the web version is built from. What happens if I forget a room password? The conversation is gone, and there is no way to recover it. The password is what derives the key, the key never leaves the browser, and the server holds ciphertext it has no means of opening, so there is nobody to appeal to. A recovery path would be a second copy of that key held by someone other than the two people talking, which is the exact thing an encrypted room exists to avoid. This is the cost of the feature rather than a gap in it, and it is worth understanding before you rely on a room for something you need later. How long does a chat room last? Ten days, after which a Firestore time-to-live policy deletes the whole room. That is deletion on a timer rather than an archive nobody ever reads, and it applies to the room whether or not anybody remembers it exists. The practical effect is that the product has almost no long-term storage to secure, because there is very little left to secure by the eleventh day. If a conversation matters beyond that window, take what you need out of it while it is still there. Does it work without an account? Yes, and that is the design rather than a trial mode. There is no email address to give, no password to invent for the service itself, and no application to install before two people can talk. You pick a chat identifier or let the page generate one, share the resulting link, and the room exists. An optional room password is separate from an account: it encrypts the conversation, it does not identify you, and nothing about it creates a profile that outlives the room. Does Anonymous Chat AI work on iPhone? No, there is no iOS build. It runs in a browser and ships an installed application on Android only, because there is no Apple Developer account behind this work and nothing here has gone to the App Store. An iPhone can open the web version like any other device, and since the whole product is browser cryptography plus a shared link, the web version is close to the whole thing. The installable Android build comes out of the same codebase through Capacitor. ## ChatExport — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.chatexport Free ChatGPT App (OpenAI Apps SDK / MCP) that exports ChatGPT conversations to Markdown, JSON, PDF, HTML, or text from inside chat, with zero content… ChatExport runs inside ChatGPT. There is no site to open first and no extension to install. Getting a long conversation out of a chat product usually means selecting it by hand and pasting it somewhere that loses the structure on the way. The alternatives are an extension reading the page over your shoulder or a service you upload the whole thing to, which is a stranger holding a copy either way. None of those is what happens here. You ask for an export and pick one of five formats, and a download card appears in the conversation you were already having. Markdown, JSON, PDF, HTML or plain text. Those five are not five versions of one thing. Markdown goes into a notebook, JSON goes into another program, and PDF goes to a person who is never going to open a text file. The mechanism is the OpenAI Apps SDK over the Model Context Protocol, which is what lets a third-party tool put its own interface inside the chat instead of beside it. The whole service is one Cloudflare Worker. A Hono application handles the protocol traffic, and the interface is a single-file React widget embedded as a string in the worker's response. One worker, one response, one file. Workers-pure means no Node APIs anywhere in it. That is a real constraint and not a preference. Anything that would ordinarily reach for a Node module has to be done with web platform APIs instead, which is why the encryption helper is written in-house rather than installed. The interesting part is what is never stored. There is no database of conversation content. An export is assembled in memory and returned as a base64 file inside the same round-trip that asked for it, so the content exists in the worker for the length of one request and then stops existing. Two things do persist. Opt-in metadata, which is the format, the filename, the size and the timestamp, and never any content. Google Drive OAuth tokens, encrypted at rest with AES-256-GCM through the in-house helper. Those are the only two, and both are named rather than summarised. A privacy claim is worth exactly as much as the list of exceptions printed under it. A separate marketing site handles everything a tool living inside a chat cannot do for itself: discovery, search, a blog, an FAQ and the legal pages. It is React on Firebase hosting, and it is what makes the product findable outside the chat window. All of it runs on free tiers. What it cost is the surface. The interface is a widget the host renders and the interaction model belongs to the host, so a product built this way inherits every decision made about that host by people who have never heard of it. Nothing here can change how the conversation around it behaves. That is the price of being where the work already happens. The second cost is narrower and sits in the code. A single-file widget embedded as a string is not a shape that grows comfortably, and a Workers-pure rule closes off a large part of the npm ecosystem before the first line is written. Both were chosen rather than discovered halfway through. It is at chatgpteai.aoneahsan.com. Nothing you export is retained, which is an easy sentence to write and is only true here because the design has nowhere to put it. Open the live site What it does, and what that costs to build Export to Markdown, JSON, PDF, HTML, plain text Runs inside ChatGPT via OpenAI Apps SDK / MCP Zero conversation-content retention Single-file React widget embedded in the worker AES-256-GCM-encrypted Google Drive tokens Workers-pure architecture (no Node APIs) Firebase-hosted marketing site with AEO Built with TypeScript Cloudflare Workers Hono React 19 Radix Themes Vite 8 Firebase Worth knowing chatgpt mcp cloudflare-workers privacy export ai Who built ChatExport? Ahsan Mahmood built it alone, and it is described and documented at chatgpteai.aoneahsan.com. The protocol handler, the widget, the encryption helper and the marketing site are one person's work. The service itself is TypeScript on a single Cloudflare Worker with Hono handling the requests, and the site beside it is React with Radix Themes and Tailwind on Firebase hosting. Two very different runtimes, one person deciding what each of them is allowed to hold. Does ChatExport keep a copy of my conversation? No. There is no database of conversation content anywhere in the design. An export is assembled in memory and handed back as a base64 file inside the same JSON-RPC round-trip that requested it, so the content is present for the length of one request and then is not. Two things do persist, and both are named: opt-in metadata, which is the format, the filename, the size and the timestamp but never any content, and Google Drive OAuth tokens, encrypted at rest with AES-256-GCM through an in-house WebCrypto helper. Which export formats does ChatExport produce? Five: Markdown, JSON, PDF, HTML and plain text. They are not five versions of the same thing. Markdown goes into a notebook, JSON goes into another program, PDF goes to somebody who is never going to open a text file, and HTML and plain text cover the two ends of that range. You pick one when you ask for the export, and the file comes back as a download card in the chat rather than as a link to somewhere else. Where does ChatExport actually run? On one Cloudflare Worker, with nothing else behind it. A Hono application handles the Model Context Protocol traffic, and the interface is a single-file React widget embedded as a string in the worker's own response, so a request and its user interface arrive together. The architecture is Workers-pure, meaning no Node APIs are used anywhere in it, which is a genuine constraint rather than a preference: anything that would ordinarily reach for a Node module has to be done with web platform APIs instead. The whole thing sits on free tiers. Does ChatExport work on iPhone? There is no iOS application, and there is no installed application on any platform. ChatExport is not something you download: it runs inside ChatGPT through the OpenAI Apps SDK, and the only other surface it has is an ordinary website. No page here claims an App Store release, because there is no Apple Developer account behind this work and nothing built here has ever been submitted to one. Where the host application itself will run is a question for the host rather than something this page can answer. ## ImtehanHub — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.imtehanhub Bilingual Urdu and English exam preparation, Class 5 to FA/FSc. The row calls the worst part of exam preparation by its own name. Test section hell. The test section at the back of a textbook is finite, and the questions in it repeat. Work through it twice and you have stopped practising, because what you are now remembering is which answer sat in which position rather than why it was the right one. ImtehanHub generates rather than lists. Practice tests are chapter-wise and non-repeating, covering multiple choice as well as short and long questions. The supply does not run out. Then the part that matters more than the supply. Every answer links back to the exact page of the textbook it came from. A student can check the platform instead of trusting it. Checking is the feature. The whole thing is bilingual end to end, Urdu and English, with right-to-left layout on the Urdu side. Right-to-left is the honest test of a translation layer, because it moves the layout rather than only the words. It covers Class 5 through 2nd Year, FA and FSc. A question bank one person writes is a question bank that stays small, so there is a contribution system underneath the test engine and the students are the contributors. Submissions sit behind CNIC verification. Peer voting surfaces the good questions, flagging catches the bad ones, moderator queues hold everything in between, and a strikes and bans system exists for accounts that keep getting it wrong. There are 25 achievements over the top. Moderation is not a feature that ships and then sits quietly. Somebody reads those queues, and the design assumes that somebody is one person. That identity gate is friction on purpose. Asking for a national identity number before somebody may contribute a question will lose contributions, and it is still the cheapest thing that keeps a public question bank usable once the internet finds it. What happens to that number afterwards is not something this page describes, because the record these pages are written from does not say. Growth is engineered rather than hoped for. Every sign-up carries UTM and source attribution, so where a student arrived from is a fact rather than a guess, and 3 successful referrals upgrade an account to Pro automatically. The constraint under all of it is a strict free-tier Firebase architecture with no Cloud Functions. A free tier is a ceiling. It is not the absence of one. I learned that here. The platform went down because a free-tier read quota ran out, and it stayed down for every real user until the quota reset. Nothing was broken. The queries were reading more than they needed, and a limit nobody had written was reached by a number nobody was watching. Every list query I have written since carries an explicit limit, and the database does the filtering and counting rather than the browser. Per-route static HTML and JSON-LD are emitted for crawlers that do not run JavaScript. A study platform nobody can find is a study platform nobody uses. What it cost is advertising. It is free for students because AdSense pays for the web and AdMob pays for mobile, and Pro removes them. A student who cannot pay sees advertising, and a student who can, or who brings three friends, does not. That is a trade rather than a gift, and it reads better stated here than discovered later by a student who had assumed the platform was funded by something else. It is also in progress rather than finished. It is at imtehanhub.aoneahsan.com, with documentation at imtehanhub-docs.aoneahsan.com. Bilingual means every question, every screen and every mistake has to be right twice. Open the live site Documentation What it does, and what that costs to build Non-repeating chapter-wise test engine (MCQ/short/long) Book-page reference on every question Bilingual Urdu (RTL) + English Community module (CNIC verification, voting, moderation) Referral-driven auto-Pro + UTM attribution Per-route static HTML + JSON-LD for AI crawlers AdMob + AdSense (ad-free for Pro) Built with React 19 TypeScript Vite 8 TailwindCSS Radix UI TanStack Query Firebase CapacitorJS Worth knowing education exam-prep pakistan bilingual react firebase Who built ImtehanHub? Ahsan Mahmood built it on his own, and it runs at imtehanhub.aoneahsan.com with documentation at imtehanhub-docs.aoneahsan.com. The test engine, the bilingual layer with its right-to-left Urdu side, the contribution and moderation system, the Firestore rules and the crawler-facing static pages are one person's work. It is React and TypeScript on Vite, with Tailwind and Radix UI on the interface, TanStack Query over the data and Firebase underneath, packaged with Capacitor for mobile. Is ImtehanHub free for students? Yes, and it is paid for by advertising rather than by the student. AdSense funds the web side and AdMob funds mobile, with a Pro tier that removes the advertising for anyone who would rather not see it. There is also a route to Pro that costs nothing: 3 successful referrals upgrade an account automatically. The arrangement is deliberate, because a paywall in front of exam practice would exclude precisely the students the platform exists for, and someone still has to pay for the platform. Why does every answer point back to a textbook page? So a student can check it instead of believing it. A practice platform that gets an answer wrong and cannot be audited is worse than no platform, because a confidently wrong answer gets memorised along with the right ones. Tying each answer to the exact page of the book it came from turns the platform into an index over material the student already trusts, rather than a second source they now have to reconcile with the first. It also makes a bad contributed question visible quickly, which matters once the question bank accepts submissions. Who is allowed to add questions? Verified contributors, behind a CNIC gate. Submissions are checked by peer voting, flagged by other users when something is wrong, held in moderator queues, and backed by a strikes and bans system for accounts that keep submitting badly. That identity gate is real friction and it will cost the platform contributions, which is the trade being made on purpose: a public question bank with no barrier to entry fills up with noise faster than moderators can clear it. What happens to that identity number afterwards is not described here, because the record these pages are written from does not say. Does ImtehanHub work on iPhone? No, there is no iOS build and there will not be one from here, because there is no Apple Developer account behind this work. ImtehanHub is a web application any phone can open in a browser, and its codebase is packaged with Capacitor for mobile with AdMob on that side. The only link this page will point you at is the web one at imtehanhub.aoneahsan.com, because that is the link the record carries. Nothing here has ever been submitted to the App Store. ## Legal Eagle Law Firm — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.legaleaglelaws.app Legal-tech SaaS: an AI-prerendered marketing site, a ~15.7k-row advocate directory on Neon Postgres, Google Meet consultation booking, a free-first AI… Most law firm websites are a brochure. There is a phone number on it and not much else. This one started as that. It was PHP, and the job was to rebuild it. What it became is a platform, and the parts that make it one are the parts that would not fit in a brochure: a searchable directory, a booking flow that writes into a real calendar, a chatbot that answers before it costs anything, and a billing layer. Start with the directory, because it is the piece that forced a real decision. It holds roughly 15,700 advocate records. People need to search them by name. That is a text-search problem rather than a storage problem, and the two want different databases. So writes stay in Firestore alongside the rest of the application's data. The directory is served from Postgres, which handles text search at that size without complaint. That split is not free. It buys a search that returns quickly and it costs a boundary that has to stay in step. Keeping everything in one database would have been simpler to reason about and noticeably slower to use. For a directory whose whole point is being searched, slow is the same as broken. The public site is prerendered rather than rendered in the browser. Thirty-nine pages are emitted as static HTML at build time, so a crawler that does not execute JavaScript reads the same content a visitor does. For a firm whose enquiries arrive through search, that is not an optimisation. It is the difference between existing and not. The chatbot is designed around cost. It works through tiers in order: a cached response first, then curated knowledge written in advance, then free model providers, and only then anything paid. Questions arriving at a law firm's site repeat heavily, so the early tiers carry most of the load. Treating paid inference as the last resort rather than the mechanism is what makes leaving it switched on affordable. One decision runs underneath several of these, and it is worth naming once. Nothing here assumes a server. The prerendering happens at build time and the application talks to its databases directly, with the secrets held at the edge. A platform serving a working practice should not depend on a machine that needs watching, because the person who would notice it had stopped is the person least able to do anything about it. Booking runs through the firm's own calendar account. A consultation is scheduled into Google Calendar with a Meet link, on the firm's account rather than an intermediary's, which means the appointment exists where the people who keep the diary already look. The billing layer is multi-currency, and that detail is about geography. It carries local Pakistani payment gateways alongside an international processor, because a platform serving people in Pakistan that only accepts international cards is a platform that works for the minority of its users. Eight Cloudflare Workers hold every server-side secret. Payment credentials, calendar access, model keys: none of them can reach a browser bundle, and the platform runs with no Firebase Functions and no Firebase Storage anywhere in it. The secrets sit at the edge. The rest stays client-side, and no always-on backend needs maintaining. What it cost is that the interesting decisions are invisible to the people it serves. A visitor sees a website. The directory split, the tiering, the prerendering and the secret boundary are all things that show up as a page loading quickly, an answer appearing, and a bill that stays small. That is the correct outcome and it makes for an odd portfolio entry, because the work is most successful exactly where it is least visible. Open the live site What it does, and what that costs to build AI-prerendered marketing site (39 pages) ~15.7k-row advocate directory on Neon Postgres Google Calendar + Meet consultation booking Free-first six-tier AI chatbot Multi-currency billing (PayFast/PayPro/XPay + Stripe) 8 Cloudflare Workers holding every secret Admin CMS with Google Drive asset pipeline Built with React 19 TypeScript Vite 8 Radix UI Themes TailwindCSS Firebase Neon Postgres Cloudflare Workers CapacitorJS Worth knowing legal-tech law-firm saas ai-chatbot neon-postgres cloudflare-workers Who built Legal Eagle Law Firm? Ahsan Mahmood built it, and it serves a law firm in Lahore at legaleaglelaws.com. It began as a rebuild of the firm's existing PHP website and grew into a legal-technology platform on one React codebase. The work described here is work that was delivered. What the site does next is the firm's decision rather than mine, and this page describes what was built rather than what happens to it from here. Why is a directory of advocates on a different database from everything else? Because text search over roughly 15,700 rows is a different problem from storing them. Writes stay in Firestore where the rest of the application's data lives, and the directory is served from Postgres, which is built for searching text at that size. Splitting a system by what each half is good at costs a synchronisation boundary and buys a search that returns quickly. Keeping it all in one place would have been simpler and slower. How does the chatbot avoid costing money on every question? It tries the cheap answers first, in order. A cached response, then curated knowledge written in advance, then free model providers, and only after all of those a paid inference call. Most questions arriving at a law firm's website are asked repeatedly, so the earlier tiers answer a large share of them. The design treats paid inference as the last resort rather than the mechanism, which is what makes a chatbot affordable to leave running. Where do the server-side secrets live? In Cloudflare Workers, eight of them, each holding credentials that must never reach a browser bundle. Payment gateways, calendar access and model providers all need keys that a client cannot be trusted with, and the whole platform runs without Firebase Functions or Firebase Storage. That combination is unusual and deliberate: the secrets sit at the edge and the rest stays client-side, so there is no always-on backend to maintain. Does it run on Apple devices? The site is a web application and opens in any browser, an iPhone's included. The codebase is packaged with Capacitor for Android as well, and there is no Apple release from me, because no Apple Developer account sits behind my own work and nothing I own has shipped to the App Store. For a firm whose visitors arrive through search, the web is the surface that matters anyway. ## pdf-data-extractor — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.pdfdataextractor Python CLI and library that lifts everything out of any PDF (text, tables, images, forms, signatures, metadata, and 23 regex-matched data types) into JSON,… Kill a 50,000-page extraction at hour six and most tools begin again at page one. That is the moment a PDF job stops being a script and becomes infrastructure. Long runs get interrupted. A laptop sleeps, a container is recycled, somebody presses control-C to see how far it has got, and the cost of that is counted in hours. So pdf-data-extractor resumes. Re-run the same command and it picks up at the first unfinished batch, using an on-disk cache to know which batches those were. Nothing about that is clever. It is the difference between a tool you can point at a real archive and one that works in a demonstration. The second problem is the output. A large extraction can produce a JSON file nobody is able to open, which is a peculiar way to fail: the work succeeded and the result is unusable. So output streams into size-bounded part files with an index manifest describing them, in JSON, JSONL or CSV. Then the extraction itself, which is wider than this category usually is. Most PDF tools do one job. Text, or tables, or images. This one takes text, tables, images, links, form fields, signatures and metadata, then adds pattern matching as a first-class output rather than as something you script afterwards. Form fields and signatures are on that list for a reason. A completed PDF form is a small database somebody printed, and an extractor that reads only the visible text hands you the questions without the answers. That is what changes the tool from a converter into an extractor. 23 built-in patterns pull emails, phone numbers, national identifiers, IBANs and invoice numbers out of a document in a single command, which turns a contract into structured data instead of into a text file somebody still has to read. Pattern matching invites a specific failure, and it is the reason this part needs care. An expression matching something card-shaped will match a great many things that are not cards. So sensitive matches are checked rather than trusted: card numbers against the Luhn algorithm, IBANs against mod-97. A match that fails its own checksum never reaches the output. Three engines cover three different jobs. A fast default, a fuller pass and a semantic pipeline built on IBM Docling for documents where the layout carries the meaning. Tesseract handles scanned pages with no text layer at all. It installs nothing, running through uvx or pipx, and it stands on PyMuPDF, pypdf and pdfplumber rather than on a parser I wrote myself. That last choice was deliberate. PDF has thirty years of edge cases in it, and a hand-rolled parser is a decade of other people's bug reports you have volunteered to repeat. Four console commands and a typed Python API sit on top. What it cost is roughly 3,189 lines of Python across versions 3.10 to 3.13. There is a second cost, and it is not technical. Extracting personal data is easy and deciding what may be done with it is not. This tool will pull every national identifier in a folder in one command, and what happens to that output afterwards belongs to whoever ran it rather than to anything the software can decide. MIT licensed. The number worth noticing here is not 23 patterns. It is the run that was interrupted at hour six and did not have to start over. What it does, and what that costs to build Extract text, tables, images, links, forms, signatures 23 built-in regex patterns (email, phone, IBAN, IDs) Checksum-validated matches (Luhn, mod-97) Size-bounded split-file output + index manifest Resumable batched extraction with on-disk cache Three engines (fast / full / docling) + OCR Zero-install via uvx/pipx + typed Python API Built with Python PyMuPDF pypdf pdfplumber Tesseract IBM Docling Worth knowing python pdf data-extraction cli ocr library Who built pdf-data-extractor? Ahsan Mahmood built it, and Python is where the mature PDF and OCR libraries actually live, and the tool follows them rather than dragging the work into a language that would have to reimplement them, so this one exists because the problem sat in the wrong language for the rest of the toolkit, and moving the language was cheaper than moving the problem. It is MIT licensed. There is no public link on this page: neither candidate address resolves today, and pointing a reader at something that answers with a 404 would be worse than saying nothing at all. What happens if a long extraction is interrupted? Re-run the same command and it resumes at the first unfinished batch. An on-disk cache records which batches completed, so a 50,000-page job killed at hour six does not begin again at page one. That single property separates a tool you can point at a real archive from one that only works in a demonstration, because long runs get interrupted as a matter of routine by a sleeping laptop, a recycled container, or somebody pressing control-C to see how far it has got. Does pdf-data-extractor work on iPhone? No. It is a Python command-line tool and library, so it runs on a machine with Python 3.10 to 3.13, which means a laptop, a server or a container rather than a phone. There is no iOS release behind any of my work, because there is no Apple Developer account here and nothing I build has shipped to the App Store. If you need extraction from a phone, the honest answer is that this tool is the wrong shape for the job and no mobile version of it exists. What stops a false match reaching the output? Checksums. A regular expression that matches something card-shaped will also match numbers that are not cards, so sensitive matches are validated rather than trusted: card numbers against the Luhn algorithm and IBANs against mod-97. A match that fails its own checksum never reaches the output. That does not make every match correct, and it removes the class of error that makes a pattern-matching extractor useless in practice, which is a page of confident nonsense somebody then has to check by hand. What does it output for a very large document set? Size-bounded part files with an index manifest describing them, in JSON, JSONL or CSV. A single multi-gigabyte JSON file is a strange way for a job to fail, because the extraction succeeded and the result is still unusable by anything that has to load it whole. Splitting the output into bounded parts keeps it readable by ordinary tools. Three engines feed that output: a fast default, a fuller pass and a semantic pipeline built on IBM Docling for documents where the layout carries meaning. ## SlackVault — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.slackvault Exports and archives Slack history so a workspace outgrowing its retention limit keeps its own record. A free workspace forgets things. It does it quietly, which is the part that matters. Conversations older than the retention window stop being reachable. So do the decisions inside them, and the files somebody shared, and the reasoning behind a choice nobody wrote down anywhere else. Nothing is deleted in a way anyone notices. It simply stops being findable, and the first sign is somebody asking why a thing was done and nobody being able to say. SlackVault keeps the record without becoming its keeper. That second half is the whole design. The obvious way to build this is a service that connects to a workspace, reads it, and stores the messages somewhere convenient, which works and makes the operator a custodian of other people's conversations. This does the opposite: the archive is written into the user's own free-tier storage, so there is no vendor server in the path that could hold anything. Privacy enforced by architecture is a different claim from privacy promised in copy. One of them is checkable. If there is no place in the design where a third party's server touches the data, the promise does not depend on anybody's good behaviour, including mine. Getting there took four surfaces, and each holds exactly one thing. The browser extension owns the workspace session, because that is where the session already lives and giving a server credentials of its own would create the custodian this avoids. A Cloudflare Worker performs the OAuth token exchange, because that requires a secret which must never reach a browser bundle. The web application reads and searches the archive. A manifest declares the scope of the whole arrangement. Splitting a small product four ways is more work than not doing it. The payoff is that each piece is easy to reason about and none of them can quietly acquire a responsibility it should not have. The Worker touches credentials and never messages. The extension touches messages and never holds a long-lived secret. The application reads storage that belongs to the person reading it. Syncing is incremental. It runs on a fifteen-minute cadence, and a full re-read of a workspace is expensive and mostly redundant, since almost nothing older has changed since the last pass. Fetching what is new keeps the operation inside a free tier, which is the constraint that decides whether a tool like this can run indefinitely or only until somebody notices the bill. The archive is meant to be used rather than stored. Threaded browsing and fuzzy search exist because an archive nobody can search is a backup rather than a record. It exports to JSON and imports from a native workspace export as well, so the data can leave the way it came in. There is a rule this design inherits and it is worth stating, because it constrains the extension half more than anything else. An extension loads no remote code. Nothing arrives after installation, which means the behaviour in the package is the behaviour for good. On a tool holding a workspace session that is not a limitation to work around. It is the reason the arrangement is trustworthy at all. What it cost is setup. The user has to bring their own storage. That is a real step and a genuine barrier, and it is the direct price of the property the whole thing exists for. A version that stored everything centrally would be easier to start using, and it would also be precisely the product this one was written in order to avoid becoming. Open the live site Documentation What it does, and what that costs to build Incremental Slack backup every 15 minutes Cloudflare Worker isolates OAuth secret exchange Archive experience (threaded browse, fuzzy search) JSON export + Slack-export import Data stored in user's own Firebase/FilesHub MV3 extension + Android/iOS via Capacitor Zero-cost operations across all four surfaces Built with React 19 TypeScript Vite 8 Radix Themes TanStack Router Firebase CapacitorJS WXT Cloudflare Workers Worth knowing slack backup archive extension cloudflare-workers privacy Who built SlackVault? Ahsan Mahmood built it, and it is at slackvault.aoneahsan.com with documentation on the same host. One person wrote the browser extension that holds the workspace session, the Cloudflare Worker that performs the token exchange, the web application that reads the archive, and the manifest that declares what the whole thing is allowed to touch. Four surfaces is an unusual amount of separation for one developer, and the separation is the design rather than an accident of how it grew. Where does my Slack data actually go? Into your own storage, not mine. The archive is written to the user's own free-tier database and file storage, which means SlackVault never holds anybody's messages and never becomes a custodian of them. That is a structural property rather than a policy: there is no vendor server in the path that could hold the data even if somebody wanted it to. A privacy claim backed by architecture is checkable in a way that a privacy claim in a paragraph is not. Why does the extension exist rather than doing it all server-side? Because the extension is the only surface that has the user's workspace session. A server would need credentials of its own to read a workspace, which puts a third party between the user and their messages and creates exactly the custodian the design avoids. Keeping the session in the browser where it already exists means nothing new has to be trusted with it. The extension syncs incrementally rather than in one pass, on a fifteen-minute cadence. What is the Cloudflare Worker for, if the data does not pass through it? It performs the token exchange, and nothing else. An OAuth exchange requires a secret that must never appear in a browser bundle, because anything shipped to a browser is readable by whoever received it. So the secret lives at the edge, the exchange happens there, and the resulting token goes to the surface that needs it. The Worker touches credentials and never messages, which is a much smaller thing to trust. Does SlackVault work on iPhone? The archive application opens in any browser, an iPhone's included, and the codebase is packaged for Android as well. There is no Apple release, because no Apple Developer account sits behind this work and nothing I build has shipped to the App Store. The extension half is a desktop browser surface in any case, since that is where a workspace session lives, so the phone is where you read an archive rather than where it is collected. ## SnapContact — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.snapcontact Contact-intelligence suite that turns every chat, business card, or email signature into a structured, scored, searchable contact across web, Android, and… There is a gap between talking to somebody and actually having them saved. SnapContact is built inside that gap and nowhere else. Capture comes first. A person can arrive by hand, from a photograph of a business card, out of a chat or profile export, off a QR code, or in one tap from the browser extension while their page is open in front of you. What comes out the other side is a structured record: name, phone, email, source, context, timezone and social links. Source and context are the two that matter. A phone number with nothing attached to it is a phone number you will not act on in three weeks. Recording how you met somebody is most of the difference between a contact list and a list of strings. The card photograph deserves its own sentence. Recognition runs in the browser, on the device, so the image is read where it was taken rather than uploaded somewhere to be processed. That has a cost, because a model inside a browser tab has less to work with than a server that can throw hardware at the same picture. The photograph stays where it was taken. Then the workspace. Contacts are organised, searched with fuzzy matching across everything at once, scored from activity that actually happened, given reminders, and put through automation workflows. Duplicate detection sits underneath all of it. Five routes into one list is five ways to save the same person twice, and a contact tool that quietly does that is a contact tool people stop trusting after the second time they notice. Sending is where the cost decisions become visible. Outgoing SMS, WhatsApp, Telegram and email go device-native by default, which means the phone sends them and the application never becomes a sending service in the middle. Bulk sending is the exception. Anyone who needs it brings their own Twilio, SendGrid or Mailgun key, and those keys are encrypted on the device and never uploaded. Bring your own key is what keeps a per-message cost out of the product, and it is the more honest arrangement, because a key that never leaves your handset is a key nobody else is able to spend. It is friction. It is also deliberate. The backbone underneath is free-tier Firebase, with no paid service anywhere in the path a user touches. About 50 distinct feature modules ship, and every one of them is wired to a real service with no demo data behind it. 66 prerendered pages carry JSON-LD so the product can be found by a crawler that runs no JavaScript. No demo data is a rule with teeth. A screen wired to a fake array looks finished in a screenshot and falls over the first time somebody real uses it. That count is 50 things that work rather than 50 that render. What it cost is width. A product with that many modules has that many things that can break, and there is exactly one person available to notice when one of them does. Import and sync alone reaches into Google, Outlook, Excel and iCloud, and every one of those is somebody else's format changing on somebody else's schedule. Width is the bill. It is at snapcontact.aoneahsan.com, with a Chrome and Firefox extension built on WXT beside it. The provider keys never leave the handset, which also means that losing the handset loses them. Open the live site What it does, and what that costs to build One-tap capture from any chat surface Browser-side business-card OCR (no upload) Import/sync (Google, Outlook, Excel, iCloud) BYOK messaging (device-local AES-encrypted keys) Real Firestore-backed engagement scoring Automation workflows + duplicate detection 66 prerendered SEO pages with rich JSON-LD Built with React 19 TypeScript Vite 8 Radix UI Themes TanStack Query Firebase CapacitorJS WXT D3.js Worth knowing contacts lead-capture crm extension react firebase Who built SnapContact? Ahsan Mahmood built it on his own, and it is at snapcontact.aoneahsan.com. The capture routes, the browser-side recognition, the workspace, the automation layer, the Firestore rules and the browser extension are all one person's work, which is why five different ways of saving a person produce the same record shape. It is React and TypeScript on Vite with Radix UI Themes and TanStack Query, Firebase underneath, D3 for the charts, Capacitor for the packaged build and WXT for the Chrome and Firefox extension. Does a photographed business card get uploaded anywhere? No. The recognition runs in the browser, on the device holding the photograph, so the image is read where it was taken and is not sent away to be processed. That choice costs something: a model running inside a browser tab has less to work with than a server that can throw hardware at the same picture, so accuracy is the thing being traded. It is the right trade for a card that often carries somebody else's mobile number and home address, and it is worth knowing which way the trade went. What happens to my Twilio, SendGrid or Mailgun keys? They are encrypted on the device and never uploaded. Outgoing messages go device-native by default, which means the phone sends the text, the WhatsApp message or the email rather than the application acting as a sending service on your behalf. Bringing your own provider key is only needed for bulk sending, and it keeps a per-message cost out of the product entirely. The consequence sits on your side: a key that never leaves the handset is a key nobody else can spend, and also one that goes when the handset does. How is a contact's engagement score worked out? From activity the application actually recorded, held in Firestore, rather than from a rating anybody types in. That is the whole claim being made for it. What the weighting is, how recency is treated and where the thresholds sit are not described on this page, because the record these pages are written from does not define them, and a scoring formula invented for a portfolio page would be a number a reader could act on. The score is a prompt to follow up rather than a verdict on a person. Does SnapContact work on iPhone? No, there is no iOS application. It is a web application any phone can open in a browser, its codebase is packaged with Capacitor for a mobile build, and the extension side covers Chrome and Firefox. There is no Apple Developer account behind this work, so nothing here has been submitted to the App Store, and the only product link the record carries is the web one at snapcontact.aoneahsan.com. ## Vessl — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.vessl Premium dark-first car marketplace + multi-tenant dealer-operations SaaS, with a 3D car viewer, financing calculator, leads CRM, and a 13-worker Cloudflare… Buying a used car online is mostly looking at photographs somebody else chose. That is the asymmetry the whole marketplace half is built against. A seller picks the angles. The angles they did not pick are the ones a buyer wants, and the only way to resolve it is to drive somewhere and look, which is expensive in the one currency nobody gets back. Vessl answers that with a model you can turn. A three-dimensional viewer sits in an editorial, dark-first showroom alongside live inventory, a search that understands what it is searching, and a financing calculator. The showroom is deliberately not a list of adverts. It is arranged like something written rather than something uploaded. Underneath the showroom is the half that dealers actually pay for. A leads pipeline on a kanban board, appointments and a calendar, quotes with public accept pages a customer can open without an account, inventory carrying an audit trail, team management with invitations and a permissions matrix, and marketing automation on a schedule. Ten operations modules, and the record says all ten shipped end to end. The two halves share one codebase. That is the point. A marketplace without dealer tools is a directory. Dealer tools without a marketplace are an admin panel. The value is in a lead arriving from the showroom and landing in the pipeline without anybody copying it, which only happens if the two were built as one thing. From the second version it is multi-tenant, and that changed the architecture rather than adding a feature. Each dealer is resolved by subdomain or their own domain, with their own accent colour and logo. The isolation between them is enforced by database rules rather than by the interface deciding what to display. That distinction is the whole of it: a rule is a boundary, and an interface filter is a display decision that a bug can undo. One dealer seeing another dealer's leads is the worst thing this product could do. So the isolation is the part that had to be right before anything above it was worth building, and it is the reason multi-tenancy is a version-two rewrite rather than a setting. The backend is thirteen workers. They run on Cloudflare's free tier, and there are thirteen rather than one because each does a single job and can fail alone without dragging the others down with it. Tenant resolution at the edge, a scheduled marketing run, a quote acceptance: unrelated work with unrelated failure modes, and combining them means an error in the least important takes the most important down with it. Splitting them also keeps each one inside a free allowance. That is the pedestrian reason and the load-bearing one. An edge backend that costs nothing to operate is an edge backend that can stay running for a product that has not started earning yet. There is a smaller decision worth naming, because it is the kind that gets skipped on a marketplace. A quote can be accepted from a public page, without an account. Requiring a customer to register before agreeing to a price they have already been given adds a step at the exact moment somebody has decided to say yes, and every step there costs deals. Token-protected public pages are more work than a login wall and they are the correct answer. What it cost is surface area. Two products, ten modules, thirteen workers and a multi-tenant boundary is a great deal for one person to keep correct at once, and the record still says in progress. Building the marketplace alone would have been finished sooner and would have been a directory. Open the live site Documentation What it does, and what that costs to build Editorial dark-first car marketplace Three.js 3D car viewer + financing calculator Leads CRM with kanban pipeline + appointments Offers/quotes with token-protected accept pages Inventory with audit trails + marketing automation Multi-tenant white-label (edge tenant resolution) 13 Cloudflare Workers (zero-cost edge backend) Built with React 19 TypeScript Vite 8 TailwindCSS Radix Themes TanStack Router Three.js Firebase Cloudflare Workers CapacitorJS Worth knowing automotive marketplace dealer-saas multi-tenant cloudflare-workers react Who built Vessl? Ahsan Mahmood built it, and it is at vessl.aoneahsan.com. It is two products sharing one codebase: a buyer-facing car marketplace and the dealer operations software behind it. One person built both halves along with the edge backend that serves them. The record puts its status at in progress, and the ten operations modules it names are recorded as shipped end to end, which is a narrower and more useful claim than calling the whole platform finished. What does multi-tenant mean for a dealer here? Each dealer gets their own resolved space, reached by subdomain or their own domain, carrying their own accent colour and logo. Isolation between tenants is enforced by database rules rather than by the interface choosing what to show, which is the distinction that matters: one is a boundary and the other is a display decision. A dealer seeing another dealer's leads would be the single worst failure this product could have. Why thirteen separate workers instead of one backend? Because each does one job and can fail without taking the others with it. A marketing automation run on a schedule, a tenant resolution at the edge and a quote acceptance are unrelated pieces of work with different failure modes, and putting them in one process means an error in the least important takes down the most. Splitting them also keeps each inside a free tier, which is what allows an edge backend to cost nothing to run. What is the 3D viewer actually for? Looking at a car properly before driving to see it. A used car listing is normally a set of photographs taken from whichever angles the seller chose, and the angles somebody did not choose are the interesting ones. A model that can be turned removes that asymmetry. It is also the sort of thing that separates an editorial showroom from a list of adverts, which is the difference this marketplace is trying to make. Does Vessl work on iPhone? The marketplace and the dealer tools are web applications and open in any browser, an iPhone's included. The codebase is packaged for Android through Capacitor as well. There is no Apple release, because no Apple Developer account sits behind this work and nothing I build has shipped to the App Store. Dealer operations work is desk work in practice, so the browser is where most of this is used regardless of platform. ## Capacitor Auth Manager — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.capacitorauthmanager Framework-agnostic TypeScript authentication library unifying 15 OAuth, passwordless, credential, and biometric sign-in flows behind one provider-less API. Fifteen ways to sign in, and a product needs about three of them. Which three is the part nobody knows at the start. A founder wants Google because everybody already has one, then the first enterprise conversation asks for Microsoft, then support asks for a magic link because a user cannot remember which button they pressed the last time. Each of those is a week. Not a week of difficult work. A week of reading one provider's console, finding where it disagrees with the last one, and writing the fourth version of the same token-refresh code you have already written in other codebases. Capacitor Auth Manager exists because I had written it more than once. It is one singleton, one provider-configuration model and one auth-state contract, and it behaves the same way in React, Vue, Angular or plain JavaScript. There is no context provider to wrap your tree in. You import it and read it, the way a Zustand store works. That is why it can be adopted a screen at a time. The provider list runs to fifteen. Google, Microsoft, Facebook, GitHub, Slack and LinkedIn cover the OAuth side, Firebase covers the projects already standing on it, and then there are the three that are not OAuth at all: password, one-time codes with magic links, and biometric sign-in. Security is where a library like this either earns its place or quietly loses it. Manual OAuth flows run real S256 PKCE rather than the plain variant that is easier to implement and protects nothing. An ID token is checked for its nonce and its expiry rather than decoded and believed. Secure storage is an interface, so where a token actually sits is your decision instead of mine. The browser biometric fallback encrypts with AES-GCM. Then there is the part the documentation says out loud. A client library cannot be your security boundary. It can run a ceremony correctly and hand you a result; whether that result deserves trust is a question for your server, and no amount of code on this side of the line answers it. That admission costs adoption. It is worth it, because the alternative is a developer who believes the wrong thing about their own login. What it cost was the boring half: a 79-test suite, clean ESM, CommonJS and browser builds, and zero lint warnings held as a condition rather than a target. The standing cost is different, and it never goes away. Fifteen providers is fifteen surfaces somebody else controls. A provider renames a scope, retires an endpoint or tightens a redirect rule, and that failure arrives in my inbox rather than in a compiler, usually from somebody whose users cannot sign in this morning. The honest version of the maintenance answer is a method rather than a promise. A fix publishes as a new version in the same pass that produced it. That is how every package here is handled, and it is all one maintainer can truthfully offer. The optional native plugin covers Android. There is no iOS release behind any of this, because there is no Apple Developer account here. It is on npm as capacitor-auth-manager under MIT, with the source at github.com/aoneahsan/capacitor-auth-manager. Read the section on client-side limits first. It is the part most authentication libraries leave you to discover on your own, usually at the moment when discovering it has already become expensive to act on. npm Source on GitHub What it does, and what that costs to build 15 sign-in providers behind one provider-less API Framework-agnostic (React/Vue/Angular/vanilla JS) S256 PKCE + OIDC nonce/exp validation AES-GCM biometric web fallback Pluggable secure storage interface Optional native Capacitor plugin (iOS/Android) Tree-shakeable ESM/CJS/browser bundles Built with TypeScript CapacitorJS Rollup React 19 Vue Angular Worth knowing npm package authentication oauth capacitor biometric Who built Capacitor Auth Manager, and who maintains it? Ahsan Mahmood, working alone, and he is the person who answers an issue on it. That is the honest maintenance picture: one maintainer, no company behind the package, and a fix that publishes as a new npm version in the same pass that produced the fix. It is on npm as capacitor-auth-manager under MIT, with the source at github.com/aoneahsan/capacitor-auth-manager. What is not on offer is a support commitment, because a promise about future availability is not one that a single maintainer can make honestly. Which sign-in methods does it cover? Fifteen providers behind one API: the OAuth set including Google, Microsoft, Facebook, GitHub, Slack and LinkedIn, then Firebase, then the three that are not OAuth at all, which are password, one-time codes with magic links, and biometric sign-in. The count is not the interesting part. Adding a fourth sign-in method to a live product becomes a configuration change instead of another week spent reading a provider console, and that week is the real cost this removes. Does Capacitor Auth Manager work on iPhone? The OAuth flows are HTTP redirects, so they run in any browser, an iPhone browser included, because none of that is platform code. The optional native plugin covers Android. There is no iOS release behind this work, because there is no Apple Developer account here and nothing I build has shipped to the App Store. So the accurate reading is web everywhere, native on Android, and no claim of any kind about what happens inside an Apple review queue. Do I have to wrap my application in a provider component? No. There is no context provider and no higher-order component to add; the library is a singleton you import and read, in the way a Zustand store works. That is what makes it adoptable one screen at a time rather than as a rewrite, and it is why the same package behaves identically in React, Vue, Angular and plain JavaScript. It builds to ESM, CommonJS and a browser bundle, and it is tree-shakeable, so a project using three providers does not carry the other twelve. What does this library deliberately not do? It does not make your client the security boundary, and the documentation says that out loud rather than implying otherwise. A client library can run an authentication ceremony correctly and hand you a result; deciding whether that result may be trusted belongs to your server, and no code on the browser side of the line changes it. It also does not host anything, hold state on your behalf, or replace the provider consoles where scopes and redirect URIs live. Those stay in your accounts, which is the version of this where you are not locked in. ## shared-features — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.sharedfeatures NPM TypeScript/React library that centralizes feature flags, cross-promotion advertising, in-app broadcasts, and 9 profile-data domains, administered from… This package started as copy-and-paste. The same three systems kept turning up in every product I shipped. A feature flag, so a half-finished screen could go out dark. A banner pointing at whichever of my own products was relevant to the person looking. An announcement channel for the times something needs saying inside the application rather than by email. Three systems, rewritten each time, drifting apart each time. The drift is the expensive part. A flag that respects the installed version in one product and ignores it in another is two behaviours wearing one name, and the day that difference matters is a day you are already busy debugging something else. shared-features is what those three became once. Feature flags here are version-aware, so a flag can be on for the build that can handle it and off for the build that cannot. That is the difference between a flag and a switch. A switch assumes everybody is running today's version, and nobody is. Cross-promotion is the second system, with impression tracking attached, because an advertisement nobody counts is a decoration. Broadcasts, announcements and alerts are the third. Nine profile-data domains sit beside them, covering contact, developer, social, address, payment, services, skills and testimonials among others, which are the shapes a profile keeps needing in product after product. All of it is administered from one panel. A banner, an advertisement or a flag changes in one place and reaches every application consuming the package. The alternative is a release in each product for a change of copy, which in practice means the copy stays wrong. What it cost is the thing that makes a shared library different from a private one. You cannot break your own consumers. The package holds a deprecate-before-remove contract, so a minor version never takes anything away, and a decision you have come to regret keeps living behind a deprecation notice until a major release earns the right to delete it. That is a tax on every design mistake, paid in public. React, Firebase, Radix and Zustand stay peers rather than dependencies, so nothing gets bundled twice and the consuming application keeps deciding its own versions. Six subpath entry points mean a project importing only the flags never carries the advertising code. The build is dual ESM and CommonJS with type declarations, produced through Vite's library mode. That is the least interesting sentence on this page and it is the one that decides whether the package drops cleanly into somebody else's toolchain or becomes an afternoon. It is a live dependency in ZTools and in this portfolio. That is the honest form of the maintenance answer. The first application to break when I get something wrong is one of mine, usually within a day, usually while I am doing something else. It is not a support promise. It is the reason a defect tends to surface before anybody else has to find it. There is no iOS release behind this work, because there is no Apple Developer account here. The companion website is wrapped with Capacitor for Android, with telemetry and Sentry behind it. On npm as shared-features. The source is at github.com/aoneahsan/shared-features. The reason to read it is not the feature list. It is that the plumbing underneath three separate products is now one thing, in one place, written down where anybody can open it and decide whether they agree with how it works. npm Source on GitHub What it does, and what that costs to build Version-aware feature flags Cross-promotion advertising with impression tracking In-app broadcasts/announcements/alerts 9 shared profile-data domains One Firestore admin console for all consumers Dual ESM + CJS + .d.ts (6 subpath entry points) Deprecate-before-remove backward-compat contract Built with TypeScript React 19 Vite 8 Radix UI Themes Firebase Zustand Worth knowing npm package feature-flags react firebase reusable-infrastructure Who built shared-features? Ahsan Mahmood built it for his own products first, which is the useful thing to know about it. It is on npm as shared-features, with the source at github.com/aoneahsan/shared-features, and it is a live dependency in ZTools and in this portfolio site. So the first application to break when something in it is wrong is one of his own, which is a different quality of testing from a suite that only ever runs on a laptop. That is not a support promise and should not be read as one. What is actually in it? Three production systems and a set of shared profile shapes. The systems are version-aware feature flags, cross-product advertising campaigns with impression tracking, and in-app broadcasts with announcements and alerts. Beside them sit nine profile-data domains covering contact, developer, social, address, payment, services, skills and testimonials among others, which are the shapes that kept being rebuilt product after product. The through-line is that each of those had been written more than once before this package existed, and each copy had quietly drifted into slightly different behaviour. Does shared-features work on iPhone? It is a React library, so it runs inside whatever your application runs in, an iPhone browser included, and there is nothing platform-specific in it. There is no iOS release behind any of this work, because there is no Apple Developer account here and nothing I build has shipped to the App Store. The companion website is wrapped with Capacitor for Android. The accurate statement is that the code is portable and the released mobile surface is Android alone. Where is it administered from? From one admin panel, so a flag, a banner or an announcement is changed once and reaches every application consuming the package. That is the whole reason it exists as a library rather than as three copied folders. Without it, changing a line of promotional copy means a release in every product that shows it, which in practice means the copy stays wrong for months. The data sits in Firestore behind that panel, and the consuming applications read it instead of each holding a copy of their own. Will a minor version break my build? No, and that is a written contract rather than an intention. The package holds a deprecate-before-remove rule: a minor release never takes anything away, and something regretted keeps working behind a deprecation notice until a major version earns the right to remove it. Keeping React, Firebase, Radix and Zustand as peers helps as well, because nothing is bundled twice and your application keeps deciding its own versions of them. The cost of that contract falls on the library rather than on you, which is the correct side for it to sit. ## Growthify (All-in-One Shopify Suite) — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.growthify Embedded Shopify app suite with a 50-block storefront theme extension and merchant growth tools. If a growth tool lives on its own domain, a merchant opens it once. The Shopify admin is where the working day actually happens, and anything asking a merchant to leave it is competing directly with the reason they signed in. A merchant's problem is rarely one thing either. It is a product page that does not convert. A collection nobody browses. A promotion that has to be configured in three separate places and then remembered in all three. So Growthify is embedded. It runs inside the Shopify admin and bills through Shopify's own billing, which means the tool and the subscription both live where the merchant already is rather than in a second place they have to remember. Every capability also has to survive a merchant who installs it, configures nothing and expects it to work, because an embedded app has no onboarding call standing behind it. The storefront half carries a stricter constraint again. Changes to a shop's public pages ship as a theme app extension rather than as edits to the merchant's theme, so what the app adds and what the merchant owns stay separable. That is a distribution decision as much as a technical one. Growthify is a yarn-workspaces monorepo. Three parts come out of it. A Remix backend and admin application, embedded in the Shopify admin, carrying the conversion, merchandising and growth tooling. A theme app extension shipping 50 Liquid blocks that a merchant places from the theme editor. And a React 19 marketing web application, with a Capacitor wrapper prepared rather than published. Shared TypeScript packages keep the three from disagreeing about the same merchant, and Prisma holds the data underneath them. The marketing site exists because an embedded app is invisible until somebody installs it. A merchant deciding whether to install has nowhere inside the admin to read about it first. Tiered billing plans are defined in the app and charged through Shopify's billing, so a subscription to this sits with the merchant's other Shopify charges instead of arriving as a separate line from a company they have never heard of. The costs start on the storefront. A Liquid block renders inside a theme somebody else wrote. 50 blocks is 50 pieces of markup that have to behave in a design nobody here has ever seen, which is a genuinely different discipline from building a page you control end to end. Embedded means somebody else owns the frame. The platform's conventions, its session model and its review process are givens rather than choices, and a change on their side is a change nobody here scheduled. That is the trade for being in the one screen that matters. Billing through Shopify is the right call and it removes a lever. Pricing moves through their model rather than through mine. One person maintaining a Remix app, a Liquid extension and a React site is one person moving between three sets of conventions in a day. The shared packages reduce that and they do not remove it. The Capacitor wrapper is prepared and not published, and this page uses the word prepared because that is the accurate one. There is no iOS build. There is no Apple Developer account behind this work, so the mobile route here is Android. Growthify is in progress. The honest version of that sentence is a page describing the blocks a merchant can place today, and saying nothing at all about the ones they cannot. Open the live site Documentation What it does, and what that costs to build Embedded Shopify admin app (Remix) 50 storefront Liquid blocks (theme app extension) Tiered Shopify billing plans Marketing web app (React 19) Shared TypeScript packages Capacitor mobile wrapper (prepared) Built with Remix React TypeScript Shopify App Liquid (Theme App Extension) Prisma TailwindCSS CapacitorJS Turborepo/Yarn Workspaces Worth knowing shopify ecommerce saas merchant-tools storefront Who built Growthify? Ahsan Mahmood built it alone, and the marketing surface is at growthify.aoneahsan.com. The Remix admin app, the theme app extension, the shared TypeScript packages and the React marketing site all come out of one yarn-workspaces monorepo maintained by one person. It is marked in progress here, so this page describes what runs today. Is Growthify free? Growthify carries tiered billing plans defined in the app and charged through Shopify's own billing, so a merchant's subscription to it sits alongside their other Shopify charges rather than in a separate checkout. Which tier gives what is managed inside the product, because that is the sort of detail a portfolio page gets wrong within a month of writing it down. Does Growthify work on iPhone? No. Growthify is an embedded Shopify app used in a browser inside the Shopify admin, and the marketing application has a Capacitor wrapper prepared for Android rather than published. There is no iOS build and there never has been, because there is no Apple Developer account behind this work. What are the 50 storefront blocks? They are Liquid blocks shipped as a theme app extension, which a merchant places from the theme editor rather than by editing theme code. That is what lets storefront changes arrive as an app capability instead of as a modification somebody has to undo later. The blocks cover the conversion and merchandising side of the product. Where does Growthify actually run? Inside the Shopify admin, as an embedded app built on Remix, with a separate public marketing site outside it. Being embedded is the whole design: a merchant runs their shop from that one screen, and a growth tool that asks them to open a different one is a tool that gets opened once. Prisma holds the data behind it. ## Empora for WooCommerce — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.empora Freemium WooCommerce suite — WP plugin with a 35+ module React admin, SaaS dashboard and Laravel backend. Open the plugins page of a working WooCommerce store and count the rows. Each row is an update cycle, a licence to renew and an author who may or may not still be maintaining the thing. Any one can break a checkout. That is the tax on a platform whose greatest strength is that anybody can extend it. A store that has been trading for a while is carrying a mixture nobody chose on purpose. It arrived one decision at a time, and every one of those decisions was reasonable on the day it was made. Consolidating some of that into one product sounds obvious, right up to the point where you have to ship it. A WooCommerce product lives in two worlds at once. The free half is distributed through WordPress.org, which has its own review rules and its own settled idea of what a plugin is allowed to do inside somebody's store. The premium half is sold through CodeCanyon, which has different rules again. WordPress.org does not distribute premium plugins. So a premium edition cannot receive an update through the channel every WordPress user already trusts, which means it needs an updater of its own before it can ship a single fix. That constraint is the reason this product has a backend rather than only a plugin. Empora is a Turborepo monorepo. Three things come out of it. A WordPress plugin, carrying a React admin of 35+ modules that runs inside wp-admin rather than as a settings page which grew one tab at a time. Then a marketing and dashboard web application, wrapped with Capacitor for Android. A Laravel 11 backend handling auth, Stripe billing, licence activation and the self-hosted updater the premium edition depends on. The proposition is one install, one update cycle and one place where the settings live. The monorepo is what lets those three agree with each other. A licence issued by the backend, checked by the plugin and displayed in the dashboard is one concept held in one place rather than three implementations quietly drifting apart. Now what it costs. A monorepo means a single version bump touches PHP, React and Laravel at once. The arrangement that makes a release coherent also makes every release larger than it looks from the outside. PHP and TypeScript are two ecosystems. Maintaining both means switching context several times a day, and the switch costs more than either language does on its own. Two channels mean two rule sets. A change that satisfies one reviewer is not automatically acceptable to the other, and finding that out happens at submission rather than at build time, which is the worst possible moment to learn it. The self-hosted updater is a server that has to answer every time somebody's store checks for a version. That is the single place in this product where being unavailable is not a private problem. Everything else here degrades quietly; the updater fails in front of a customer. Licence activation is a boundary somebody will try to walk around. Every mechanism that makes that harder also makes a legitimate install slightly more irritating, and the balance between the two is a judgement rather than a setting. No iOS. There is no Apple Developer account behind this work, so the mobile half of Empora is Android. Empora is still in progress. What is written above is what exists today, and this page says nothing about what comes next, because a roadmap on a portfolio is a promise nobody signed. Open the live site What it does, and what that costs to build 35+ module React admin Free + premium WP plugin distribution License activation + self-hosted updater Stripe billing SaaS marketing/dashboard web app Capacitor Android wrapper Built with WordPress (PHP) React TypeScript Laravel Stripe CapacitorJS TailwindCSS Turborepo/Yarn Workspaces Worth knowing woocommerce wordpress ecommerce saas plugin Who built Empora for WooCommerce? Ahsan Mahmood built it alone, and it is at empora.aoneahsan.com. The WordPress plugin in PHP, the React admin inside it, the Laravel backend and the Capacitor wrapper are one person's work across one Turborepo monorepo. It is marked in progress on this site, which means the page describes what exists rather than what is planned. Is Empora free? It is freemium: the plugin is distributed free on WordPress.org, and a premium edition is sold through CodeCanyon with Stripe billing and licence activation behind it. The split is a distribution fact rather than a marketing one — WordPress.org and CodeCanyon have different rules, and a premium plugin cannot receive updates through WordPress.org at all. Which capabilities sit on which side is managed in the product itself. Does Empora work on iPhone? No. The plugin runs inside a WooCommerce store, so the store owner uses it in a browser, and the companion dashboard is wrapped with Capacitor for Android. There is no iOS build, because there is no Apple Developer account behind this work. What is in the Empora plugin admin? A React admin of 35+ modules, running inside wp-admin rather than as a settings page that grew one tab at a time. The point of putting many store-enhancement modules behind one install is that a store owner gets one update cycle instead of a dozen. Each module is configured in the same place, by the same interface. How does a premium Empora licence receive updates? Through a self-hosted updater that the Laravel backend serves, because WordPress.org does not distribute premium plugins. Licence activation runs against that same backend, so an install knows whether it is entitled to the version it is asking for. That is also the one component in this product where being unavailable is immediately visible to a customer. ## Aura CRM — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.auracrm A voice-first client operations engine for independent professionals who spend hours on post-session admin. The interesting hour of a client session is the one after it ends. A coach, a consultant, a therapist, an advisor: each finishes an appointment and then owes a set of small obligations. Notes while the detail is fresh. A task or two. A follow-up message that sounds like them. Sometimes an invoice for the part that ran over. None of that happens in the room, and most of it happens badly. It gets done hours later from memory, or in a batch on a Sunday, or not at all. The record degrades quietly, and nobody notices until the moment they need to remember what was agreed in March. Aura CRM's proposal is that speaking is the write path. Finish the session, say a short update out loud, and let the system transcribe it, update the record, raise the tasks, draft the follow-up in a matching style, queue anything billable and refresh a private progress view for the client. The write happens while the detail is still in your head rather than after it has gone. That is the concept, and here is the part that matters more than the concept. It is not running. The record calls it documented roadmap rather than shipped functionality, and the honest description of what exists today is a polished landing page sitting on a production-grade foundation. Saying anything else would be describing an intention as a product, which on a page like this is simply a false claim with better adjectives. What is real is the foundation and the positioning. The stack is React with Tailwind on a Supabase backend carrying Postgres, vector search, authentication and edge functions. A Capacitor shell is configured for web and Android, and there is no iOS release behind any of it. There is a runtime theme customiser with accent presets, dark mode, a high-contrast option and a reduce-motion setting, and a brand mark whose gradient binds to the live theme variables rather than being a fixed image. The architecture anticipates a requirement that cannot be retrofitted. Row-level security in the database and awareness of business-associate agreements when routing to AI services are decisions made before there is data, because a system that will one day hold therapy notes cannot acquire those properties later. That is architectural readiness. It is not a certification and nothing here claims one. The positioning is deliberately narrow, and that was the risk worth testing first. Five specific kinds of practitioner rather than businesses in general. Narrow positioning either resonates immediately or fails immediately, and finding out which costs a landing page rather than a voice pipeline. There is a service layer underneath all of it, centralised rather than scattered. Logging, errors, analytics, storage and data access each sit behind one place instead of being called directly from wherever they were needed. That sounds like housekeeping. It is the difference between swapping a provider in an afternoon and hunting forty call sites. On a product whose eventual shape depends on services nobody has chosen yet, it is the cheapest decision available. What it cost is honesty. An entry like this is easy to write as though the concept were the product, and no reader could tell from the outside. The alternative is a page admitting the interesting half is not built, which is less impressive and is the only version worth putting a name on. Open the live site What it does, and what that costs to build Voice-first capture concept where a spoken update is the primary write path into the CRM Audience-specific positioning for five practitioner segments Polished responsive marketing landing with hero, segments, pricing, FAQ, and waitlist Runtime theme customizer with accent presets, dark mode, high-contrast, and reduce-motion Theme-adaptive brand mark whose gradient binds to live CSS variables HIPAA-ready Supabase architecture with row-level security and BAA-aware AI routing Centralized service layer for logging, errors, analytics, storage, and data Capacitor mobile shell configured for web, Android, and iOS Built with React 19 TypeScript Vite Tailwind CSS v4 shadcn/ui Radix UI TanStack Query TanStack Router Zustand Supabase pgvector Capacitor Worth knowing crm voice-first saas supabase react capacitor vertical-saas client-operations What actually exists of Aura CRM today? A marketing landing page on a production-grade foundation, and the record says so in those words. The positioning, the theme system, the service layer and the mobile shell are built. The voice pipeline that the whole concept rests on is documented roadmap rather than shipped functionality. That is an unusual thing for a portfolio entry to lead with, and leading with it is the only way the rest of the page is worth reading. What is the voice-first idea meant to solve? The admin that follows a client session rather than the session itself. A coach, consultant or advisor finishes an appointment and then owes notes, tasks, a follow-up message and sometimes an invoice, and that work happens hours later from memory or does not happen. The concept is that speaking a short update immediately afterwards becomes the write path into the record, instead of typing one later. That concept is designed and documented; it is not running. Why build the landing page before the product? Because the positioning was the risky part. This targets five specific practitioner types rather than businesses in general, and whether that framing lands is a question worth answering before building a voice pipeline underneath it. A landing page is a cheap way to put the argument in front of people. It is also the honest reason this entry exists at Phase 0 rather than being held back until there is more. What does HIPAA-ready mean here, and what does it not mean? It describes how the backend is arranged, not a certification anybody has granted. The record names row-level security in the database and awareness of business-associate agreements when routing to AI services, which are architectural choices made in advance because retrofitting them into a system holding therapy notes is not realistic. Nothing here claims a compliance audit, a signed agreement or a legal status. The architecture anticipates the requirement, and that is the whole of the claim. Does Aura CRM work on iPhone? There is no iOS release. The record lists a Capacitor shell configured for web, Android and iOS, which describes how the project is set up rather than what has been published, and no store listing exists on either platform. There is no Apple Developer account here and nothing I build has shipped to the App Store. The landing page opens in a phone browser like any other web page, which is what there is to open today. ## Bazaaro — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.bazaaro A trust-first classifieds marketplace (OLX alternative) with escrow, verified identity, and SafeMeet pickup zones for buyers and sellers. Everyone who has used classifieds knows how they fail. The listing that is too cheap. The account created yesterday. The buyer who agrees a price, arranges a time and then stops replying. The meeting in a car park that you think about more than you would like to. None of that is an edge case. It is the ordinary texture of the thing, and it has stayed that way for years because trust is expensive to build and nobody is punished for leaving it unbuilt. Bazaaro is an attempt at the expensive version. The design puts four things at the centre rather than at the margins. Money held in escrow until the buyer confirms. Identity that has been verified, carrying a trust score. Geofenced pickup zones, so the meeting happens somewhere chosen rather than somewhere suggested. And an evidence locker for when a dispute happens anyway. Each of those inverts a risk that currently sits with the person who has the most to lose. Escrow makes the seller wait instead of the buyer gamble. Verification puts a cost on creating an account for a single fraud. A designated pickup place removes the negotiation that most often ends with somebody agreeing to a location they are not comfortable with. Now the part that has to come before any of that is admired. None of it is running. The record says the signed-in marketplace is planned and not yet live, and what builds green today is the scaffold, a design system, internationalisation, a graceful-degrade service layer and the public marketing site. The trust machinery is architecture and intention. A page describing it in the present tense would be describing a product that does not exist. What is built is built emerging-markets-first, and that is a structural decision rather than a preference. Right-to-left layout, multiple currencies and cash-on-delivery paying into escrow are not features bolted onto a finished marketplace. Right-to-left reaches into every layout assumption. Currency reaches into the data. Cash-on-delivery reaches into the payment flow and changes what escrow even means. Products that treat these as a later export problem find out that the assumptions underneath were load-bearing. The service layer degrades rather than breaking. It runs without paid API keys, returning something sensible when a service is not configured instead of failing at boot. That is what lets a marketplace this size be developed at all by one person, because the alternative is needing every third-party account provisioned before the first screen renders. One codebase targets web, mobile and desktop, which is a claim worth qualifying. Sharing a source tree across three delivery shapes saves the duplication, and it does not make three products from one. Each still needs its own packaging, its own store relationship where a store is involved, and its own decisions about what belongs on that surface. What is shared is the logic. What is not shared is the finishing, and the finishing is where the time goes. What it cost is the gap between the idea and the software. Trust-first is a genuinely good argument and the marketing site makes it well, which is exactly the situation where a portfolio page becomes misleading without meaning to. The concept is complete. The scaffold is real. The thing that would make either matter is not built, and saying so is the only version of this page worth publishing. Open the live site What it does, and what that costs to build Trust-first design with escrow that holds money until the buyer confirms Verified identity and trust score system (planned) SafeMeet geofenced pickup zones and evidence-locker disputes Design-system shell with themed accent customizer and branded error pages Multi-language i18n with RTL support for emerging markets Public marketing site with Home, Pricing, and Safety pages Graceful-degrade integration layer that runs without paid API keys Cross-platform codebase targeting web, mobile, and desktop Built with React 19 TypeScript Vite 6 Tailwind CSS v4 shadcn/ui Zustand TanStack Query React Router 7 react-hook-form Zod Firebase Firestore i18next Worth knowing classifieds marketplace escrow trust-and-safety react firebase capacitor cross-platform What of Bazaaro is actually built? The scaffold, a design system, internationalisation, a service layer that degrades gracefully without paid keys, and the public marketing site. The record is explicit that the signed-in marketplace is planned rather than live. So the trust machinery that the entire concept rests on, meaning escrow, verified identity and the pickup zones, is designed and not running. That is the first thing a reader should know and the last thing a marketplace page usually says. What problem is it aimed at? The failures buyers and sellers already expect from classifieds: scams, fake accounts, people who vanish mid-conversation, and fees that feel extractive. Those are not edge cases on existing platforms, they are the ordinary experience, and they persist because trust is expensive to build and cheap to leave unsolved. Whether this design would fix them is untested, since the part that would do the fixing has not shipped. How is escrow supposed to work here? Money is held until the buyer confirms, rather than moving when the seller asks. That inverts the usual risk: the seller waits instead of the buyer gambling. It is designed alongside cash-on-delivery paying into the same escrow, which matters in markets where cards are not the default and a purely card-based design excludes most people. All of that is architecture and intention. None of it is currently taking anybody's money. Why build for emerging markets first? Because retrofitting is worse than starting there. Right-to-left language support, multiple currencies and cash-on-delivery are not features added to a finished marketplace; each one reaches into layout, data and payment flow. A product built the other way round tends to treat them as an export problem later and discovers the layout assumptions are load-bearing. Starting from that constraint costs early effort and avoids a rewrite. Does Bazaaro work on iPhone? There is no iOS release. The codebase targets web, Android and desktop from one source, and the record lists iOS among the intended Capacitor targets, which describes the setup rather than anything published. There is no Apple Developer account here and nothing I build has shipped to the App Store. What exists to open today is the public marketing site, which a phone browser reaches like any other page. ## CallVault — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.callvault Offline-first Android app that records your own calls and backs them up to storage and a database you control. I did not build a way around Android's call-recording limits. Android closed that door to ordinary applications after Android 10, and pretending otherwise produces software that records silence and a user who finds out afterwards. So CallVault treats capture as an abstraction with two honest strategies behind it instead. A RecordingSource interface, two implementations. One is a microphone foreground service that needs no root. The other is a folder watcher that ingests an external recorder's output on a rooted device. Which one you get depends on the phone in your hand rather than on a promise made on a page. Neither of them is silent, and that is deliberate. Recording runs behind a live banner and a persistent notification, so the person holding the phone can see it happening. An application that hides a recording is a different product with a different purpose, and this one is for recording your own calls. The library is offline-first, and SQLite is the source of truth. A Drift database on the device holds the record of what exists. Sync is a separate, idempotent, multi-stage state machine with retry and backoff, which means a failed upload is a state to resume from rather than a row that quietly vanishes. That distinction is the whole reliability story. A design where the server is the source of truth and the device holds a cache is a design where a bad afternoon on a train costs you a recording. Turning it round costs storage on the handset and buys a system that cannot lose something it already has. Where the audio goes is worth reading twice. Recordings upload to self-hosted FilesHub storage. Only metadata syncs to your own Supabase project, behind Postgres row-level security. There is no third-party analytics in the application at all. The server is yours. That is more setup than signing into somebody's account, and it is the only version of this that is defensible for a recording of a phone call, because the alternative is a private conversation sitting on infrastructure neither party to it has ever heard of. Around that sit the ordinary things a library needs. Playback with seek, skip and speed. Search across the number, the contact and your own notes, with a stats dashboard over the top. Notes are searchable because a call is usually easier to find by what was said in it than by the number that made it. An optional biometric lock on the application, and a Wi-Fi-only control for uploads. The admin dashboard is the same codebase compiled for the web, which is the reason there is one at all. The Flutter side is Riverpod and go_router with Material 3 on top, freezed for the models and just_audio for playback, with GitHub Actions doing the builds. One codebase, two outputs. What it cost is distribution. It is sideloaded by design, because the Play Store does not permit silent call recorders, so this is an application somebody installs deliberately rather than one they come across. It is Android-only. It is in development rather than finished, and saying so is cheaper than having it discovered. That last sentence is on the page for the same reason the banner is on the screen. It is at callvault.aoneahsan.com. Whether recording a particular call is lawful where you are standing is a question about your jurisdiction and not about this software, and no application is able to answer it for you. Open the live site Documentation What it does, and what that costs to build Transparent call recording with a live banner and persistent notification Pluggable capture engine (no-root mic service or rooted folder watcher) Offline-first library where SQLite is the source of truth Idempotent multi-stage sync pipeline with retry and backoff Your-server-only storage: audio to FilesHub, metadata to your Supabase Audio playback with seek, skip, and speed controls Search by number, contact, or notes plus a stats dashboard Optional biometric app lock and Wi-Fi-only upload controls Built with Flutter Dart Riverpod v2 go_router Drift (SQLite) Kotlin Supabase FilesHub Material 3 freezed just_audio GitHub Actions Worth knowing call-recording android flutter offline-first supabase self-hosted privacy riverpod Who built CallVault? Ahsan Mahmood built it, in Flutter and Dart with a Kotlin capture engine underneath, and it is at callvault.aoneahsan.com. Riverpod holds the state, go_router the navigation, Drift the on-device database, and the same codebase compiles to a Flutter Web admin dashboard so there is no second application to keep in step. It is in development rather than finished, and it is Android-only. The person who wrote the capture engine is the person who wrote the sync pipeline it feeds. Does it record without the caller knowing? It does not record without YOU knowing: a recording runs behind a live banner and a persistent notification, which is a deliberate design decision rather than a platform requirement somebody worked around. What the person on the other end of the call sees is not something the application controls, and nothing here claims to have told them. An application that hides a recording is a different product with a different purpose, and this one exists to record your own calls. Whether a given recording is lawful where you are is a question about your jurisdiction rather than about the software, and no application can answer it on your behalf. Where do the recordings actually go? To your own servers, and nowhere else. Audio uploads to self-hosted FilesHub storage, while only metadata syncs to your own Supabase project behind Postgres row-level security, so the recording and the record of it live in two places you control. There is no third-party analytics in the application at all. That arrangement costs more to set up than a vendor account would, and it is the only arrangement that is defensible for a recording of a phone call. What happens when an upload fails? Nothing is lost, because SQLite on the device is the source of truth rather than a cache of the server. Sync is a separate idempotent multi-stage state machine with retry and backoff, so a failed upload is a state to resume from rather than a row that quietly disappears. A Wi-Fi-only setting exists for anyone who would rather uploads waited for a network they are not paying for by the megabyte. Being offline-first here is a data-integrity decision more than a convenience. Does CallVault work on iPhone? No. It is Android-only, and the capture engine is Kotlin sitting on Android APIs, so there is nothing here that could be pointed at another platform without being rewritten. There is no iOS release behind any of my work in any case, because there is no Apple Developer account here and nothing I build has shipped to the App Store. It is also sideloaded by design rather than installed from a store, because the Play Store does not permit silent call recorders. ## Code Craft Studio — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.codecraftstudio React library to scan and generate QR codes and barcodes, with optional Capacitor native support for Android and iOS. A QR code is not a picture of a link. It is a small encoding format with types inside it. A WiFi network is one shape. A contact card is another, a calendar event a third, and each carries a syntax a scanner either recognises or renders as a line of characters that means nothing to anybody. Get the shape wrong and it still scans. That is what makes this area quietly annoying. A malformed WiFi payload does not raise an error anywhere; it produces a code that opens as gibberish on somebody's phone at the door of a café, and no one in that exchange finds out why. Code Craft Studio covers the whole surface instead of one corner of it. Generation runs across 22 or more purpose-typed QR shapes, including WiFi, contact cards, events, locations, coupons and menus, with the syntax handled per type rather than left to the caller. Barcodes cover 14 or more formats: EAN, UPC, Code 128, Code 39, Code 93, ITF, Codabar, Data Matrix, PDF417 and Aztec. Scanning is the other half, and it has a platform problem. A browser reads a code through the camera, and for most applications that is the end of the story. It is not the end of the story in a warehouse, where a scan happens hundreds of times an hour, in poor light, at an angle nobody would choose. Native scanners are better at precisely that, which is why the same API upgrades to a platform scanner through ML Kit when Capacitor is present. The calling code does not change. Because Capacitor stays optional, a plain React application carries none of it. The interface layer is provider-less: one hook, plus drop-in components for scanning, generating and a combined studio view, so a working scanner is an import rather than a project. Design customisation is not decoration here. Colours, an embedded logo, frames, dot and corner styles all change how reliably a code reads, and the error-correction level decides whether a logo in the middle costs you a scan. Exposing those together is the difference between a code that looks right on a screen and one that reads off a printed menu under a bad lamp. Validation runs per type. Barcode checksums are verified rather than assumed, because a check digit nobody checks is a decorative digit. None of that is visible until the code leaves the screen. A code is generated on a laptop and then read off a laminated card, a receipt, a menu under glass or a phone held at arm's length, and the failures live in that gap rather than in the encoder. What it does not have today is worth naming plainly. Real analytics, extra export formats and cloud sync are not in it. Scanning and generating are the finished parts; those three are not, and a page implying otherwise would be exactly the sort of claim you discover an hour after installing. The native path is Android. There is no iOS release behind this work, because there is no Apple Developer account here. A phone gets the browser camera instead. MIT, on npm as code-craft-studio. A scanner that works in a demonstration and fails in a stockroom is the ordinary outcome in this area, and the platform adapters exist for that gap rather than for the demonstration. npm What it does, and what that costs to build Browser-camera QR scanning with optional native upgrade Multi-format barcode scanning (EAN, UPC, Code 128/39/93, ITF, PDF417, Aztec) QR generation across 22+ typed data shapes (WiFi, vCard, event, coupon) Barcode generation across 14+ 1D and 2D formats Design customization: colors, embedded logo, frames, error correction Provider-less useCodeCraftStudio() hook plus drop-in React components Runtime platform adapters selecting web or Capacitor native scanning Per-type validation and barcode checksum verification Built with TypeScript React Capacitor @zxing/library jsbarcode qr-scanner qrcode ML Kit Vision framework Rollup Swift Kotlin Worth knowing qr-code barcode react typescript capacitor scanner npm-package cross-platform Who built Code Craft Studio? Ahsan Mahmood built it, scanner and generator both, along with the platform adapters that pick between the browser camera and a native scanner. It is on npm as code-craft-studio under MIT, and the npm page is the link this project has. Building both halves matters more than it sounds here. A generator that emits a slightly wrong WiFi payload and a scanner that happily reads it are two bugs that hide each other, so writing both sides against one set of type definitions is the point rather than a coincidence. Do I need a native app to scan a code? No. The browser camera path works in a plain React application, with no Capacitor and no native build involved anywhere. Adding Capacitor upgrades the same API to a platform scanner through ML Kit, which starts to matter when scanning happens hundreds of times an hour in poor light rather than once in a demonstration. Your calling code does not change between the two, because the platform adapter is selected at runtime. A web-only project carries none of the native surface in its bundle. Does Code Craft Studio work on iPhone? The browser camera path runs in a phone browser, an iPhone included, because that half is a web API rather than platform code. The native scanning upgrade is Android, through ML Kit. There is no iOS release behind this work, because there is no Apple Developer account here and nothing I build has shipped to the App Store. So a code will scan on an iPhone through the browser, and no claim is being made anywhere about a native Apple scanner. Which formats does it read and generate? Barcodes across 14 or more formats, including EAN, UPC, Code 128, Code 39, Code 93, ITF, Codabar, Data Matrix, PDF417 and Aztec, plus QR codes across 22 or more purpose-typed shapes such as WiFi, contact cards, events, locations, coupons and menus. The typed shapes are the useful part rather than the count. A WiFi code and a contact card have different syntaxes, and getting one slightly wrong produces a code that scans perfectly and then shows a stranger a line of nonsense. What is not in Code Craft Studio today? Real analytics, additional export formats and cloud sync are not in it. Scanning and generating are the finished parts, and those three are named here rather than left for you to find after the install. Design customisation is present and worth knowing about, since colours, an embedded logo, frames and the error-correction level all change how reliably a printed code reads. What the library cannot tell you is whether your printer, your lamination or the lighting ruined the result, and only a test print answers that. ## Corra — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.corra A unified SaaS for K-12 teachers that keeps planning, grading, SPED/IEP, comms, and behavior on one shared student record with a FERPA-safe AI copilot. A teacher's day is fragmented by software that does not talk to itself. The lesson is planned in one place. The differentiated version for three students who need it is made somewhere else. The quiz comes from a third tool, the rubric from a fourth, the gradebook is its own thing entirely, and the substitute plan is written from scratch on the morning somebody is ill. Every one of those already exists inside the lesson. They are derivatives of a thing the teacher has already made, and the reason they get remade is that no two of the tools share a record. The work is not the thinking. The work is the retyping, and it happens at the end of a day that was already long. Corra's proposal is one shared student record with everything reading and writing it. Plan once, and the variants, the quiz, the rubric, the substitute packet and the gradebook rows come from that same spine rather than from a person entering the same facts five times. Twelve teacher workflows are specified against it, from planning and grading through special-education compliance, parent communication with translation, behaviour documentation and sub plans. Now the sentence that has to come before any of that is admired. None of it is built. The record puts the status at architecture-planning phase: research, branding, a decision-complete architecture and a running design-system scaffold exist, and the twelve modules are specified rather than shipped. There is no confirmed live address and no store listing. Describing the workflows in the present tense would be describing a specification as a product. What was actually decided is the part that cannot be added later. A shared record cannot be retrofitted across twelve modules built independently. Independent modules produce twelve schemas that almost agree, and almost is the expensive kind. Deciding the spine first is slower to show anybody and is the only order in which the idea survives contact with the second module. The same reasoning applies to the copilot's data path. Every model call is designed to pass through a gateway that removes identifying information by default, so raw student data does not reach the model at all. That is one place rather than twelve call sites each remembering to be careful, and on a product touching children's education records the difference between those two designs is the entire position. Designing around a regulation is not the same as being certified against one. The architecture anticipates the requirement. Nobody has audited it and no claim of compliance is being made here. The distinction is worth holding onto precisely because it is the kind that marketing copy erases. Translation is in the parent communication design rather than beside it. A logged contact trail in a family's own language is a different product from an English inbox with a translate button, and which one it is gets decided at the schema rather than in the interface. What it cost is proof. There is nothing to show. Architecture produces a scaffold and a set of documents, and a portfolio entry that admits as much is a great deal less impressive than one describing twelve working modules would be. It is also the only accurate one. The accuracy matters more here than anywhere else, because the subject is children's records. Open the live site What it does, and what that costs to build Shared student-record spine that every module reads and writes with zero re-keying FERPA-safe AI copilot that de-identifies student data before any model call AI-drafted grading and feedback the teacher reviews, with rubric library SPED IEP/504 compliance suite with graphed progress and audit-ready evidence Lesson and unit planning with standards-coverage tracking and differentiation Parent comms inbox with 100+ language translation and logged contact trail One-tap sub plans that convert a lesson into a print-ready packet Transparent hours-reclaimed ledger with conservative, teacher-adjustable math Built with React 19 TypeScript Vite Tailwind CSS v4 Radix UI TanStack Query TanStack Router Zustand Capacitor Firebase Cloudflare Workers i18next Worth knowing edtech k12 teacher-saas ai-copilot iep ferpa cross-platform sped What exists of Corra today? Research, branding, a decision-complete architecture and a running design-system scaffold. The record calls the status architecture-planning phase and says the twelve functional modules are specified but not yet built, with no confirmed live address or store listing. So none of the teacher workflows described below are running. That is the first thing to say about it, and saying it anywhere other than first would make everything after it misleading. What is the shared student record actually for? Removing re-keying. A teacher plans a lesson, then needs a differentiated variant, a quiz, a rubric, a substitute plan and a gradebook row, and in ordinary tool-sprawl each of those is entered again somewhere else. If they all read and write one record, the lesson flows into its derivatives without being retyped. That is the whole architectural bet, and it is why the record is designed before the modules rather than accumulated by them. How is the AI copilot meant to handle student data? By not sending it. Every call is designed to route through a gateway that de-identifies by default, so raw student information does not reach a model. That is a structural choice rather than a policy sentence, and on a product touching a child's education record the difference is the entire compliance position. It is designed and specified. Like the rest of the modules, it is not built, and this page will not describe it as though it runs. Why start with the architecture rather than a module? Because the shared record cannot be retrofitted. Twelve modules built independently produce twelve schemas that almost agree, and reconciling them afterwards is a rewrite rather than a refactor. The same is true of the de-identification gateway: routing every model call through one place is a decision made before there are calls, not after. Front-loading both is slower to demonstrate and is the only order in which either works. Does Corra work on iPhone? Nothing is running yet on any platform. The architecture targets the web as an installable application, mobile through Capacitor, and desktop through Tauri from one codebase, which describes intent rather than anything published. There is no Apple release, no Apple Developer account behind this work, and nothing I build has shipped to the App Store. The record also confirms no live address and no store listing, so there is currently nothing to open anywhere. ## DoorNest — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.doornest A cross-platform Flutter app that matches roommates by lifestyle, then helps them split rent, rotate chores, and live well together. Most roommate products solve the introduction and stop. That is the easy half. Two people meet. They get on well enough over coffee and they sign something. Then they discover that one of them gets up at six and the other goes to bed at two, that cleanliness meant different things to each of them, and that nobody agreed in advance who buys the washing-up liquid. DoorNest is built around the second half. Matching comes first, and it is ranked rather than ordered by whoever appeared most recently. A lifestyle quiz scores people on sleep, cleanliness, budget and schedule, which are the four things that actually generate friction between housemates. It is not a subtle insight. It is simply the thing swipe-ordered products do not do, because ranking is harder than listing. Then the product carries on past the match, which is the unusual part. Rent and expenses split between people. Chores rotate on a schedule that moves rather than a rota somebody drew once. House rules are voted on as a group, so the arrangement is a constitution rather than one person's preferences enforced by mood. There is a shared list, a calendar and chat. Trust is handled structurally. Nobody is simply asked to be careful. Identity verification exists. Past housemates can leave reviews, and vouching lets somebody stake their own credibility on another person, which is a stronger signal than a rating because it costs the person giving it something real. Contact details stay hidden until they need not be. Anonymous reporting is there for when something goes wrong. Those exist because the product puts strangers into a shared home, which is a category of risk that a rating out of five does not cover. Underneath it is Flutter with a Supabase backend, and a schema defined across twenty-five migrations. Twenty-five is a lot. It is a fair proxy for how much of this is data modelling rather than screens. Roommate matching looks like a matching problem and turns out to be a household problem, with expenses, rotas, votes, reviews and roles all referencing each other. One decision matters most. Every domain has both a live implementation and a mock one behind the same interface, so the entire application runs with zero credentials configured. Development never stalls waiting for an account, and the seam where a real service arrives is designed deliberately rather than cut later under time pressure. Most projects discover that seam during an integration and regret where it landed. There is a second consequence of the mock layer that is easy to miss. A product that runs without credentials can be demonstrated to somebody before any account exists, which means the design gets tested against real reactions early rather than after the integrations are paid for. That ordering is unusual and it is the right way round. What it cost is the wiring. Live authentication, push, maps and payments are all still to be connected to anything real, and neither the web address nor a store listing is confirmed anywhere on the record. The record says so, and this page repeats it rather than describing a built application as a running one. Credential-gated is a precise description, and it is more useful to a reader than calling the whole thing in progress. One says which part is missing. The other says only that something is. Open the live site What it does, and what that costs to build Lifestyle-based roommate matching via a personality quiz Rent and expense splitting between housemates Rotating chore schedules and a shared calendar House-rules constitution with group voting ID verification, vouching, and anonymous reporting Reviews from past roommates with hidden contact details Role dashboards for owners, brokers, verifiers, and admins In-app chat, notifications, and a joint shopping list Built with Flutter Dart Riverpod go_router freezed Supabase PostgreSQL FlexColorScheme dartz Material 3 google_sign_in Sentry Worth knowing roommates shared-living flutter supabase rent-splitting co-living cross-platform proptech What state is DoorNest in? Early, and credential-gated, which the record states plainly. The application is built and the schema behind it is defined across twenty-five migrations, but live authentication, push, maps and payments are not yet wired, and neither the web address nor any store listing is confirmed live. So the shape is real and the connections to the outside world are not. That is a specific kind of unfinished and it is worth naming precisely rather than calling it in progress. How does the matching work? A lifestyle quiz ranks people by sleep pattern, cleanliness, budget and schedule rather than by who appeared first. That ordering is the whole argument: the reason shared living goes wrong is rarely that two people disliked each other on sight, it is that one of them gets up at six and the other goes to bed at two. Ranking on the things that actually cause friction is cheap to do and almost nobody does it. What happens after two people match? That is the part most matching products skip. Rent and expenses get split, chores rotate on a schedule, house rules are voted on as a group, and there is a shared shopping list and a calendar. Matching is a single moment; living together is every week afterwards. A product that stops at the introduction has solved the easy end of the problem and handed the difficult one to a group chat and a shared spreadsheet nobody updates. How can the whole app run with no credentials? Every domain has two implementations, one live and one mock, behind the same repository interface. The application asks for a repository and gets whichever is configured, so the entire product runs end to end with nothing provisioned. That is unusually disciplined and it pays twice: development does not stall waiting for an account, and the seam where a real service plugs in is designed rather than discovered later under pressure. Does DoorNest work on iPhone? There is no iOS release. It is built in Flutter targeting Android, iOS and web from one codebase, so the code exists for that platform, but no store listing is confirmed on either and there is no Apple Developer account here. Nothing I build has shipped to the App Store. The distinction matters on this entry more than most: a cross-platform framework means the build is possible, not that anything has been published. ## MRO Express — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.mroexpress A B2B parts-sourcing and on-demand delivery platform for HVAC, plumbing, and electrical contractors. A part fails on a job and the day stops. Not for the repair, which is often quick. It stops for the sourcing: identifying the exact replacement from a worn label, then finding who has one, at what trade price, near enough to collect. That is usually a sequence of telephone calls made while somebody stands in a plant room waiting. Contractors call it the scavenger hunt, and it is the target here. MRO Express is designed to collapse it: photograph the broken part, have it identified, compare live stock and contractor-tier pricing across distributors, and have a courier bring it to a coordinate on a job site rather than an address somebody has to leave to visit. That is the design. Here is what is running. An application shell with theming and observability. A marketing home built to be read in field conditions. The provider-contract scaffold beneath them both. The record marks identification, aggregation, courier dispatch, payment splitting and accounting sync as roadmap, tracked as numbered work still to do. None of the interesting half is live, and the record says so about itself before anybody else can. The scaffold is the interesting part. It is also an unusual choice. Every external dependency sits behind a typed contract with two implementations: a free mock and a real one, selected by configuration. Five domains are built that way, covering vision, catalogue, courier, payments and accounting. That has two consequences and both of them matter. The whole product builds and demonstrates end to end with nothing provisioned and nothing charged, so development never waits on a commercial agreement with a distributor. And each domain flips to live independently, so the integration seam is designed deliberately rather than cut later under the pressure of a signed contract and a deadline. Mock-first is slower at the start and it is the difference between a demo and a foundation. Supabase carries the entire backend, and the domain is the reason. Orders and pricing are relational. Matching a photographed part against a catalogue is a vector problem. Dispatching to a job site is geospatial. Those are three different capabilities, and Postgres carries all three through extensions rather than requiring three services with synchronisation between them. Row-level security, realtime updates and server-side functions come from the same system. So the backend is one thing. On a platform that would eventually settle money between a distributor, a courier and itself, the number of systems that must agree about who is allowed to see what is a real risk, and keeping it at one is the cheapest reduction available. One more decision follows from working in the field rather than at a desk, and it shows up in the smallest places. The shell carries theming with light and dark modes and accent and font switching, plus a loader that prevents the wrong theme flashing before the first paint. On a phone held in a plant room those are not decoration. Readability under bad lighting, in gloves, at arm's length is the difference between a tool somebody uses and one they put back in the van. What it cost is excitement. A product whose whole promise is the roadmap is difficult to write about without borrowing its future tense. The parts that exist are a shell and a set of contracts. That is honest and unexciting, and the alternative would be a page describing an identification feature that has never seen a photograph. Open the live site What it does, and what that costs to build Themeable app shell with dark/light mode, accent and font switching, and anti-FOUC loader AI Lens to identify a part from a photo via OCR and pgvector semantic matching (roadmap) Multi-distributor catalog aggregator with live stock and contractor-tier pricing (roadmap) On-demand courier dispatch with live position and status tracking via Realtime (roadmap) Stripe Connect escrow split settlement across distributor, courier, and platform (roadmap) QuickBooks / Xero accounting sync per order (roadmap) Mock-first integration spine with typed provider contracts selectable by env Env-gated logger, error, and analytics observability that never blocks boot Built with React 19 TypeScript Vite 8 Tailwind CSS v4 Radix UI TanStack Router TanStack Query Zustand Supabase Capacitor 8 D3 Dexie Worth knowing b2b marketplace capacitor supabase react hvac parts-delivery on-demand What works in MRO Express today? A themeable application shell, a marketing home built for field conditions, and the provider-contract scaffold underneath both. The record marks the identification, aggregation, courier, payment and accounting features as roadmap, and tracks them as numbered work still to come. So the parts-sourcing product described below is designed and not running. The record calls its own state honest, and repeating that honestly here is the least this page can do. What is the parts scavenger hunt? It is the hour a contractor loses finding a component. A part fails on a job, the exact replacement has to be identified from a worn label, and then it has to be located across several distributors with different stock and different trade pricing, usually by telephone, usually while somebody waits. The work itself is quick. The sourcing around it is what actually consumes the day, and it is the thing this platform is aimed at. What does mock-first mean and why does it matter? Every external dependency sits behind a typed contract with two implementations: a free mock and a real one, chosen by configuration. Five domains are arranged that way. The product therefore builds and demonstrates end to end with nothing provisioned and nothing charged, and each domain can be switched to live independently. It means development never waits on a commercial agreement, and the integration seam is designed rather than discovered later. Why Supabase for all of it rather than several services? Because the domain needs several database capabilities that happen to live in one place. Relational data for orders and pricing, vector search for matching a photographed part against a catalogue, and geospatial queries for dispatching to a coordinate are three different problems, and Postgres carries all three with extensions. Row-level security, realtime updates and server-side functions come from the same system, so the backend is one thing rather than four with a synchronisation problem between them. Does MRO Express work on iPhone? There is no Apple release. One codebase targets web and mobile through Capacitor, and the record lists iOS among its intended targets, which describes configuration rather than anything published. No Apple Developer account sits behind this work and nothing I build has shipped to the App Store. What exists to open is the web address, which a phone browser reaches like any other page. ## Nursana — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.nursana Cross-platform SaaS for nurses bundling calculators, credential tracking, an on-device shift brain sheet, wellbeing, and more into one app. A nurse's phone already has six applications on it and none of them talk. One for the drug calculation. One tracking when a licence expires. A notes application standing in for a handover sheet. Something for the practice questions. And the things with no application at all, which end up on paper in a pocket and go through the wash. Nursana is an attempt to make that one thing. Nine modules, and the list reads oddly until you notice what they have in common: offline calculators for the point of care, a wallet tracking licences and continuing education, an on-device shift sheet, anonymous wellbeing check-ins, schedule tracking with fatigue flags, practice questions, finance tools, a private incident vault, and a verified community. They have in common that each one is a thing somebody currently improvises. The improvised versions are a calculator application, a phone reminder, a folded piece of paper and a group chat. None of those were designed for the job, and the paper one goes home in a uniform pocket carrying information it should not be carrying. Which is why the brain sheet stays on the device. It holds patient information, and the design keeps that off servers entirely rather than protecting it on them. There is an export in a handover format for when it needs to leave. That constraint is not a compromise made to save money. It is the thing that makes the feature safe enough for one person to have built. The product draws three lines and stays inside them. Reference rather than advice. Wellness rather than therapy. Documentation rather than legal guidance. Each marks where the software stops and a human takes over. Those lines matter more than any module above them. A calculator returns a number and does not decide whether a drug should be given. A check-in records how somebody is and does not treat them. A vault holds a record of what happened and does not say what anybody's rights are. Software for a clinical audience that blurs those boundaries is not being helpful, it is accumulating liability on behalf of its user. The wellbeing check-ins are anonymous by design, and route outward. Where a response indicates someone needs more than an application, the design points to external help. The important word is external. Noticing and pointing outward is a legitimate thing for software to do; responding is not, and the distinction is the difference between a wellbeing feature and one pretending to be a service it is not. There is a reason a subscription product for nurses is difficult to build on a free tier, and it shapes the module list. Anything genuinely clinical would need a maintained data source, a validation process and somebody accountable for it. So the modules that exist are the ones where correctness lives in arithmetic and in the user's own records rather than in a body of knowledge somebody has to keep current. That is a real boundary on what this can become, and it is drawn deliberately rather than discovered later. What it cost is time. This is early. The architecture, the branding, the design system and the module scaffolding exist. The platform layer and the modules themselves are being completed in waves, there is no confirmed address and no store listing, and nobody is relying on any of it during a shift. On a product for clinicians that gap is the most important thing on the page, and it belongs near the top as well as here. Open the live site What it does, and what that costs to build Offline point-of-care calculators for drip rates, weight-based doses, and conversions Credential wallet tracking nursing licenses, certifications, and CE renewals On-device shift brain sheet with EHR-ready SBAR export and zero PHI on servers Anonymous-by-design wellbeing check-ins with high-risk routing to external help Schedule tracking with fatigue-safety and circadian flags NCLEX-style practice questions with rationales for students and new grads Private advocacy and incident vault with state-rights references Runtime theme customizer with five accent palettes and light/dark support Built with React 19 TypeScript Vite 6 Capacitor 8 Tailwind CSS v4 Radix UI Zustand TanStack Query React Router 7 Firebase (Firestore, Auth, Functions) Cloudflare Workers D3 Worth knowing nursing healthtech saas cross-platform capacitor react firebase privacy-first What state is Nursana in? Early. The record puts it at an initial version with the architecture, branding, design system and module scaffolding in place, while the platform layer and the feature modules are still being completed in waves. There is no confirmed live web address and no published store listing. So the nine modules described below are designed and scaffolded rather than finished, and nothing here should be read as a tool anybody is currently relying on during a shift. What are the safe lanes? Three boundaries the product is designed inside: reference rather than advice, wellness rather than therapy, and documentation rather than legal guidance. Each marks where the software stops. A calculator gives a number and does not decide whether to give a drug. A check-in records how somebody is and does not treat them. A vault holds a record of an incident and does not tell anybody what their rights are in law. Why is the brain sheet on the device? Because it holds patient information and patient information does not need to be on anybody's server. A shift brain sheet is the working note a nurse keeps through a shift, and the design keeps it local, with an export in a handover format when it needs to leave. Nothing about that is a limitation being worked around. It is the reason the feature can exist at all inside a product built by one person. How do the wellbeing check-ins work? Anonymously by design, with routing to external help where a response indicates it. The important word is external: the product's role is to notice and to point outward, not to respond itself, because responding would be therapy and this is not that. What that routing points to is not something this page will state, since a support contact printed in a portfolio and left there is worse than no contact at all. Does Nursana work on iPhone? There is no Apple release. One codebase targets web, mobile through Capacitor and desktop, and the record lists iOS among its intended targets, which describes configuration rather than something published. No Apple Developer account sits behind this work and nothing I build has shipped to the App Store. The record also confirms no live address and no store listing, so there is nothing to open on any platform yet. ## OrbitCubs CRM — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.orbitcubs A three-surface CRM (web, Android, browser extension) for tracking contacts, deals, and pipelines for solo operators and small teams. A CRM is only as good as the moment somebody actually updates it. Everything else is downstream of that. The pipeline view, the reporting, the reminder that a deal has gone quiet: all of it assumes the record is current, and the record is current only if updating it was easier than not bothering. Most customer relationship software loses at exactly that point rather than at any of the ones its feature list is about. So this one is three surfaces rather than one. There is a web application. There is an Android build. And there is a browser extension that captures contacts and notes from whatever page is open. They are not three products. They share one core of services, so how a deal is stored is decided once and every surface inherits it, which is the only version of this arrangement that does not end in reconciliation work. The extension is the surface that answers the update problem. A contact usually appears while you are reading something else. A profile, a company page, an email in a browser tab. A CRM that requires leaving that page, finding the right screen and retyping what is already on screen is a CRM updated at the end of the week, from memory, badly. Capturing in place removes the switch, and the switch is the whole cost. What each surface is for differs, and pretending otherwise would waste them. The extension captures. The phone is for checking something between two meetings. The web application is where pipeline work actually happens, because that is work with a keyboard and a wide screen behind it. Building the same interface three times would have produced three mediocre versions of one. Underneath sits a client-side architecture with no serverless functions. Firebase holds authentication and the database, uploads go to FilesHub rather than cloud storage, and device state uses the platform's own preferences. The client talks to the database directly, under its rules. That choice moves the security boundary somewhere specific and it is worth being explicit about where. With no trusted middle layer, the database rules are the boundary. There is no server-side code to check a caller's intent afterwards, so a rule that filters instead of refusing, or a list query that does not prove its own permission from its own filters, is not a subtle flaw. It is the whole gate. Building this way is cheaper to run and less forgiving of a careless query, and the second half of that sentence is the part that matters. There is a documentation repository beside the product, which is less common than it should be. A CRM built for solo operators and small teams gets handed over, or picked up again after six months away. Both are the same problem: somebody needs to understand a system nobody is available to explain. Documentation written while the thing is being built is documentation that matches it. Written afterwards, it describes what somebody remembers. What it cost is confirmation. The web application is live. The record does not confirm that either the Android listing or the extension listing has been published, and this page will not fill that in on the record's behalf. Three surfaces built is a different claim from three surfaces shipped, and the difference is exactly the kind a portfolio blurs when nobody is checking. Open the live site Documentation What it does, and what that costs to build Contact tracking and management Deal and sales pipeline tracking Browser extension to capture contacts and notes from any web page Capacitor Android build with feature parity Firebase Auth sign-in across surfaces Client-side architecture with FilesHub for uploads Form handling with react-hook-form and zod validation Shared core services across web, mobile, and extension Built with React 19 TypeScript Vite Firebase Firestore TanStack Router TanStack Query Zustand Radix UI Tailwind CSS Capacitor WXT Worth knowing crm contact-management pipeline browser-extension capacitor firebase react sales Who built OrbitCubs CRM? Ahsan Mahmood built it alone, and the web application is at orbitcubs.aoneahsan.com. One person wrote the shared service layer, the web interface over it, the Android packaging and the browser extension that captures contacts from a page. There is a separate documentation repository as well. Building three surfaces over one core is a decision that only pays off if the same person holds all three, because the moment they diverge somebody has to reconcile them. What does the browser extension actually do? It captures a contact or a note from whatever page you are looking at, without leaving it. That is the whole argument for the extension existing: a CRM that requires you to switch tabs and retype what you just read is a CRM that gets updated later, which usually means never. Whether any given site's markup gives up something useful is a question about that site rather than about this extension, and no claim is made here on their behalf. Are all three surfaces the same application? They share the same core services rather than the same interface. The web app, the Android build and the extension each read and write through one service layer, so a change to how a deal is stored happens once instead of three times. What differs is what each surface is good for. The extension captures, the phone checks something between meetings, and the web application is where actual pipeline work gets done. Where is the data and what holds the files? Firebase holds authentication and the database, and uploads go to FilesHub rather than to cloud storage. There are no serverless functions in the architecture, so the client talks to the database directly under its rules rather than through a trusted middle layer. Local storage on device uses the platform's own preferences store. That is a client-side design with the constraints a client-side design brings, and the security rules are where the boundary actually lives. Does OrbitCubs CRM work on iPhone? No, there is no iOS build. The web application opens in a phone browser and the packaged build is Android through Capacitor, because there is no Apple Developer account here and nothing I build has shipped to the App Store. The record also does not confirm that either the Play listing or the extension store listing has been published, so the web address is the one surface this page can point at with confidence. ## Polymath AI Workspace — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.polymath A document-based AI workspace with four lenses — Study, Career, Knowledge, and Document-Pro — over one shared RAG core. Four different tools, and underneath they are the same tool. Studying from a textbook. Keeping notes that can be found again. Preparing for an interview against a job description. Reading a contract for the clause that will cause trouble. Each of those starts by taking a document apart so it can be asked questions. Polymath builds that part once and puts four faces on it. A file arrives, whether a PDF, a word processor document, plain text or a clipped web page, and the core parses it, splits it into chunks, embeds those and classifies the content. Everything above that is an interface onto the same store. The four lenses are Study, Knowledge, Career and Document-Pro. Study carries spaced-repetition cards on a scheduling algorithm, timed exams and hands-free audio review. Knowledge does semantic search across notes with suggested links and a daily review feed. Career handles tailoring a resume, drafting a cover letter, mock interview chat and a Kanban tracker for applications. Document-Pro scans contracts and financial documents for risk and compares one document against another. One core, four faces. That is the reason one person could build all of them. It is also the reason a document uploaded for one purpose is immediately available to the others, which is a property nobody would build deliberately across four separate applications and which turns out to be the useful part. Retrieval answers with citations that deep-link to the exact chunk. That precision is the whole difference between a citation and a gesture at one. An answer pointing at a hundred-page document has told you almost nothing about where its claim came from, and checking it means reading the document again, which is what you were trying to avoid. Pointing at the passage makes verification a click. For a system that can be confidently wrong, that is not a nicety. The key model is bring-your-own. That decides what this can be. A retrieval product makes a paid call every time somebody asks something, which is the cost structure that quietly kills side projects. Here the user supplies their own key, held encrypted and decrypted only on the server rather than in the browser, so per-call costs sit with the person making the calls. That is what makes it operable by one person at all. A stored key is a stored credential, so the encryption is not decoration either. It is the reason asking somebody for their key is a reasonable request rather than an alarming one. There is a design consequence of the shared core that is worth naming, because it cuts both ways. Four lenses over one store means a change to how documents are chunked or embedded affects all four at once. That is efficient when the change is an improvement and unforgiving when it is not, so the core is the part of this codebase where a mistake is most expensive. Building it first and putting interfaces on afterwards is the only sequence in which that risk is visible. What it cost is that none of it is deployed. The record is precise about this and calls its own status honest: wired end to end, typecheck and build and continuous integration passing, no live address, no store listing. Compiling is not shipping. It is a genuine milestone on a product this size and it is not the same as somebody being able to use it, and this page keeps the two apart. Open the live site What it does, and what that costs to build Document ingest and RAG chat with citations that deep-link to the source chunk FSRS spaced-repetition flashcards, timed exams, and hands-free audio review Semantic note search with AI-suggested links and a daily-review feed Resume tailoring, cover letters, mock-interview chat, and a Kanban application tracker Contract, finance, and general document risk scanning with doc-vs-doc comparison Bring-your-own-OpenAI-key vault with AES-GCM encryption, decrypted only server-side Stripe billing with tiered plans and a role-gated admin panel One codebase shipped to web, iOS, Android, and desktop Built with React 19 TypeScript Vite Supabase PostgreSQL pgvector Deno Edge Functions Capacitor Tauri Tailwind CSS shadcn/ui Stripe OpenAI Zustand TanStack Query Worth knowing ai-workspace rag supabase pgvector spaced-repetition document-ai capacitor tauri What state is Polymath in? Wired end to end, with typecheck, build and continuous integration passing, and not deployed anywhere. The record is unusually precise about this and calls its own status honest. So the four lenses described below exist as code that compiles rather than as a product anybody is using, and there is no live address and no store listing. Working and shipped are different claims and this page keeps them apart. Why four different products on one core? Because they are the same problem seen from four angles. Studying, keeping notes, preparing for interviews and reading contracts all begin with documents that have to be parsed, split, embedded and made searchable. Building that ingest core once and putting four interfaces on it is far less work than four applications, and it means a document uploaded for one purpose is already available to the others. What does a citation deep-link to? The exact chunk it came from, not the document. That distinction is the difference between a citation and a gesture at one. An answer pointing to a hundred-page PDF has told you almost nothing about where it got its claim, and it cannot be checked without rereading. Pointing at the specific passage makes verification a click, which is the only version of a citation worth having in a system that can be confidently wrong. Whose OpenAI key does it use? Yours. The design is bring-your-own-key, with the key held encrypted and decrypted only on the server side rather than in the browser. That has two consequences: per-call costs belong to the person making the calls rather than to whoever runs the service, and a product that would otherwise be unaffordable to leave running becomes possible for one person to operate. The encryption detail matters because a stored key is a stored credential. Does Polymath work on iPhone? Nothing is deployed anywhere yet. The codebase targets web, mobile through Capacitor and desktop through Tauri, and the record lists iOS among the intended targets, which describes configuration rather than a published application. No Apple Developer account sits behind this work and nothing I build has shipped to the App Store. There is no live address and no store listing on any platform today. ## Safar — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.safar A safety-first ride-hailing platform for riders, drivers, and fleet operators, built from one cross-platform codebase. Ride-hailing is three products. They wear one name. A rider wants a car now and a price they can trust. A driver wants work, a route and to be paid correctly. A fleet operator wants to know where fifteen vehicles are and which of them is losing money. They are looking at the same trips and they are not looking at the same product. Safar is built as those three from one codebase. The roles came first. That ordering is the whole engineering argument of this entry so far. Authentication with role-based access control, a shell that changes shape depending on who signed in, and separate dashboards for riders, drivers, fleets and administration. Underneath sits a typed shared layer for users, vehicles, trips and pricing, so all four surfaces agree about what a trip is rather than each maintaining its own quietly divergent idea of one. Doing the roles first is unglamorous and it is correct. Access control that reaches every screen cannot be added once the screens exist. A product that builds booking first and discovers the role model afterwards ends up checking permissions in the interface. That is not a boundary, and the fix is a rewrite rather than a patch. Now the part that matters most on this page. Nobody can book a ride. Live geography, the booking flow, the fare engine and payments are documented roadmap rather than shipped functionality. The address on the record, safar.aoneahsan.com, answers. There is no store listing on any platform. A ride-hailing product that cannot hail a ride is an odd thing to publish, and publishing it accurately is the only version worth doing. The safety features are named and they are not built. An SOS control. Trusted contacts. A fare locked at the moment of booking rather than recalculated afterwards. Those three are the reason somebody would choose this over an incumbent, and they are precisely the features a portfolio page is tempted to write in the present tense. They are intent. Nothing here protects anybody yet. There is a health page, and it is more interesting than it sounds. It reports which integrations are actually configured. A product leaning on maps, payments and a realtime database can boot and appear healthy while three of them are unwired, and the symptom appears much later as behaviour nobody can reproduce. Making that visible on a page turns a silent condition into a stated one. Guest-mode boot follows the same principle: start, then say what is missing. Internationalisation is in from the beginning, with browser language detection. That is the same reasoning as the roles. Adding a second language to a finished interface means revisiting every string and every layout assumption, and mobility products are used by people who did not choose the language the developer thought in. Two more things are real, and both are the kind that get skipped. The interface kit is accessible by construction rather than audited afterwards, and forms are typed and validated rather than trusted. Neither is visible in a screenshot. Both are the sort of decision that is cheap now and expensive to retrofit into forty screens later, which is the same argument as the roles and the languages. What it cost is that this is a foundation described honestly rather than a service. The dashboards render, the roles hold, the data layer is typed and the design system is deliberate. None of that moves a person from one place to another. Saying so plainly is worth more than a page that reads like a product and disappoints the first person who tries to use it. Open the live site What it does, and what that costs to build Auth with role-based access control and a role-aware app shell Separate rider, driver, fleet, and admin dashboards Guest-mode boot with a /health page showing which integrations are configured Accessible Radix/ShadCN UI kit with react-hook-form + Zod forms Typed shared domain layer for users, vehicles, trips, and pricing Class-based dark mode with runtime-remappable accent colors Internationalization via i18next with browser language detection Planned safety features: SOS, trusted contacts, and fares locked at booking time Built with React 19 TypeScript Vite 6 Tailwind CSS v4 Radix UI React Router 7 react-hook-form Zod TanStack Query Firebase Cloudflare Workers Capacitor 7 Tauri 2 Stripe Worth knowing ride-hailing mobility react typescript capacitor tauri firebase cross-platform Can anybody book a ride with Safar today? No. The record is direct about this: live geography, booking, the fare engine and payments are all documented roadmap rather than shipped, and there is no confirmed live web address or store listing. What exists is authentication, role-based access control, the four dashboards and a typed shared data layer underneath them. A ride-hailing product that cannot yet hail a ride is an unusual thing to publish, and describing it any other way would be worse. Why build three surfaces before the rides work? Because the roles are the hard structural decision and retrofitting them is painful. A rider, a driver and a fleet operator see genuinely different products with different permissions over the same trips, and access control that reaches every screen has to be designed before there are screens. Getting the shape right first is unglamorous sequencing. Building the booking flow first and discovering the role model afterwards is how systems end up with permissions checked in the interface. What is the health page for? It shows which integrations are actually configured, which sounds mundane and prevents a specific failure. A product depending on maps, payments and a realtime database can appear to run while three of them are unconfigured, and the symptom surfaces much later as behaviour nobody can reproduce. A page that states what is connected turns an invisible condition into a visible one. Guest-mode boot exists for the same reason: the application starts and tells you what it is missing. What are the safety features and are they built? They are named and they are not built. The record lists an SOS control, trusted contacts and fares locked at the moment of booking as planned features. Those are the parts that would make the product worth choosing, and they are exactly the parts a page like this is tempted to describe in the present tense. They are design intent. Nothing here claims any of them protects anybody today. Does Safar work on iPhone? There is no iOS release. One codebase targets web, mobile through Capacitor and desktop through Tauri, and the record lists iOS among the intended mobile targets, which describes configuration rather than a published application. There is no Apple Developer account here and nothing I build has shipped to the App Store. The record carries a web address at safar.aoneahsan.com and no store listing on any platform, so a browser is the only way in. ## SNGDC — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.sngdc A synthetic natural gas distribution utility platform: public corporate site, customer self-service portal, and a no-code admin studio. A utility has three audiences. They want nothing in common. The public wants to know what the company does and whether it is hiring. A customer wants a bill, a usage history and somebody to complain to. Staff want to change what the first group reads and manage what the second group is billed, without asking an engineer to deploy anything. Most organisations answer that with three separate systems. SNGDC answers it with one codebase carrying three surfaces: a public corporate site with services, careers, investor relations and the legal pages; a customer self-service portal covering bills, usage analytics drawn as charts, notifications and support tickets; and a role-based operations studio where staff run the other two. The studio is the load-bearing part. It decides whether the other two stay true. More than a dozen content areas of the public site are editable directly, with no code and no deployment. Societies, customers and billing are managed in the same place, behind a permission matrix covering five roles. No-code sounds like a convenience and is actually a maintenance argument. Every content change requiring an engineer is a content change that waits. The waiting ones accumulate quietly until the site is describing a company that has moved on, and nobody notices because no single postponement was unreasonable. Removing the engineer from that loop is what keeps a corporate site accurate in its second year. The sensitive material gets its own path. A utility signup involves identity documents and property paperwork, which is the most consequential data in the system. Those go to a dedicated file service rather than sitting beside ordinary application data, and identifying information is stripped before anything reaches analytics. People forget the second half. Analytics is where personal data leaks by accident rather than by decision, because nobody thinks of a page view as carrying any, and a redaction applied at the boundary is the only version that holds when a new event is added six months later by somebody who was not thinking about it at all. The architecture is free-tier throughout. There are no serverless functions. Authentication, the database and hosting sit on a free tier, uploads go to that separate file service, and the client talks to the database directly under its own rules. For a platform of this size that is an aggressive constraint, and it is the reason the whole thing can exist without an operating budget behind it. One more property follows from having no serverless functions, and it is worth stating because it constrains the design rather than decorating it. With no trusted middle layer, the database rules are the access boundary. A five-role permission matrix is therefore enforced where the data is rather than in the interface that displays it, which is the only place a permission model actually holds when somebody reaches the system by a route nobody designed for. What it cost is a link. There is nowhere to send you. The record carries no address, and the candidate held elsewhere in my own notes does not resolve when it is asked. A utility platform lives behind a customer login rather than on the open web, so an absent public address is a good deal less surprising here than it would be almost anywhere else. It is still an absence. A page implying otherwise would be the easiest kind of dishonesty to get away with. What it does, and what that costs to build Public corporate site with hero slides, services, careers and investor relations Customer portal for bills, D3 usage analytics, notifications and support tickets No-code content-management studio for 12+ public-site domains RBAC admin/operations area with a 5-role permission matrix Google sign-in (Firebase popup on web, native plugin on mobile) Full Radix theme customizer with cross-device sync via Firestore Native Android/iOS build with 30+ Capacitor plugins Sensitive-document handling (CNIC + property docs in FilesHub, PII redaction) Built with React 19 TypeScript Vite 8 Radix UI Tailwind CSS 4 Firebase (Auth + Firestore) Capacitor 8 Zustand react-hook-form + Zod D3.js TipTap FilesHub Worth knowing utility platform gas distribution customer portal react capacitor firebase admin dashboard content management Where can I see SNGDC? Nowhere from this page. The project record carries no address in any slot, and the one candidate address held elsewhere in my own records does not resolve, so nothing here points anywhere. A utility platform is also the kind of system that lives behind a customer login rather than on the open web, so an absent public link is less surprising than it would be for a consumer product. It is still an absence and the page states it. What does no-code mean in an operations studio? That staff change the public site without a developer or a deployment. More than a dozen content areas are editable directly, which sounds like a convenience and is actually the difference between a site that stays current and one that goes stale in the second month. Every content change that requires an engineer is a content change that gets postponed, and the postponed ones accumulate until the site is describing a company that has moved on. How are the sensitive documents handled? A utility signup involves identity documents and property paperwork, which is the most sensitive material in the whole system. Those go to a dedicated file service rather than sitting alongside ordinary application data, and identifying information is stripped before anything reaches analytics. The second half matters as much as the first: analytics is where personal data leaks by accident, because nobody thinks of a page view as carrying any. Why does a corporate utility site need a theme customiser? It does not, strictly, and it has one because the portal half is used repeatedly rather than visited once. A customer checking a bill returns every month, and a member of staff working in the operations studio is in it all day. Preferences that follow somebody across devices are worth more in that setting than on a page somebody reads once. The corporate site simply inherits it. Does SNGDC work on iPhone? There is no Apple release from me. The platform is a web application packaged for Android through Capacitor, and the record lists iOS among the intended native targets, which describes configuration rather than a published application. No Apple Developer account sits behind this work and nothing I build has shipped to the App Store. With no live address on the record either, there is currently nothing to open on any platform. ## Trialith — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.trialith Clinical-trial protocol intelligence SaaS for pharma sponsors and CROs — audit, simulate, and compile protocols into CDISC-ready EDC schemas. A clinical trial protocol is a long document that people read and then retype. It describes who is eligible, what happens at each visit, when those visits fall, what is measured and how the result will be judged. Then somebody reads it and builds the database that will collect the data, by hand, from the prose. Two expensive things follow from that. The first is that the retyping is slow, and a database build measured in weeks sits between a finished protocol and a study that can start. The second is worse: a document read by humans keeps its contradictions. Visit windows that do not quite line up. Eligibility criteria that conflict on an edge case. A statement in one section disagreeing with a statement in another. Those inconsistencies surface later as amendments. An amendment is not a correction in a file. It means re-approval, re-training the sites running the study, and frequently re-consenting the people already enrolled in it. A meaningful share of them are caused by problems that were in the document from the first version and were simply not spotted by anybody reading it linearly. Trialith's proposal is to treat the protocol as data instead. If the protocol is a structured artefact on a standard model rather than prose, then three things become mechanical. It can be audited for internal contradiction. Its statistics can be computed from it, including simulated scenarios. And the data capture schemas can be generated rather than assembled by a person reading it. Three engines, one for each. Now the necessary sentence. None of them is built. The record puts this at Phase 0, with the foundation, the design system and a marketing landing page shipped and all three engines specified and on the roadmap. Everything above describes a design. That matters more than usual here. The audience is pharmaceutical sponsors and the organisations that run trials for them, which is an audience that evaluates claims for a living. A page overstating what exists would be found out immediately and would cost more than the honest version does. Underneath, the foundation constrains everything else. The protocol workspace is modelled on the industry's canonical standard rather than on a shape invented here, which is the decision that makes generated output usable by systems nobody controls. Access is enforced at the database, and the AI gateway is provider-swappable with the key supplied by the user. No patient data enters it. The design keeps it out entirely: a protocol describes how a study will run rather than who is in it, so the product works on study design rather than on any person enrolled in one. Private storage and row-level access are in the architecture anyway, because an unpublished protocol is commercially sensitive even when it contains nothing about anybody. There is a second reason the standard model came first, and it is about who has to accept the output. A schema this generates is consumed by systems built by other companies, to a specification nobody here controls. Inventing a convenient internal shape and translating at the boundary would have been faster to build and would have moved every compatibility problem to the point where it is most expensive to discover. Conforming from the start is slower and it is the only version that could ever be adopted. What it cost is proof. Three engines are named on this page and none of them runs. Building the standard model, the design system and the foundation first is the correct order, and it produces by some distance the least demonstrable milestone available. That is the accurate state. It is not a satisfying one. Open the live site What it does, and what that costs to build PACE engine: protocol consistency audit with visit-window, contradiction, and eligibility flags SIM engine: analytic power/sample-size and in-browser Monte-Carlo simulation COMPILE engine: USDM to ODM v2.0 and CDASH eCRF generation Protocol workspace on the CDISC USDM canonical model Branded landing page with a Schedule-of-Activities compiling animation No-PHI-by-design, HIPAA-ready architecture with RLS and private buckets Provider-swappable bring-your-own-key AI gateway Capacitor Android shell with Trapeze-managed native config Built with React 19 TypeScript Vite Tailwind CSS v4 Radix UI / shadcn TanStack Router Zustand Supabase Cloudflare Workers Capacitor react-hook-form Zod Worth knowing clinical-trials cdisc edc healthtech b2b-saas supabase cloudflare-workers react What exists of Trialith today? The foundation, the design system and a marketing landing page. The record calls this Phase 0 and says the three engines are specified and on the roadmap rather than shipped. So the protocol auditing, the simulation and the schema compilation described below are designed and not running. On a product aimed at pharmaceutical sponsors that distinction is not a nicety, since the people evaluating it would check. What does treating a protocol as data change? Everything downstream of it. A clinical trial protocol is normally a long document that humans read and then re-enter, by hand, into the systems that run the study. Treating it as a structured artefact instead means the inconsistencies inside it can be found mechanically, the statistics can be computed from it, and the database that collects trial data can be generated rather than built. The document stops being the end of the process. What are the three engines meant to do? One audits, one simulates, one compiles. The audit looks for the internal contradictions that force a protocol amendment later: visit windows that do not line up, eligibility criteria that conflict, statements that disagree with each other. The simulation computes statistical power and sample size, including by running scenarios in the browser. The compiler turns the standardised protocol model into the schemas an electronic data capture system needs. Why does an amendment matter enough to build a tool against? Because a protocol amendment is expensive and most of them are avoidable. It means re-approval, re-training sites, and often re-consenting participants, and a large share are caused by inconsistencies that were present in the document from the beginning and simply not spotted. Finding those mechanically before a study opens is a much cheaper intervention than correcting them once it is running. Does it hold patient data? No, and the record calls that no-PHI-by-design. A protocol is a description of how a study will be conducted rather than a record of anybody in it, so the product works on study design rather than on patient information. Row-level security and private storage are in the architecture regardless, because a sponsor's unpublished protocol is commercially sensitive even when it contains nothing about a person. ## Whiteboard Video Maker — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.wbamai Client-side whiteboard-animation studio that draws, narrates, and exports WebM and GIF videos entirely in the browser, for educators and creators. Video tools upload your work and make you wait. That is the shape of nearly all of them. A project is assembled in a browser, sent to a queue, rendered on somebody's machine, and returned some minutes later with a watermark on it unless money changed hands. The waiting and the watermark are the same fact: rendering costs the operator something, and both are how it gets recovered. This one never sends anything anywhere. Drawing, narration and encoding all happen on the device the person is using. There is no render queue, no upload, and no watermark, because nothing is being rendered on anybody's behalf and there is no cost to recover. The encoding only recently became possible. Browsers can now encode video directly. The export path works frame by frame through that interface, producing a WebM file or assembling an animated GIF. A few years ago the only route from a web application to a video file was to send the work elsewhere. The architecture here is downstream of a browser capability arriving rather than of a clever decision. Above the export sits a real editor. It is not a template filler. There is a layered timeline with keyframes on individual elements, animation curves, a hand-drawing overlay and four board styles to work on. Narration uses the browser's speech synthesis, alongside recorded voice and audio mixing, with libraries of characters, icons, images, sound effects and a large font picker. Most tools in this category are template fillers wearing an editor's clothes. The animation is fixed and the content slots in, so everything made with one looks like everything else made with it. Keyframes on individual elements is the line between choosing a preset and directing something, and it is considerably more work to build. Projects live in the user's Drive. The permission requested is the narrow one that grants access only to files the application itself created, so it cannot see anything else in the account. A broader scope would have been simpler to implement and would have meant asking people to trust an application with everything they keep rather than with the files it made for them. That is the privacy position stated as a permission rather than a paragraph. It is checkable at the moment of granting, which no sentence on a website ever is. One consequence of the whole arrangement is worth stating, because it is the part a user notices. Everything the product can do is bounded by what a browser can do on the machine in front of somebody. There is no server to hand a hard problem to and no capability that can be added by provisioning something. That is a genuine ceiling. It is also the same decision that removes the queue, the upload and the watermark. What it cost is that the device does the work. Encoding video locally means the export takes as long as the machine takes, and a slower computer is slower here in a way it would not be if a server did it. That is the direct price of never uploading anything. The record also puts this in progress: the web address and the store listings are prepared and not yet confirmed live, so the honest position is that this is finished enough to describe and not yet open to visitors. Open the live site What it does, and what that costs to build Konva + RoughJS canvas with a layered keyframe timeline and animation curves Hand-draw overlay animation and four board styles (whiteboard, blackboard, glassboard, greenscreen) In-browser video export to WebM (WebCodecs + webm-muxer) and animated GIF (gif.js) with no watermark Text-to-speech narration via Web Speech API plus voice recording and audio mixing Asset libraries: characters, icons, images, emoji, sound effects, and a 53-font picker Project storage in the user's own Google Drive via the drive.file OAuth scope Firebase Auth with cross-device Firestore sync for projects and preferences Radix theme customizer with light/dark, accent, radius, scaling, and font-family controls Built with React 19 TypeScript 6 Vite 8 Konva RoughJS WebCodecs API Zustand Radix UI Firebase Capacitor 8 Web Speech API Amplitude Worth knowing whiteboard-animation video-maker webcodecs konva capacitor client-side explainer-video privacy-first Does my video get uploaded anywhere to be rendered? No. Drawing, narration and encoding all happen on your own device, and there is no server rendering step anywhere in the design. That is unusual for video tooling, where uploading a project and waiting for a queue is the normal arrangement. It also means no watermark, because a watermark is how a service recovers the cost of rendering for you, and nothing here is rendering on anybody's behalf. Where are my projects stored? In your own Google Drive, through a narrow permission that only grants access to files the application itself created. It cannot see the rest of your Drive. That scope choice is the whole privacy position in one decision: a broader permission would have been simpler to implement and would have asked people to trust an application with everything they own rather than with the files it made. How does exporting work without a server? Frame by frame, through the browser's own video encoding interface, muxed into a WebM file or assembled as an animated GIF. Modern browsers can encode video directly, which is a relatively recent capability and is what makes this shape of product possible at all. A few years ago the only way to produce a video file from a web application was to send the work somewhere else and wait for it. Is it a real editor or a template filler? A real one. There is a layered timeline with keyframes on individual elements, animation curves, a hand-drawing overlay and four board styles. The distinction matters because most tools in this category are template fillers wearing an editor's clothes, where the animation is fixed and only the content changes. Keyframes on elements is the line between choosing a preset and actually directing something. Does Whiteboard Video Maker work on iPhone? There is no Apple release. One codebase targets web and mobile through Capacitor, and the record lists iOS among the intended targets, which describes configuration rather than something published. No Apple Developer account sits behind this work and nothing I build has shipped to the App Store. The record also says the web address and the store listings are prepared and not yet confirmed live, so nothing is open to visitors today. ## WebAuthn Server BuildKit — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.webauthnserverbuildkit Framework-independent TypeScript server library that verifies passkey registration and authentication ceremonies for Node.js backends. A passkey has two halves and almost everything written about it describes the same one. The browser half is the demonstration. You call an API, a device prompt appears, and something comes back that looks like success. It is not success. What came back is a claim, and a claim is worth exactly what the thing checking it decides to do. The other half is a server whose job is to disagree. Verify the signature. Verify that the challenge is one you issued, that it has not been used, that it has not expired and that it was issued for this operation rather than for another. Verify the origin. Verify the relying-party ID hash and the authenticator flags. Verify the signature counter has not gone backwards, because from here that is what a cloned authenticator looks like. WebAuthn Server BuildKit is that half, written on its own. It implements the Level 3 registration and authentication ceremonies and stays framework-independent, so it sits behind Express, Fastify, Koa, Next.js or anything else running on Node.js. Storage is an adapter, so credentials and challenges live in PostgreSQL, MongoDB, Redis or whatever your product already runs. That choice stays yours. Then the part I would rather be honest about than impressive. Attestation is where libraries of this kind overclaim. A statement is verified per format, and it is reported as trusted only when it chains to a trust anchor you supplied. With no anchor there is no trust claim: the library says the statement verified structurally, and stops. That one decision separates a security library from a security feeling. Sessions are encrypted with AES-256-GCM and can be created, validated, refreshed and revoked. The algorithm allowlist is explicit, and a credential's algorithm is pinned at authentication so it cannot be quietly substituted later. Everything is secure by default, with opt-outs you have to write down deliberately. Errors are typed and machine-readable, each carrying a suggested HTTP status, because a verification failure your route handler cannot classify turns into a 500 and a support ticket. What it cost was scope discipline. One runtime dependency, and not zero. Parsing CBOR by hand is an efficient way to write a security bug nobody notices, so that one stayed and everything else was refused. 311 tests hold the ceremonies, and the ones that matter are the failing cases, since a verifier nobody has watched refuse anything is not yet a verifier. Dual ESM and CommonJS builds with type declarations mean it drops into a backend that already has opinions about modules. There is a limit written into the package rather than discovered in production. This is a relying-party library. It does not manage your users, own your session store, or decide your recovery policy, and a passkey system without a recovery policy is a support queue with a date on it. It pairs with capacitor-biometric-authentication on the client side. The two were written against each other rather than against a specification read separately twice, which is the only reason their assumptions line up. There is no iOS release behind any of this, because there is no Apple Developer account here. This is server code. MIT, on npm as webauthn-server-buildkit, with the source at github.com/aoneahsan/webauthn-server-buildkit. Read the verification path before installing it. This is the layer where nearly right and right look identical from the outside, and the difference only shows up on the day somebody is looking for it. npm Source on GitHub Documentation What it does, and what that costs to build Server-side WebAuthn registration and authentication ceremony verification Per-format attestation verification (packed, fido-u2f, android-key, tpm, android-safetynet, apple) with honest trust-anchoring Single-use, expiring, operation-scoped challenge enforcement Algorithm allowlist plus credential-algorithm pinning at authentication AES-256-GCM encrypted session create, validate, refresh, and revoke Storage-agnostic adapter system for MongoDB, PostgreSQL, Redis, or any database Framework-independent integration with Express, Fastify, Koa, and Next.js Typed, machine-readable error classes with suggested HTTP status codes Built with TypeScript Node.js cbor-x WebAuthn Level 3 FIDO2 COSE CBOR X.509/ASN.1 AES-256-GCM tsup ESLint Yarn Worth knowing webauthn passkeys fido2 typescript nodejs authentication biometrics npm-package Who built WebAuthn Server BuildKit? Ahsan Mahmood wrote the verification code, the attestation handling and the session layer. It is on npm as webauthn-server-buildkit under MIT, with the source at github.com/aoneahsan/webauthn-server-buildkit. It exists because the client-side biometric package he had already published would have been an unfinished feature without it: a client can run a ceremony, and only a server can decide whether the result means anything at all. One author on both sides is why the two agree about the shape of what crosses between them. What exactly does it verify? The signature, the challenge, the origin, the relying-party ID hash, the authenticator flags and the signature counter, across both registration and authentication ceremonies at WebAuthn Level 3. The challenge checks are the ones most often reduced to a formality: it has to be one your server issued, unused, unexpired and scoped to this operation rather than to another. The counter check earns its place for a different reason. A counter that has gone backwards is what a cloned authenticator looks like from the server's side of the connection. Does WebAuthn Server BuildKit run on iPhone? It does not run on a phone at all, because it is server code executing on Node.js behind your API. The device question belongs to whichever client talks to it, and the browsers and platforms that implement WebAuthn are what decide it. What I will not claim in either direction is an Apple platform: there is no Apple Developer account behind this work and nothing I build has shipped to the App Store, so read anything Apple-shaped here as untested rather than supported. Which database does it need? Whichever one you already run. Storage is a pluggable adapter, so credentials and challenges can sit in PostgreSQL, MongoDB, Redis or anything else you can write an adapter against, and the library holds no opinion about your schema beyond what it must read back. That is deliberate. A passkey library that dictates a datastore forces an operational decision having nothing to do with authentication. The one runtime dependency it does carry handles CBOR parsing, which is not a thing to hand-roll. What does honest trust-anchoring mean? It means a statement is reported as trusted only when it chains to a trust anchor you supplied. With no anchor, the library tells you the attestation verified structurally and nothing further, rather than presenting an unanchored verification as proof of where a device came from. Most products do not need attestation trust at all, and being told so plainly is more useful than a green tick. The distinction is small in code and it is the entire difference between a security library and a comfortable feeling. ## FourTools iOS Recovery — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.fourtools A cross-platform Tauri 2 desktop suite that helps owners recover access to their own iPhones, iPads, and iPods. You own the device and you cannot get into it. That is a real situation with a disreputable search history attached to it. The same handful of words describes somebody recovering an iPad that spent two years in a drawer and somebody holding a phone that is not theirs. Every tool in this corner has to decide which of the two it is built for. FourTools iOS Recovery decides it in the software rather than in the marketing. Runs are gated on a server. A licence is activated and a preflight check passes before the desktop application will touch a device at all. The acceptable-use and stolen-device rules are enforced there rather than printed in a document nobody opens. Enforcement that lives in the client is enforcement a user can delete. The desktop half is Tauri 2, a Rust core under a React interface, shipping in five languages: English, German, Spanish, French and Japanese. Most of the design effort went into one wizard. A guided DFU restore removes a passcode, with per-model animations, because DFU is a sequence of button presses that differs between devices and a static instruction is the wrong shape for it. Getting it wrong only puts you back at the beginning, which is tolerable once and infuriating on the fourth attempt. Two paths leave the contents alone. Screen Time and Restrictions passcode removal clears the passcode without wiping the device, and an MDM enrollment-screen bypass covers the small business that has lost the credentials to hardware it bought outright. Underneath sits the part that is genuinely hard. 17 Rust crates wrap the libimobiledevice and idevicerestore C stack through bindgen FFI, and IPSW download carries TSS personalisation with signature verification rather than trusting whatever file happened to arrive. A Rust CLI drives every orchestrator headlessly, so the desktop application is one caller rather than the only one. A Go backend handles licensing, telemetry, billing and the refusal rules, with PostgreSQL, Redis and OpenTelemetry behind it, and Next.js front-ends for the operations side. Now the part that decides whether this page is honest. It is early stage. The architecture, the crates, the services, the continuous integration and the interface all build and run against a mock device backend. The real on-device path is gated on two things I have not done: vendoring the LGPL C libraries, and validating against physical hardware. So nothing here has recovered a real device yet. Not one. The gap between a mock backend and a phone in DFU mode is where projects of this kind usually find out what they got wrong. What it cost is the promise. The truthful answer to a lot of these requests is no, and what is possible depends on the chip generation in the device you are holding. A tool that says so at the start loses the sale that a vaguer one would take, and I would rather lose it there than at the end of a restore. That framing is the actual product, more than any single wizard is. There is no public link here. The project is not publicly reachable, and there will be nothing to open until the on-device path has been validated on hardware somebody can watch. A monorepo in three languages is also a cost worth naming. Rust for the device work, Go for the services, TypeScript for the interfaces, and one person moving between all three. Each boundary is a place where a change has to be made twice, and the reason to accept that is that the alternative was doing the C interop in a language that would have to reimplement it. Read the refusal rules before the feature list. They are the part that decides what this is. What it does, and what that costs to build Guided DFU passcode-removal restore with per-model animations Screen Time / Restrictions passcode removal without wiping the device MDM enrollment-screen bypass for lost small-business credentials IPSW download with TSS personalization and signature verification Developer CLI driving every orchestrator headlessly Server-side license activation and run preflight gating Acceptable-use and stolen-device refusal enforcement Five-language UI (English, German, Spanish, French, Japanese) Built with Rust Tauri 2 React TypeScript Go PostgreSQL Redis Next.js Tailwind CSS Radix UI OpenTelemetry bindgen FFI Worth knowing ios-recovery tauri rust golang desktop-app dfu-restore monorepo device-recovery Who built FourTools iOS Recovery? Ahsan Mahmood built it, as a single monorepo holding Rust, Go and TypeScript. The desktop application is Tauri 2, a Rust core under a React interface; the device work is 17 Rust crates over the libimobiledevice and idevicerestore C stack through bindgen FFI; the licensing and enforcement side is Go with PostgreSQL and Redis behind it. There is no public link on this page, because the project is early stage and there is nothing yet for a reader to open and check. What does it refuse to do? Run against a device somebody else owns, as far as the software can tell. Licence activation and a run preflight happen on the server rather than in the desktop application, so acceptable-use and stolen-device rules are enforced where a user cannot simply edit them out. That is a deliberate architecture choice with a cost attached: it means the tool needs a working connection to start a run, and it means the enforcement is only as good as the checks behind it. Both of those are better said than discovered. Does it wipe the device? That depends which path you run, and two of them are built specifically not to. The guided DFU passcode-removal path is a restore, with per-model animations because the button sequence differs by device. Screen Time and Restrictions passcode removal is described the other way round: it clears the passcode without wiping what is on the device. The MDM enrollment-screen path addresses a business that has lost the credentials to hardware it owns. Read which one you are about to run before you run it. Can I point it at a real device today? No. Everything currently builds and runs against a mock device backend, and the real on-device path is gated on two things that have not happened: vendoring the LGPL C libraries, and validating against physical hardware. So the architecture, the crates, the services, the continuous integration and the interface are all real and exercised, and no phone has been recovered by it. Saying otherwise would be the easiest claim on this page to make and the most expensive one to be caught in. Why is there a command-line version as well? So that every orchestrator can be driven headlessly, without the window. A Rust CLI called fourtools runs the same operations the desktop application does, which matters twice: a technician can script a repeatable sequence instead of clicking through one, and the orchestrators are forced to stay separable from the interface that usually calls them. A tool where the only way to run the logic is the graphical front end is a tool whose logic and window have quietly become the same thing. ## Linux Cleanup — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.linuxcleanup A modular Bash and Node.js CLI that safely reclaims regenerable cache and junk disk space for Linux developers. Disk space on a developer machine goes to things that would rebuild themselves. Package-manager caches. Browser caches. Gradle, Cypress and Playwright binaries, Android emulator images, and node_modules folders belonging to projects that finished a year ago. The awkward part is not deleting them. It is being certain that a given folder is one of those things, rather than the one piece of state on the machine that cannot be regenerated. Linux Cleanup reclaims 10–50+ GB of that junk, and every gigabyte of it is a decision somebody has to be right about. So the whole tool is built around a single guard. Every deletion passes through one function, safe_rm, which refuses an allowlist of protected paths. One place to read. One place to audit, and one place a mistake could live. A cleaner that calls rm inside each section has as many chances to get a path wrong as it has sections. The second guard is time. Cache cleaners prune by staleness rather than emptying wholesale, so a cache untouched beyond a threshold you set goes and the rest stays. A cache is only junk once nothing has needed it for a while. The tool runs as a guided 10-step walkthrough, and each step reports the bytes it reclaimed rather than saving a total for the end. That ordering matters more than it sounds: a running total tells you when to stop. There is also a read-only scan that deletes nothing. There is an all-safe batch mode for the sections that need no judgement, plus a whiptail or dialog interface for anyone who would rather not read flags. Every run writes a report. Schema-versioned JSON, with Markdown and a self-contained HTML export on request. The version is on the schema because a report format that changes silently makes last month's report unreadable to this month's script. It makes no network calls at all. Nothing about a disk-cleanup tool needs an internet connection, and a program that deletes files and also phones home is asking for a trust it has not earned. It is a modular Bash engine behind a thin Node.js launcher, published to npm and run with npx linux-cleanup. The launcher is thin on purpose. The part that deletes things is shell, which is the language the rest of the machine is already speaking. There are installers for a weekly cron entry and a shell alias, for people who would rather this happened without being remembered. What it cost is the licence. Linux Cleanup is source-available rather than OSI open source: the licence is No-Derivatives and Non-Commercial. You can read every line before you run it. You cannot ship a modified version of it, and that is worth stating on the page rather than leaving it to be found in a file at the bottom of a repository. The second cost is the allowlist itself. A guard that refuses protected paths is also a guard that will occasionally refuse something you genuinely wanted gone, and the honest fix for that is to widen it deliberately rather than to route around it. Every safety rail here is a small amount of friction that somebody eventually resents. The source is at github.com/aoneahsan/linux-cleanup. Read the allowlist first. It is the shortest way to learn what the tool will not touch, which is a more useful thing to know than any list of what it will. npm Source on GitHub What it does, and what that costs to build Guided 10-step interactive walkthrough with per-step reclaimed-bytes totals Allowlist-based safe_rm guard that refuses protected paths Staleness-gated pruning of caches unused beyond N days Package-manager, browser, and dev-tool cache cleanup Stale node_modules finder across project roots Schema-versioned JSON reports with Markdown and HTML export Read-only scan, all-safe batch mode, and whiptail/dialog TUI Weekly cron and shell-alias installers with zero network calls Built with Bash Node.js npm npx jq whiptail dialog cron Semantic Versioning Diátaxis Worth knowing linux cli bash disk-cleanup cache-cleaner devtools npm system-utility Who built Linux Cleanup? Ahsan Mahmood, working alone, in Bash with a thin Node.js launcher over the top. The source is at github.com/aoneahsan/linux-cleanup and it is source-available rather than OSI open source: the licence is No-Derivatives and Non-Commercial, which is a limit better read before a fork than after one. Shell is the right language here because the machine being cleaned is already running one, and the launcher exists so that npm can deliver the thing. The person who wrote the delete guard is the person who wrote the report format. How does it decide a cache is junk? By age, against a threshold you set. Cache cleaners prune what has gone untouched beyond a number of days rather than emptying a cache wholesale, because a cache is not junk while something is still reading it, and a tool that wipes every cache on principle has simply made your next build slow. The stale node_modules finder works the same way across project roots. That leaves you deciding one number instead of a hundred individual folders, which is the only version of this job anybody does twice. What stops it deleting a path it should not? One function. Every deletion passes through safe_rm, which checks its target against an allowlist of protected paths and refuses anything outside it, so there is a single place to read and a single place to be wrong. A cleaner that calls rm inside each section has as many chances to get a path wrong as it has sections. There is also a read-only scan that deletes nothing at all, which is the sensible first run on a machine you have not cleaned before. Does Linux Cleanup run on macOS or on a phone? No. It targets Linux, and macOS has a separate tool of mine rather than a flag on this one, because the paths, the cache locations and the package managers differ enough that one script covering both would be wrong in both places. It does not run on a phone in any form. There is no iOS release behind any of my work either, because there is no Apple Developer account here and nothing I build has shipped to the App Store. What does a run leave behind? A schema-versioned JSON report, with Markdown and a self-contained HTML export when you ask for them. The schema carries a version because a report format that changes quietly makes last month's report unreadable to this month's script. Nothing leaves the machine: the tool makes no network calls at all, so there is no telemetry, no update check and nothing to opt out of. Per-step reclaimed-bytes totals sit in the report as well, which is how you find out whether the run was worth doing. ## MacLeanup — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.macleanup A safe-by-default macOS cleanup CLI covering 28 cleanup sections with a real dry-run, per-section confirmations, and zero-install delivery via npx. A cleanup tool is a delete tool with a friendlier name. That is the whole design problem, and everything else follows from it: how much the tool asks, what it refuses to do unattended, whether an action can be taken back. MacLeanup covers 28 sections. Xcode and DerivedData. Gradle. Package-manager caches, a Docker prune, browser caches. Stale node_modules, large dormant files, plus an audit of what has installed itself as a LaunchAgent. The interesting part is the dry run. It is a real one. The --dry-run flag routes every destructive call through a no-op helper rather than printing a description of what the tool intends to do, so the code path you rehearse is the code path that runs. A dry run written as a separate set of print statements is a second implementation, and the day the two disagree is the day you find out which one was lying. Deletion decisions use two clocks. A file has to be old by access time and by modification time before it becomes a candidate. Either measure on its own will condemn something that is still quietly in use, and the cost of that mistake is not symmetrical with the cost of leaving a stale folder alone. Removing an application moves it to the Trash rather than calling rm, so the usual recovery is dragging it back out. System sections are behind sudo. Irreversible sections are excluded from the all-sections flag unless you opt into them by name, which makes the convenient option also the conservative one. Beyond that sit named profiles: dev, minimal, cache-only, deep, audit. Per-section threshold flags cover anyone who wants to disagree with a default. The engine is one Bash file of roughly 3,600 lines with zero runtime dependencies. It is published to npm. It runs with npx macleanup, on a machine where nothing has been installed yet. Being one file is the point. A tool that deletes things should be short enough to read before you run it, and a dependency tree is a list of other people you have also decided to trust. Inspectable is a claim, and a single script is the only version of that claim a person can actually check in an afternoon. Nothing is reported anywhere. There is no telemetry and there are no network calls; logs and JSON run summaries stay in a folder in your home directory, where a run from three months ago is still there to compare against. There is a window, and it is the part I would point at first. A native macOS application built with Tauri wraps the same script through its JSON output mode. It reimplements nothing. Two implementations of the same destructive behaviour is how a graphical tool and a command-line tool end up disagreeing about what one flag means, and only one of those is the one anybody tested. What it cost is the licence and the shape. MacLeanup is source-available rather than OSI open source, which is a real limit and belongs on the page rather than in a file nobody opens. The single-file engine has its own price: every new section is a destructive path that also has to exist as a no-op path, which is roughly twice the work per feature. Twice the work, deliberately. That is why the dry run stays honest. The source is at github.com/aoneahsan/macleanup, and the dry-run flag is the right way to meet it, because everything written above is a claim until you have watched the tool decide to do nothing. npm Source on GitHub What it does, and what that costs to build 28 targeted cleanup sections spanning developer caches, Docker, browser caches, and system maintenance Real --dry-run that routes every destructive call through no-op helpers Per-section confirmation prompts with deep sections gated behind an explicit opt-in flag Two-condition delete gate using both atime and mtime to protect files still in use Named profiles (dev, minimal, cache-only, deep, audit) plus per-section threshold flags Persistent logs and reports written to ~/.mac-cleanup with JSON run summaries Zero telemetry and zero network calls from the cleanup engine Native macOS GUI (Tauri) wrapping the same script via its --json output Built with Bash Node.js npm npx macOS Tauri Rust React Vite TypeScript Firebase Worth knowing macos cleanup cli bash npm developer-tools disk-space maintenance Who built MacLeanup? Ahsan Mahmood wrote all of it, engine and interface both. The cleanup engine is one Bash file of roughly 3,600 lines with zero runtime dependencies, published to npm so that it runs with npx macleanup, and the source is at github.com/aoneahsan/macleanup. The native macOS window lives in the same repository and calls that same script. It is source-available rather than OSI open source, which is a licence limit worth knowing before you plan anything on top of it. What does the dry run actually do? It runs the real code path with every destructive call routed through a no-op helper. That is the difference between a dry run and a description of one: a mode that prints what a tool intends to do is a second implementation of the tool, and the two can disagree without either of them telling you. Here the sequence you rehearse is the sequence that executes, minus the deletion. Running with --dry-run first is the recommended way to meet the 28 sections, because the output is the same and nothing has happened. How does it decide a file is dormant? By two clocks rather than one. A file has to be old by access time and by modification time before it becomes a candidate, because either measure alone will condemn something that is quietly still in use: a file nobody has edited for a year may be read every morning, and a file touched last week may have been written once by an installer and never opened. Thresholds are adjustable per section, and named profiles cover the common shapes: dev, minimal, cache-only, deep and audit. Is there a graphical version? Yes, a native macOS application built with Tauri, and it lives in the same repository. It wraps the same Bash script through that script's --json output and reimplements no cleanup logic whatsoever, which is the whole point of it. Two implementations of the same destructive behaviour is how a window and a terminal end up disagreeing about what a flag means, and only one of them is the version anybody tested. The window is a front end. The script is still the product. Does it report anything back to me or anyone else? Only to you, on your own machine. There is no telemetry and there are no network calls in the cleanup engine, so nothing about your disk, your applications or your run leaves the laptop. Logs and JSON run summaries are written under a folder in your home directory, which means a run from three months ago is still readable and comparable. The cleanup engine makes no network calls, which is the claim the row itself scopes to the engine. The graphical version reimplements none of that logic; it is the same script with a window in front of it. ## SysScope — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.sysscope Read-only Bash CLI that audits your Mac or Linux machine and grades which local Ollama models it can actually run. Will this model actually run on this laptop? The usual answer is a number of gigabytes, and the answer that matters is a different one. A model fits when the weights, the context you intend to use and everything already resident leave enough headroom for the machine to avoid swapping. Two laptops holding the same amount of memory can give opposite answers. So the question is really about the machine. SysScope reads the machine first and grades models against what it found there. Apple Silicon core layout, GPU cores, Metal version, NVIDIA VRAM, memory, disk, battery and thermal state, turned into an inference memory budget rather than a specification sheet. Fifteen popular Ollama models then get a verdict each: fits, tight, or too big. Tight is the one worth having. Fits and too-big are usually obvious from a model's own page, while the middle case is where something loads, behaves during a short test and then degrades at the end of a long context, which is a slow way to find a limit. Beside that sits a health scorecard for disk, memory, AI capability and battery, each with a one-line reason rather than a bare score. The implementation choice is the part I would defend. It is pure Bash with no dependencies, and it runs on the bash 3.2 that ships with macOS rather than demanding a newer shell. That constraint costs real convenience. It also means the tool runs on a machine somebody has just handed you, before anything has been installed on it, which is precisely the moment this question gets asked. It is read-only. There are no network calls, and nothing on the system is modified. That is stated at the top because a script which inspects your hardware is a script people are right to be wary of, and the only convincing answer is a codebase short enough to read first. Output arrives three ways: a colourised terminal report, a Markdown report meant for sharing, and JSON for anything that has to parse it. Three run modes cover full, quick and AI-only. It works as an interactive menu or unattended behind a flag. Sharing the report is the risk. A hardware dump carries serial numbers, UUIDs and a hostname that is very often somebody's actual name. The share mode redacts those before the report leaves the machine, which is the difference between a file you can post in a thread and one you should not. What it cost is portability, and that cost is ongoing. Every hardware probe is a different command on a different platform, so shell code reading system_profiler on one machine and nvidia-smi on another accumulates special cases faster than it accumulates features. It ships two ways. One is npm, run instantly through npx; the other is a single-file bundle you fetch and pipe to a shell, which is how a tool reaches a machine with no Node.js on it yet. It runs on a Mac or a Linux machine. There is no iOS release behind any of my work, because there is no Apple Developer account here. A phone is not what this is for in any case. npx sysscope, on npm, with the source at github.com/aoneahsan/sysscope. Read it before running it. That is the standing advice for anything distributed this way, and being small enough to make that practical was a design goal rather than an accident. npm Source on GitHub What it does, and what that costs to build Grades 15 Ollama models against a computed inference memory budget with a fits / tight / too-big verdict Detects real hardware: Apple Silicon P/E cores, GPU cores, Metal version, NVIDIA VRAM, RAM, disk, battery, thermals Health scorecard rating disk, memory, AI, and battery with one-line reasons Three run modes: full, quick, and ai-only Multiple outputs: terminal report, shareable Markdown, and machine-readable JSON Privacy --share mode redacts serials, UUIDs, and hostname Dual distribution via npx and a curl | bash single-file bundle Interactive menu or fully scriptable unattended operation with --yes Built with Bash POSIX shell Node.js npm system_profiler sysctl nvidia-smi ANSI terminal output GitHub-Flavored Markdown JSON Worth knowing cli bash local-ai ollama system-audit apple-silicon developer-tools npx Who built SysScope? Ahsan Mahmood wrote it in Bash, deliberately. It is on npm and runs through npx sysscope, with the source at github.com/aoneahsan/sysscope. A dependency-free shell script is an unfashionable choice and it is the right one for a tool whose entire job is to run on a machine before anything has been installed on it. Shipping it through npx means the machine you are diagnosing needs nothing installed first, which is the whole point of a tool you reach for when a machine is already misbehaving. What does it check before grading a model? The machine, in more detail than a specification sheet gives you: Apple Silicon core layout, GPU cores and Metal version, NVIDIA VRAM, memory, disk, battery and thermal state. Those readings are turned into an inference memory budget, and that budget is what a model gets graded against. It is the difference between comparing a model's size to your total memory and comparing it to the memory genuinely available for inference on this machine, in this state, with whatever else is already resident. Does SysScope run on iPhone? No. It is a terminal tool for a Mac or a Linux machine, and it does not run on a phone at all. There is no iOS release behind any of my work either, because there is no Apple Developer account here and nothing I build has shipped to the App Store. It does know a good deal about Apple hardware, since core layout and Metal version are among the things it reads, and that is a desktop question rather than a mobile one. Is it safe to run on a work machine? It is read-only and makes no network calls, so it inspects the system and changes nothing on it. That claim is worth checking rather than believing, and it is checkable: a single Bash file with no dependencies is short enough to read before you run it. The share mode also redacts serial numbers, UUIDs and the hostname before a report leaves your machine, because a hardware report is exactly the kind of file that carries somebody's name out into a public thread by accident. What does a tight verdict mean? It means the model will load and you should expect trouble at the edges. Fits and too-big are usually obvious from a model's own page; tight is the case where something runs happily through a short test and then degrades at the end of a long context or under thermal pressure, which is a slow and confusing way to learn a limit. The verdict comes from the computed inference budget rather than from a rule of thumb, and the scorecard beside it gives a one-line reason for disk, memory, AI capability and battery. ## Candy Rush — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.candyrush A match-3 puzzle game with 150 levels across 10 worlds, special candies, boosters, a daily challenge and offline play — live on the web. A daily challenge only works if everybody gets the same board. That is a constraint on the engine long before it is a feature. Deal the board from an ordinary random number and two players comparing scores are comparing two different games, which makes the leaderboard beside it decorative. So the match-3 engine is pure and deterministically seeded. Same seed, same board, same cascades, on any machine. It is a set of functions with no reference to the screen at all, which is what makes it testable properly rather than testable by playing it. That decision paid for itself twice. Because the engine is decoupled from rendering, the same logic drives a React board animated with Framer Motion and an optional Phaser 3 renderer. Neither of them holds a rule. A bug in a rule cannot be a rendering bug. A rendering change cannot quietly alter a score. The game on top is 150 hand-crafted levels across 10 themed worlds. Striped, wrapped and colour-bomb specials, cascade scoring, boosters, achievements, and leaderboards kept in Firestore. Hand-crafted rather than generated, which is much slower to make and is the difference between a level with an idea in it and a level with a difficulty number attached. Cascades are where scoring gets interesting. A cleared match drops new pieces in. Those can match again, and again after that. A scoring model that treats the fifth cascade like the first is a scoring model that ignores the thing players are actually aiming at. It installs as a progressive web app and it plays offline. Progress stays on the device until there is somewhere to put it. Sign-in is Google or anonymous, and cloud save carries an account onto another machine, which is the part that has to be right: a lost level is a small betrayal a player remembers for much longer than a good one. Anonymous matters more than it sounds. A puzzle game that demands an account before the first move is asking for something before it has given anything. The soundtrack is procedural. Every note is synthesised at runtime through the Web Audio API rather than downloaded as a file, which keeps the install small and makes the audio code like everything else in the project. What it cost is the second renderer. Two rendering paths is one more than a game of this size needs, and keeping the engine ignorant of both is a discipline the codebase has to keep paying for rather than a decision made once. Every new special, every new booster, has to be expressed as a rule first and a picture second. The reward is that the rules have one home. There is a second cost and it is the levels. 150 of them, by hand, is a long stretch of work that no framework helps with and no test can tell you is good. The engine can replay a level identically from its seed. It cannot say it is good. It is live on the web at candyrush.aoneahsan.com. Play the daily challenge if you want a single measurement of any of this. Everyone playing that day is on the same board. That is the only fair comparison a puzzle game is able to offer, and it exists because the engine was built to make it possible rather than added afterwards. Open the live site What it does, and what that costs to build 150 levels · 10 worlds Special candies + cascades Pure functional engine Offline PWA + cloud save Daily challenge + boosters Built with React 18 TypeScript Vite Phaser 3 Radix / shadcn Zustand Framer Motion Firebase Web Audio API Tailwind CSS Capacitor Vitest Worth knowing match-3 puzzle game pwa phaser react firebase daily-challenge Who built Candy Rush? Ahsan Mahmood built all of it, and for a game that means more parts than usual: the match-3 engine, the 150 levels, the two renderers, the cloud save and the soundtrack. It is live at candyrush.aoneahsan.com, which is the only claim on this page you have to take on trust for as long as it takes to open a tab. A game is an unforgiving portfolio piece, because a reader does not have to read anything to find out whether it works. Why does the engine need to be deterministic? Because the daily challenge is worldwide, and a shared challenge only means something if every player gets the same board. A deterministically seeded engine produces the same layout and the same cascades from the same seed on any machine, so a score is comparable rather than a coincidence. The same property makes the game testable: a level that broke for somebody can be reproduced exactly instead of hunted for. Without it, a leaderboard is a list of people who were dealt different games. Does Candy Rush work without a connection? Yes. It installs as a progressive web app and plays offline, because a puzzle game is the exact thing people reach for when the connection is bad. Progress made offline belongs to the device until sign-in gives it somewhere to go, at which point cloud save carries it to another machine. The parts that genuinely need the network are the ones that involve other people: the worldwide daily challenge and the Firestore leaderboards. Does Candy Rush work on iPhone? No installed version exists. It is a web game, so a browser on a phone will open and play it, and that is the only route onto an iPhone there is. There is no iOS release behind any of my work, because there is no Apple Developer account here and nothing I build has shipped to the App Store. The web build is what is live and playable today, and it is the only one this page describes. Do I have to make an account to play? No. Anonymous sign-in gets you into the first level without a form, and a Google sign-in exists for anyone who wants cloud save and a place on the leaderboards. A puzzle game that asks for an account before the first move is asking for something before it has given anything. The whole game is built the same way round: play first, decide later whether you want the progress to follow you somewhere else. ## Ludo — Cross-Platform Multiplayer Board Game — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.ludo Multiplayer Ludo — play online with friends, local pass-and-play, or vs AI bots, on web, Android & Chrome. One tap to start, no signup. The same codebase builds a browser tab, an Android package and a Chrome extension. One React and TypeScript project with a Phaser 3 board, delivered three ways. That only works if the rules are not tangled into the thing drawing the board. That is a design problem. Ludo itself is the four-player board game most people already know, and its rules are simple to say and full of small cases that nobody agrees about out loud. So the rules are a pure TypeScript engine with test coverage and no knowledge of the screen. Three things run on it: the board a person plays; the bots at three difficulties; the headless simulation tests that play whole games with nothing drawn at all. A bot that plays through the same engine as a person is a bot that cannot cheat by accident. That is the sentence I would defend hardest. Wire an opponent directly into the game state and it will eventually see something a player cannot, or move in a way the rules do not permit. The report that comes back is that the game feels unfair rather than that a rule is broken. Online play is matchmaking or a private room code. Real-time state over Firebase for the table that is spread across four places. Local pass-and-play for the one where everybody is already sitting down. A 100-tier campaign for when nobody else is about. Around that sits an economy, a friends list and leaderboards, all on Firebase's free tier. Those three are what turn a game into something a person comes back to, and they are also the parts that quietly cost money to run if they are built without a budget in mind. The free tier is the constraint. It is not a footnote on the stack list. A board game built on a free tier has to cost almost nothing to run, or it disappears in the month it gets popular. Every read is bounded and the state a game of Ludo needs is genuinely small, which is what makes a real-time multiplayer board game affordable to leave switched on. Sound is synthesised at runtime. Every effect comes out of the Web Audio API rather than an audio file, so there is nothing extra to download and nothing to license. What it cost is surface area. Four ways to play, and each of them is the same rule set approached from a different angle: online, local, bots and campaign. Each is a place where a rule could be enforced in one path and quietly forgotten in another, which is the entire argument for the engine being one thing and everything else being a caller of it. Three delivery targets multiply that again. A browser tab, an Android package and a Chrome extension have different lifecycles, and a game that assumes it will never be suspended mid-turn is a game that has not been run inside an extension. It is in development. The web app is live at ludo.aoneahsan.com, and everything described above is what runs there rather than what is planned. The Android and Chrome builds come from the same codebase. Open it and start. There is nothing to install and no account to make first. That was decided early and it constrained everything after it, because an anonymous player still needs somewhere to keep a campaign, a wallet and a friends list. That is a harder problem than a sign-up form would have been. Open the live site What it does, and what that costs to build Online multiplayer Local play & AI bots 100-tier campaign Tested rules engine Economy & leaderboards Built with Vite React 18 TypeScript Phaser 3 Radix UI Zustand Firebase Auth Firestore Capacitor Chrome MV3 Web Audio API Tailwind CSS Worth knowing ludo board-game multiplayer game react phaser firebase capacitor Who built Ludo? Ahsan Mahmood built it, and the whole thing comes out of one React and TypeScript codebase with a Phaser 3 board. That single codebase produces the web application, an Android build through Capacitor and a Chrome extension on Manifest V3, which is the only reason one person can keep three delivery targets in step. The web app is live at ludo.aoneahsan.com. It is in development rather than finished, and what is written on this page is what runs there today. Can I play with people who are not in the room? Yes, through matchmaking or a private room code. Matchmaking finds you a table; a room code is for the group who already know each other and would rather not be matched with strangers. Local pass-and-play covers the opposite case, where everybody is sitting in the same place and one device is enough. There are also AI bots at three difficulties and a 100-tier campaign for the evenings when nobody else is about, which is most evenings for most people. How do the bots decide a move? Through the same rules engine a human player uses, at one of three difficulty settings. That matters more than the difficulty labels do: a bot wired directly into the game state can cheat by accident, seeing something a person cannot or moving in a way the rules do not permit, and nobody notices until a player does. Running the bots through the same pure engine makes that impossible rather than unlikely. The headless simulation tests use that engine too, playing games with nothing drawn on screen. Does Ludo work on iPhone? No. Ludo builds for three places and none of them is an Apple platform: the web, Android through Capacitor, and Chrome as an extension. There is no iOS release behind any of my work, because there is no Apple Developer account here and nothing I build has shipped to the App Store. A phone browser will still open the web version, which is the only way onto an iPhone this game has, and it is the build this page describes. Where does the multiplayer state live? In Firebase, on its free tier, along with the economy, the friends list and the leaderboards. That constraint shaped the design rather than following it: a board game running on a free tier has to cost almost nothing per player, or it goes off the internet in the month it becomes popular. Every read is bounded and the state a Ludo game actually needs is small, which is what makes leaving it running affordable. The alternative was a server that has to be paid for whether anyone is playing or not. ## Monopoly Game — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.monopoly A modern Monopoly rebuild for web + mobile — local pass-and-play, solo vs AI bots, or online private rooms with real-time sync. A board game with a test suite of more than 200 cases behind it sounds excessive until two players disagree. Monopoly is easy to play and awkward to implement. The classic rule set is the stated scope here, and that scope includes all the parts people forget: auctions, mortgages, the supply of houses, the rents that depend on what else somebody owns. Every one of those is a rule that can be almost right. So the tests are not ceremony. More than 200 Vitest cases are what let three different ways of playing share one rule set without each of them quietly growing its own version of a rule. Three modes, one engine. Local pass-and-play for 2 to 6 people around a single device. Solo against bots at easy, medium or hard. Online multiplayer in private rooms, synced in real time through Firestore. Private rooms rather than open matchmaking, which keeps the multiplayer surface to a group who already know each other and keeps the scope to something one person can stay responsible for. One engine is the only reason there can be three. Each mode is a different way of getting turns into the same rules, rather than three implementations that agree with each other right up until the evening they do not. A rule fixed in one place is fixed everywhere, and a rule that is wrong is wrong everywhere, which is easier to find. The bots use it too. Three difficulty settings sit over the same rules, so the difference between an easy opponent and a hard one is how it chooses rather than what it is permitted to do. The economy is deliberately small. Coins are earned in play and they buy cosmetic tokens and board themes. Nothing bought with them changes a roll or a rent, because a property game where money can be added from outside the game has quietly become a different game with the same board. It runs in four languages. Underneath is Vite and React with TypeScript, Zustand for state, Tailwind and Radix UI for the interface, Framer Motion for the pieces moving, and a Workbox service worker making it installable. Capacitor packages the same codebase for a native shell. Now the limit, which belongs high on any honest page about this one. It is complete and playable, and it is not deployed. There is no public web address and no store build, so nothing described above is something you can go and open this afternoon. That is why there is no link here. A portfolio's unit of evidence is a released thing, and by that measure this one has not arrived. What it is instead is a finished implementation with its rules under test, which is worth describing accurately rather than dressing up as something a reader can check. What it cost is the last mile. The engine, the modes, the economy, the languages and the tests are all done. What sits between that and a deployed address is unglamorous in a way that is easy to keep postponing: a domain, a build pipeline, store listings, the paperwork each of those asks for. None of it is hard. It is simply not the part anybody wants to spend a Sunday on. Finished and unreleased is an honest state for a project to be in. It is also the state a portfolio is most tempted to describe as something else, which is the reason this page says it twice. What it does, and what that costs to build Local pass-and-play Solo vs AI bots Online private rooms Coin store Full classic ruleset Built with Vite React 18 TypeScript Tailwind CSS Framer Motion Radix UI Zustand React Router Firebase Capacitor PWA / Workbox Vitest Worth knowing monopoly board-game multiplayer pwa react firebase capacitor game Who built Monopoly Game? Ahsan Mahmood built it, as one Vite and React codebase in TypeScript with Zustand holding the state and a Vitest suite of more than 200 cases behind the rules. There is no public link on this page, because the game is finished and has not been deployed anywhere a reader can reach. That is an unusual thing for a portfolio page to say and it is the accurate thing, and a portfolio whose unit of evidence is a released product should be the first to admit when something is not one yet. Where can I play it? Nowhere yet, and that is the honest answer rather than a soft one. There is no public web address for it and no native build in any store, so nothing on this page is something you can open today. What exists is a complete and playable implementation with its rules under test, sitting in a repository. Describing that accurately seemed better than describing it as available, since the second version is checkable in about four seconds and this whole site rests on being checkable. What do the coins buy? Cosmetic tokens and board themes, and nothing else. Coins are earned in play rather than bought with money, and nothing bought with them changes a dice roll, a rent, or how much cash a player starts with. That boundary is deliberate: a property game where money can be added from outside the game has stopped being the game and become something else wearing its board. The economy exists to give a long-running player something to collect, not to sell an advantage. How do three modes stay consistent with each other? One rules engine, and a test suite of more than 200 cases over it. Local pass-and-play for 2 to 6 players, solo against easy, medium or hard bots, and online rooms synced through Firestore are three ways of getting turns into the same rules rather than three implementations of them. That is the only arrangement where a rule fixed once is fixed everywhere. The classic rule set has enough small cases in it that three copies would disagree within a month, and the disagreement would surface as an argument between players. Does Monopoly Game work on iPhone? No, and it does not work on Android either yet, because nothing has been deployed. It is built as an installable web app and packaged with Capacitor, so the native path exists in the codebase without a released build at the end of it. There is no iOS release behind any of my work in any case, because there is no Apple Developer account here and nothing I build has shipped to the App Store. The web build is the one that is complete. ## CRXForge (Extension Template) — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.crxforge Cross-browser (Chrome/Firefox/Edge) Manifest V3 extension starter on WXT, React 19, and TypeScript, store-compliant by default. A rejection for remote code arrives after the work is finished. That is the shape of the problem. The extension is built, the listing is written, the screenshots are cropped, and then a reviewer finds a script pointing at a content delivery network and the submission goes back. The repair is rarely small either. The offending dependency is usually the authentication SDK or the analytics tag, and both were chosen in week one. CRXForge is a starter with those decisions already made. Google sign-in runs through the Chrome Identity API rather than the Firebase Auth SDK. Analytics is bundled: Amplitude over HTTP, Firestore over REST, Sentry compiled in, nothing fetched at runtime. The content security policy is a strict script-src of self. Permissions start minimal and grow only when something needs them. None of that is clever. All of it is the difference between a submission and a resubmission, which is measured in weeks rather than in hours when a store queue is involved. One codebase builds for Chrome, Firefox and Edge through WXT on Manifest V3, with React, TypeScript, Radix UI Themes and Zustand above it. Around that sit the pieces every extension rewrites: a typed wrapper over chrome.storage, a type-safe message bus between popup, background and content scripts, useAuth and useStorage hooks, and i18n scaffolding present from the first commit rather than retrofitted in month six. There is also a check that runs before you submit. A grep gate looks for remote code in the built output, because this particular rule is invisible to a type-checker, a linter and a passing build alike. A green build tells you the extension compiles. It tells you nothing at all about whether a reviewer will accept it. Then the honest part, which is why this is a template rather than a package. CRXForge is a degit scaffold. You copy it and it becomes your repository. It is not published on npm, it is not a live extension in any store, and the demo interface is deliberately thin because it exists to be deleted. That distinction matters more than it sounds. A scaffold you copy is a scaffold you own, with no upstream that can shift under you and no version to keep up with. It also means you inherit its mistakes permanently, since there is no mechanism to push a correction into a copy somebody took in March. What it cost is what every template costs. It goes out of date. WXT moves, a store revises a policy, a dependency ships a breaking release, and a starter that was correct in one quarter is quietly not correct in the next. So the useful way to read it is as a record of decisions rather than as code to trust indefinitely. Each compliance choice in it exists because the rule behind it is one browser stores state plainly, and the cost of finding that out late is a rewrite rather than an edit. It builds for Chrome, Firefox and Edge. Safari is not a target. There is no iOS release behind any of this, because there is no Apple Developer account here. MIT, at github.com/aoneahsan/crxforge. Read the pre-submission checklist before the first commit rather than after the first refusal. Reading it early costs an afternoon, and reading it late costs the difference between a store queue you entered once and one you entered twice. Open the live site Source on GitHub What it does, and what that costs to build Cross-browser builds for Chrome, Firefox, and Edge from one codebase Store-compliant Google sign-in via the Chrome Identity API Typed chrome.storage wrapper with a reactive useStorage hook Type-safe popup-background-content messaging bus Manifest V3 with strict CSP and minimal default permissions Bundled analytics pattern (Amplitude HTTP, Firestore REST, Sentry) with no CDN scripts Pre-submission remote-code grep gate and customize-it checklist React 19 + Radix UI Themes accessible popup and options pages Built with WXT 0.20 React 19 TypeScript Radix UI Themes Zustand 5 Manifest V3 Chrome Identity API Vite ESLint 10 Yarn 4 Worth knowing browser-extension manifest-v3 wxt react typescript starter-template chrome-extension cross-browser Who built CRXForge? Ahsan Mahmood built it out of extensions he had already shipped. It is MIT licensed and lives at github.com/aoneahsan/crxforge, which is where you copy it from rather than install it. That provenance is the only real reason to prefer it over any other starter, because the compliance decisions in it are the ones a shipped extension has to make rather than the ones a starter can leave to the reader. A template written by somebody who has not shipped one looks identical until the constraint bites. Is CRXForge an npm package? No. It is a degit scaffold, copied with npx degit aoneahsan/crxforge, so it becomes your own repository from the first commit. It is not published on npm and it is not a live extension in any store, and saying that plainly is the whole point of this answer. What you get is ownership with no upstream to track. What you also get is permanence, because a fix made here later cannot reach a copy you took in March. Does CRXForge work on iPhone? No. It produces desktop browser extensions for Chrome, Firefox and Edge, and none of those is a phone target. Safari is not a target either, and there is no iOS release behind any of my work, because there is no Apple Developer account here and nothing I build has shipped to the App Store. If a Safari extension is what you need, this template will not get you there, which is better said on this page than found out during a build. Why does Google sign-in go through the Chrome Identity API? Because the Firebase Auth SDK fetches code at runtime, and a submission carrying it comes back. The Identity API keeps the entire sign-in path inside the file the reviewer downloaded, which is the single substitution that removes the most common reason an extension is refused. The analytics choices follow the same logic: Amplitude over HTTP, Firestore over REST and Sentry compiled in, none of them pulled from a CDN. A strict script-src of self is what turns that intention into something the browser enforces. What do I have to replace before submitting? The demo interface first, because it is deliberately thin and exists to be thrown away. After that the customise-it checklist covers the parts a template cannot decide for you: the permission set, the listing copy, the analytics keys and the extension's own identity. A grep gate runs before submission and looks for remote code in the built output, which matters because that rule is invisible to a type-checker, a linter and a green build alike. ## LinkedIn Posts Automation — a project by Ahsan Mahmood URL: https://aoneahsan.com/projects/com.aoneahsan.a1la A Cloudflare Worker plus Supabase system that auto-publishes pre-written LinkedIn posts via the official API, with a React admin dashboard. The obvious way to post on a schedule is to drive a browser and click the buttons for somebody. This does not do that. Posts go out through LinkedIn's official Posts API, so the account is doing something the platform has a documented route for rather than something that only looks like a person typing. Nothing here scrapes a page. Nothing here signs in as you. That decision costs the project every convenience a scraper would have bought, and it is the reason the rest of the design looks the way it does. A Cloudflare Worker holds the schedule. It builds a daily plan weighted by engagement, inside windows you set yourself, then publishes what the plan picked: the post, its card image, and the link placed in the first comment rather than in the body. Why the first comment helps is not a claim I will make here. It is what the tool does. What LinkedIn does with it afterwards belongs to LinkedIn. Supabase is the source of truth for selection. That sounds like plumbing until you think about what a scheduler is for. A post published twice is worse than a post never published, because the second copy is visible to everybody who saw the first and there is no undo that removes the memory of it. So selection happens against the database, and a published item is never picked again. The words themselves are written in advance. A pipeline reads pre-written post files, seeds the database from them, and the scheduler works through what is there. Nothing generates text at publish time. That is a smaller product than one that writes for you, and smaller on purpose. A queue of sentences somebody meant is worth more than a queue a machine filled overnight, and the scheduler's only job is deciding when each of them goes out. The dashboard is a real application rather than a settings file. It is React with Capacitor, signed in through Firebase, reading over TanStack Query, and it exists so cadence can be watched, priorities and windows edited, and a single post skipped or pushed back into the queue without anyone going near the worker. There is a second way in for the times a dashboard is the slowest thing to reach for: an operator API behind a bearer token, giving status, logs, the current plan and a manual run. Configurable email notifications carry the digests, the finished runs and the warnings. Now the part that does not go away. LinkedIn's authorisation is renewed by hand, roughly every 60 days. The system warns before the token expires and halts cleanly on a 401 instead of hammering a dead credential. It cannot renew itself. This is automation with a standing appointment, which is the honest description and the thing a page like this usually leaves out, because runs itself reads better than runs itself until the sixty-first day. Writing that limit down was cheaper than being surprised by it twice. The documentation says so in the same place it says everything else the API will not do. The whole stack sits on free tiers. A Worker on a cron, a Postgres database, a small dashboard: none of it needs a server kept alive, and none of it carries a bill that grows with the number of posts. It is at a1la.aoneahsan.com. The re-authorisation is still in the calendar, and it always will be. Open the live site What it does, and what that costs to build Tier-aware engagement-weighted daily post scheduler with configurable windows Publishing through LinkedIn's official Posts API with card image and first-comment link Supabase source-of-truth selection that never reposts a published item Bearer-token operator API for headless status, log, plan, and run control React and Capacitor admin dashboard to monitor, configure, and requeue posts Content pipeline that parses pre-written post files and seeds the database Token lifecycle handling with expiry warnings and clean 401 halts Configurable email notifications for digests, completions, and token expiry Built with Cloudflare Workers Hono TypeScript Supabase PostgreSQL Cloudflare KV React 19 Vite Tailwind CSS Capacitor Firebase TanStack Query Worth knowing linkedin automation cloudflare-workers supabase serverless scheduler react capacitor Who built LinkedIn Posts Automation? Ahsan Mahmood built it alone, and it is at a1la.aoneahsan.com. One person wrote the Cloudflare Worker that runs the schedule, the Supabase schema that decides what publishes next, the content pipeline that seeds it and the React and Capacitor dashboard that operates it. The pieces are Hono on Workers with Cloudflare KV, Postgres on Supabase, and TanStack Query with Firebase Google sign-in on the admin side. Nothing here was assembled from a template, which is also why the awkward parts, such as the manual re-authorisation, are written down rather than hidden. Does it scrape LinkedIn or drive a browser? No. Publishing happens through LinkedIn's official Posts API, and there is no headless browser, no page scraping and no borrowed session cookie anywhere in the design. That is a limit rather than a feature, because it rules out everything the API does not expose and leaves the product living inside somebody else's documented boundary. What LinkedIn permits, rewards or changes next is theirs to decide, and it is not something this page will speak for. The tool does what the API allows and stops at that edge. What happens when the LinkedIn token expires? It stops, and it tells you first. Authorisation has to be renewed by hand roughly every 60 days, so the system watches the expiry date, warns ahead of it, and halts cleanly on a 401 rather than retrying against a credential that is already dead. Configurable email notifications cover digests, completed runs and the expiry warning itself. There is no way around the renewal from inside the product, and pretending otherwise would only mean the queue stopped publishing on a morning when nobody was looking. Can the same post be published twice? No, because selection reads from the database rather than from a file on disk. Supabase is the source of truth: each item carries its published state there, and the scheduler picks only from what has not gone out yet. The seeding pipeline can be re-run over the same pre-written post files without sending anything a second time. That matters more than it sounds, because a duplicate is visible to everyone who saw the first one and deleting it does not undo having sent it. Does LinkedIn Posts Automation work on iPhone? No, there is no iOS build. The scheduler is a Cloudflare Worker and never runs on a phone at all, while the admin dashboard is a web application any browser can open, packaged with Capacitor for Android. There is no Apple Developer account behind this work, so nothing here has been submitted to the App Store. The only product link the record carries is the web one at a1la.aoneahsan.com, and that is the surface worth checking. ## What does zero-cost infrastructure cost you? URL: https://aoneahsan.com/blog/what-zero-cost-infrastructure-costs-you Nothing paid sits in the critical path, so the product costs nothing to run. What that takes off your bill, what it forbids, and where a free tier ends. Nothing paid sits in the critical path, so the product costs nothing to run. The constraint decides the architecture before it decides a bill. Where a free tier stops being free is stated up front, not discovered in production. What it removes from your side is a monthly invoice for keeping it switched on. What it does not remove is capability. The rule itself is short. Free-tier infrastructure, work done in the browser wherever the browser can do it, and no paid service in the critical path. Zero-cost infrastructure is the short name for it. I am Ahsan Mahmood, and every product in the catalogue is built under that rule. It has to run without a paid service in the critical path, without a server that must be kept alive, and without a vendor whose pricing can move under you. That began as a budget decision and became a design one. Every service page states the same constraint beside the work it describes. One word does the damage here. The rule is about what the product costs me to keep switched on. It says nothing at all about what the product charges the people using it. A paid plan sits on top of free-tier infrastructure without contradicting a line of this post. What leaves your bill, and what stays on it? Three things leave. A rented server, a paid service sitting in the middle of a feature, and a vendor whose pricing can move under you while the product sits idle. What stays is what you were always going to pay. Hosting, third-party services and store fees are yours and are paid to those vendors directly. So the invoices arrive with your name already on them. A domain, a store registration and an API you chose to pay for with your own key in it. There is no line item for keeping the thing switched on. The costs that do stay with you are written down on pricing . None of that is accidental. The list of what a product may depend on is short. It is settled before the first screen is drawn. Why does the running cost decide the architecture? Because a constraint that hard forces one question early: does this feature need a server at all? The answer is no far more often than the industry's defaults suggest. The version that does not need one arrives with predictable running costs and no surprise invoice. It rules things out, which is the point. A constraint that never costs you anything was not a constraint. In practice that means work done in the browser wherever the browser can do it. The database is Firebase or Supabase on a free tier, picked because it survives a real launch, with the query budget treated as a real constraint rather than a formality. A server appears only where one is genuinely required. Laravel, or a Cloudflare Worker. What does the constraint forbid? An architecture that only works while traffic is small. The free tier is a budget, not an excuse, so a design that falls over in the week the product succeeds is not a saving. It is a bill nobody has told you about yet. It also forbids a feature that needs a paid API in the middle of it, unless you are paying for that API and know you are. That sentence sits on the about page , among the things I say I will not build. Concretely: the search cluster sized for a catalogue that does not exist, and the always-on service kept warm for traffic that has not arrived. Both are cheap to draw on a diagram and expensive to keep switched on. Both would land on your bill rather than mine. Where does a free tier stop being free? At the quota, usually without warning. A free-tier database meters reads rather than money, so a careless query does not arrive as a bill at the end of the month. It takes the product off the air for everyone using it until the counter resets. The exam platform went down on a day nobody had changed anything, because a free-tier read quota ran out. Nothing had broken. A screen fetched a whole collection so it could filter it in the browser, which is unremarkable with a few dozen rows in development and ruinous with real content in production. Every automated check on the way to production had been green. The platform is ImtehanHub , and the rule that came out of that day sits in every data layer I have written since. An unbounded list read is a bug on the first day and an outage on the day the collection grows — so every list query carries an explicit limit and the database does the filtering, the sorting and the counting. A quota does not degrade politely. It stops. What does the constraint not cost you? Capability. Sign-in, a database that enforces its own access rules, file storage, transactional email, push notifications and error tracking are all available under the rule. What changes is which provider gets picked and how the data layer is written. That is my problem rather than your users'. ZTools is the product I would point at. It began as a web app of utilities for Zaions, my own company, and I rebuilt it into a tools platform with an Android app, a browser extension and a docs site beside it. All of those are live. None of that changed what it costs to run. One item on that capability list I did not want to rent. Where a product needs file storage or transactional email, that is FilesHub, a platform I built and operate myself. It exists precisely so the free-tier rule did not have to bend the first time an app needed to send a password reset. FilesHub is a server, so it does cost something to run. I pay for it. That cost is one platform carrying every product I have, not a line item on each one. It does not reach your bill. I would rather own that piece than route your files through a service whose pricing page I do not control. Who runs the product when I am not available? You, or whoever you hire next. The repository, the store listings and the documentation are yours. Nothing needed to run or rebuild the product stays on my machine, and nothing is licensed back to you. The handover is written on the assumption that I am not around to answer questions about it. Full IP transfer of the delivered code lands on final payment, with a handover that names where the thing is deployed and what it depends on. Anything past the delivered code, such as the hosting it sits on or the accounts it runs on, is what the scope says. It is not a term I publish on a page that has never seen your setup. So what a delivered product depends on is named in writing at the handover, rather than found later in a stack trace. There is no server only I know how to restart, and no month in which the cheapest way out of a dependency is to keep paying me. Finished means it runs without me. So what does it cost you in the end? An architecture that only makes sense at a scale you do not have yet. Designs of that shape get ruled out. If one of them is what you had in mind, you hear it before anything starts rather than in month four, when the invoice for the message queue arrives. It is a real limit. If what you are building genuinely needs that architecture in its first month, this is the wrong constraint for it — and I would rather say so than take the work. Tell me through the contact form what you are building and roughly where you expect it to run. If you want the engineering half of this, a zero-cost backend architecture that actually holds is the one to read next. Does a product that costs nothing to run have to be free to use? No. The rule is about what the product costs me to keep switched on, not about what it charges the people using it. A paid plan sits on top of free-tier infrastructure without contradicting anything. One word is doing the work of two ideas here, and only one of them is mine. Which running costs still stay with me? Hosting, third-party services and store fees are yours and are paid to those vendors directly. A domain, a store registration and any API you decided to pay for are invoices with your name already on them, and none of them route through me. What does not appear is a line item for keeping the product switched on. Is a free tier safe to launch a real product on? Yes, when the quota is treated as the specification rather than as headroom. That means the database filters, sorts and counts instead of the browser, every list read is paginated with an explicit limit, and a query written without one is a defect on the day it is written, not the day the table grows. Where it is not safe is under an architecture that only works while traffic is small. Does the constraint limit what my product can actually do? Sign-in, a database that enforces its own access rules, file storage, transactional email, push notifications and error tracking are all available under it. What changes is which provider gets picked and how the data layer is written, which is my problem, not your users'. What it genuinely rules out is a feature that needs a paid API in the middle of it, unless you are paying for that API and know you are. What happens if the product grows past the free tier? It moves onto a paid tier of the same provider, and that move is planned for, not discovered. The database reference and URL live in the application's configuration and nowhere else, so pointing the product at a bigger project is usually a configuration change rather than a rewrite. The constraint is a starting position, and it is meant to be outgrown. ## Can I hire a full-stack developer remotely? URL: https://aoneahsan.com/blog/hire-a-remote-full-stack-developer-across-time-zones Yes. Pakistan Standard Time, UTC+5, asynchronous by default, with calls across US, UK, EU and Australian hours, and a first reply within one business day. Yes. Pakistan Standard Time, UTC+5, asynchronous by default, with calls scheduled across US, UK, EU and Australian hours, a weekly demo you can watch run, and a first reply within one business day. My name is Ahsan Mahmood. I am a full-stack developer working from Pakistan. The client work on this site was built for people who never met me, in time zones that were not mine. The rest of it is my own. What goes wrong when you work with a developer in a different time zone? Four things, and they are one fear wearing four coats. Being over-promised and then abandoned mid-build. Paying for something that never ships. Doubting that one person can genuinely cover design, frontend, backend, mobile, store submission and security. Lock-in — a codebase and a set of accounts that only make sense to the person who set them up. Each of those has the same root. You cannot see the work, so you are asked to trust a description of it. The description is written by the person being paid to produce it. Distance creates none of them. What distance removes is the reassurance you would otherwise get for free — the corridor conversation and the person visibly at a desk — so on a remote engagement that reassurance has to be built into the way the work runs rather than absorbed from the room. What time zone are you in, and when can we talk? Pakistan Standard Time, UTC+5. I work asynchronously by default and routinely schedule calls across US, UK, EU and Australian hours. A message sent in the North American afternoon is usually answered while you sleep, because a business day here is measured from when I read it rather than from when you sent it. The overlap is not generous. US Pacific sits furthest from UTC+5, so a call into it costs somebody a real slot in an early morning or a late evening. I would rather say that plainly than describe a working week that quietly assumes you will take the inconvenient end of it every time. How does the work actually run week to week? Weekly progress demos against milestones, not a status report. You see the thing running. If something slipped you hear it that week, while there is still time to change what we do about it. Asynchronous by default means the work does not wait on a meeting. Decisions that need me arrive written and get a reply within one business day. The demo at the end of the week is where anything still ambiguous gets settled in front of you, not in a thread. A status report is a claim about progress. A demo is evidence of it. That difference matters more at distance than it does in a room, because at distance the claim is the only thing you would otherwise be given. You also get one accountable person. The person who modelled the data is the person who wrote the security rules, packaged the Android build and answered the store reviewer. Nobody is behind me. What has this way of working shipped? ZTools is the clearest case. It is a tools web app built for Zaions, my own company. Nobody was supervising any of it. It began with 20 tools. I rebuilt it rather than extending it. The redesign took it past 550 tools. Then it grew an Android app, a browser extension and a documentation site. All three product surfaces are live — the web app at ztools.zaions.com, with the Play and Chrome listings beside it on its project page . That is the version that sounds like progress. Here is what it cost. Roughly two years passed between v1 and the version running now. It was built while other products were being built beside it. No client was watching any of that. There was no weekly demo, because there was nobody to demo it to. The work moved anyway. That is still true this week. Trizlink, LifeWell, LabFlow, ClearHire and HabitForge are all live right now while I redevelop each of them, and this site is going through the same rebuild. Every one of those is open in a browser today and still being worked on, so you can look at any of them without asking me how it is going. What happens to the code if the developer disappears? Full IP transfer, documentation that matches the code, and a handover that assumes I am not available. If the project only runs while I am on it, I have not finished. You are not buying a codebase only I can read. That promise weighs more remotely. Lock-in you cannot inspect is the kind you find out about late, and nobody can walk to my desk to check whether the documentation was ever written. The services page names what each engagement excludes beside what it includes, because an unstated exclusion is the thing nobody discovers until the week it starts to matter. Does this work for a remote role? Yes, and it has taken three shapes already. I have worked inside a team as an ordinary developer, as a manager leading one and as a tech lead. Perkforce is the clearest example, and it is already named on the about page of this site. I started as a developer. Over time I moved from my own tasks to reviewing pull requests and making the technical decisions. I left as the tech lead, running every flow from start to end, which by then was effectively a one-person team with AI doing part of the lifting. Since March 2026 I have been independent, working on my own products full time and taking client projects alongside them. Fixed price, milestone, retainer or fractional — whichever fits the shape of the work. What does working this way cost you? Hours. A question asked at the end of your working day is usually answered before the start of your next one rather than in the ten minutes you were hoping for. Same-minute answers are not offered. One business day is the commitment and anything faster is luck, which is worth knowing before a team is organised around it. The second cost is that I will sometimes tell you the work is not mine to take. I would rather say that on the first call than halfway through it, and that is the same sentence whether the work is a project or a role. The first call is thirty minutes and costs nothing, and it is where that gets decided. Tell me what you are building and roughly when you need it . If you would rather read before you write, the next post is the junior-to-senior journey , which covers the same working pattern from the inside rather than from a client's side of it. Neither of those costs is created by the time difference. Distance only exposes them. Will we ever be online at the same time? Yes — calls are scheduled across US, UK, EU and Australian hours. I am on Pakistan Standard Time, UTC+5, so the overlap with the UK and Europe is comfortable and the overlap with US Pacific is the one that costs somebody an early morning or a late evening. I schedule into it, and I do not describe it as wider than it is. How does an engagement start? With a call, before anything is quoted. The first call is thirty minutes and costs nothing. It is where I say whether I am the right person for the work, because I would rather tell you that on the call than have us discover it together in month two. Can you work inside my team's existing process? Usually yes. I have worked inside a team as a developer, as a manager leading one and as a tech lead, so the question is which of those a role needs. Which one it is gets settled on the first call. Who owns the code and the accounts when the work ends? The delivered code is yours, with full IP transfer on final payment. The accounts are whatever the scope says they are, so if the deployment and the accounts it runs on are to come with it, that is written into the scope rather than assumed. The handover names where the thing is deployed and what it depends on, and it is written on the assumption that I am not available, because a repository on its own is not a working product. How do I know work is actually happening between calls? The weekly demo is the answer, and between demos the work is visible in what changed rather than in a report about it. Trizlink, LifeWell, LabFlow, ClearHire and HabitForge are all being redeveloped right now while staying live, and this site is going through the same thing. If something slipped you hear it in that week's demo, while there is still time to change what we do about it. ## How long does a web app, an MVP or a full product take? URL: https://aoneahsan.com/blog/how-long-a-web-app-mvp-or-full-product-takes Small features 1–2 weeks, an MVP 4–8, a full production app 8–16. Where those ranges come from, how each is delivered, and what makes one of them slip. Small features 1–2 weeks, an MVP 4–8 weeks, a full production app 8–16 weeks. Those are ranges I have hit on work of that shape before. Something I have genuinely not built before gets a range and a spike, not a confident date. That is the whole answer. A spike is a short piece of work with one output: a better estimate. It is agreed as a milestone like any other. My name is Ahsan Mahmood and I work alone: one person, one React and TypeScript codebase, web and Android out of the same repository. Why does every timeline answer sound like a dodge or a lie? Because two of the common answers are unusable. One is a refusal to give a number at all. The other is a confident date for work nobody has done before, which holds right up until the first thing nobody accounted for. Both cost you the same thing. You cannot plan a launch, a hire or a marketing date around either of them, so you end up guessing on top of somebody else's guess. A fixed number attached to an unscoped brief is a number one of us is going to be unhappy about. How do you know how long a web app or an MVP will take? From work of the same shape. Small features 1–2 weeks, an MVP 4–8 weeks and a full production app 8–16 weeks are bands I have hit before on projects that looked like this one. That is a different claim from a promise about yours. The unit is a shipped thing rather than a sprint count. Started is not shipped. A range I have hit on work of that shape before is the strongest thing I am willing to say before a scoping call, and it is still a range rather than a date. You can open the products. FilesHub , LifeWell and ZTools each have a page on this site with the live URL that resolves it. Those pages prove the things shipped. They do not carry how long each one took, so the spans below are my own account of that. LifeWell took me about eight months. Not eight months of a scoped engagement: a v1 that worked, then a full redevelopment of the UI, the UX and the backend flows. The redevelopment is the part worth understanding. A v1 that works is not the same artefact as a product whose interface, flow and backend hold together. It cost months no estimate of the v1 could have contained, because the thing being estimated did not exist yet. ZTools ran longer. It started as a web app with 20 tools and became a redesigned product with 550+, with an Android app, a browser extension and a docs site behind it. That took me about two years. FilesHub, at the other end, took me about five months to a fully working, integrated utility. Those spans are the reason the three bands are quoted the way they are. They are product lifetimes: my own products, carried through a v1 and a rebuild while other products were being built beside them. A band describes something much narrower — a scoped client engagement worked to one milestone plan, with a demo every week. That difference is also why a range is quoted only after scope, and never before it. Something I have not built before does not get a date. It gets a range and a spike, and what comes back from the spike is a narrower range plus whatever it found on the way. That is not a hedge. An estimate with evidence behind it gets narrower as the work goes on. One without evidence does not. What counts as a small feature, an MVP, or a full production app? What already exists, mostly. A small feature is one feature, integration or fix on a codebase that already exists, with thirty days of bug fixes after delivery. That is the only one of the three I can define on a page rather than on a call. The other two are scope questions wearing time labels. The services page carries the same three ranges beside the work they apply to, and where the line falls between an MVP and a full production app gets settled on the scoping call, in writing, before anything is quoted. What moves that line is scope. How much of the product has to survive real users, real data and a store reviewer. What adds time to a Play Store submission? Four things people forget: the merged manifest audit, the Data Safety form, the ten App-content declarations and a listing written to pass review rather than to sell. The surprise is rarely the build, because Google Play listings are packaged with Capacitor from the same web codebase rather than rewritten. Those four add more than most plans allow for. Not one of those is optional. I start with the merged manifest. The permission list a reviewer reads is the merged one rather than the one you wrote. The Android build behind this site carries 26 permissions in its merged manifest, and 21 of them were injected by a single plugin. Finding that at submission is expensive. The queue itself is not mine: a submission waits on a reviewer I do not employ, and no amount of planning on my side moves that. What do you get to see each week? A working demo against the milestone every week. Most projects start within 48 hours of acceptance, and after that the demo replaces the status update. If something slipped, you hear it that week, while there is still time to change what we do about it. You watch the thing run. That start is the easiest date in this post to keep, because starting is the part that only needs me. The rest needs both of us. A milestone you can watch run is also a date that has already been tested, and estimating the next one from a demo beats estimating it from a brief. What makes a software timeline slip? Three things. Only one of them is mine: work nobody has done before, which is what the spike exists to shrink. A decision on your side that a milestone cannot start without. And a queue at a store or a third party that neither of us owns. My own estimate is the one I can act on, and the spike is how I act on it. The other two are yours and the world's, and pretending otherwise is how a plan quietly becomes a story about who is to blame for a week nobody named at the time. The demo is where all three become visible, and where a slipped week is traced to one of them. A slipped week is said in the week it slips. That is not a comfortable discipline. A demo showing less than last week's plan is a worse meeting than a status email, and it is the only version of the meeting that leaves you time to decide something. What ends an engagement is a scope agreed at the start, and my own products never had one. Trizlink took about two years to finish every flow. ClearHire took about ten months. HabitForge is seven-plus months in and still going. All three are live. None of them was a scoped engagement, and none is what the three bands describe. Two limits belong here. The first: I do not quote a date from a brief alone, so the range you get before a call is the published one and nothing narrower. The second is money. $800 is the floor for a small, well-defined feature rather than an estimate of yours; the rest of the ladder sits on pricing . Tell me through the contact form what you are building and roughly when you need it. The first call is thirty minutes and costs nothing, and it is the call where I say whether I am the right person for the work. If the Android half is what your calendar turns on, what Capacitor actually costs is the next thing to read. Every range on this page was hit on work I had already done. Yours gets one after the call. Can you add one feature to an app someone else built? Yes. One feature, integration or fix on a codebase that already exists, with thirty days of bug fixes after delivery. That is the shape the 1–2-week band describes, and it is the only one of the three sizes I can define without a call. What decides where it lands inside the band is how much of the existing codebase the change has to touch, and how much of it I have to understand before touching anything. How long does Google Play review add to a launch? I do not publish a number for it, because the review queue is the store's and not mine. What I can size is the work in front of it: the merged manifest audit, the Data Safety form, the ten App-content declarations and a listing written to pass review rather than to sell. Those sit inside the estimate. The queue does not. Can you commit to a fixed launch date? No. What replaces it is a range, a milestone plan and a working demo every week, so a date gets corrected by something you watched rather than by an email in month two. If a date is genuinely immovable, the scoping call is where we work out what fits inside it, and it is also where I say whether I am the right person for the work. What is a spike, and when would you use one? A spike is a short piece of work whose only output is a better estimate. It is for the part of a project nobody involved has built before, where a range is the honest answer and a date is not. It is agreed as a milestone like any other, and what comes back is a narrower range plus whatever it found on the way. Why are your own products measured in months when the bands are measured in weeks? Because my own products are lifetimes rather than scoped engagements: a v1, then a redevelopment, built while other products were being built beside them. LifeWell took me about eight months that way and ZTools about two years, from 20 tools to 550+ across web, Android and a browser extension. The bands describe a scoped client engagement worked to one milestone plan with a demo every week, which is the only shape I will attach a number to before the work starts. ## How much does custom software cost, and what is in a quote? URL: https://aoneahsan.com/blog/how-much-custom-software-costs-and-what-a-quote-includes $800 is the floor for a small, well-defined feature. Everything above it comes from a scoping call, in writing. What that quote contains, and what it omits. $800 is the floor for a small, well-defined feature. Everything above that comes from a scoping call, in writing, before any work starts. You are probably holding more than one number. The number for your work comes from scope, integrations and timeline. A brief carries none of the three. My name is Ahsan Mahmood. Since March 2026 I have been independent, working on my own products full time and taking client projects alongside them. This post is about the document that arrives after the call, and about the parts of an engagement people usually find out about later — the parts that decide whether two quotes were ever comparable. Why do two quotes for the same project differ so much? Because they are answers to different questions. Each one encodes a different guess at scope, a different list of what is included and a different idea of what happens once the work is delivered. The numbers look comparable because they are both numbers. They are not describing the same work. One prices a build. One prices a build and a launch. One prices a build, a launch and the months after it, without saying anywhere that it does. The clauses that decide the difference are rarely the ones in bold. They are the exclusions, the terms for what happens after delivery and the line about who owns the code. A quote that answers none of those is not cheaper than one that answers all of them. It is shorter. What can be quoted before a scoping call? Floors, never a quote. $800 is the entry floor for work nobody has scoped: one small, well-defined feature, integration or fix on a codebase that already exists, with thirty days of bug fixes after delivery. It describes a shape of work rather than a project. The services page carries the same floor beside the work it applies to, and pricing carries a floor for each defined package. I do not restate either here. A fixed number attached to an unscoped brief is a number one of us is going to be unhappy about. The call is free. It is where I say whether I am the right person for the work. What goes wrong when the number comes before the scope? Either the number goes up later, or the part nobody wrote down never gets built. Both are worse than the conversation they replaced. The revision is the outcome you were told would not happen. The omission is the one neither of us sees until month two, when a real person needs the thing the brief never named. Watch how it happens. A brief says the product needs user accounts. Everyone reads that sentence and pictures something slightly different: a sign-in form, or a sign-in form plus password reset, email verification, an administrator who can suspend somebody and a record of who did what. Nobody is careless. The sentence holds more than one feature and reads like one, which is how briefs work rather than a failure of anybody's attention. The number is agreed against the shortest reading of it. In month two the rest of the reading arrives, because a real person needs to reset a password and a real operator needs to suspend an account. Now the two of us are in a conversation neither of us wanted. The number is agreed and the work is underway, so the difference has to come from somewhere. There is a blunter version of the same problem. Say the agreement is a todo app. A week in, the request is to add e-commerce features to it, on the original date and for the original price. That is not going to happen. Requirements change, so the cost changes and the timeline changes with them. I say which of the two moved and why, in plain terms, rather than absorbing the change and letting the work come out smaller. A change is re-scoped and re-quoted before it is built. I would rather tell you on that call that I am the wrong person for the work than discover it together in month two. What is actually in a written quote? Scope comes first, and the number follows from it. The document names what is being built, what it integrates with and what the timeline is, which are the three things the number comes from. It arrives in writing after the scoping call. Never from a brief alone. The exclusions are the part a brief never carries — and the part that decides the number. An exclusion can read like this: the delivered code transfers on final payment, and the deployment it runs on and the accounts behind it are not included unless this scope names them. That line is written before work starts, so a change to it later is a conversation rather than an argument. Four shapes are available: fixed price, milestone, retainer or fractional. The work decides which one fits. Milestone work is delivered against a working demo every week, not a status report. A retainer is a set number of days a month for ongoing work, and it is also what maintenance after delivery is bought as. Underneath all four the engagement is the same. A scoping call that costs nothing and runs thirty minutes. Scope in writing before anything is built. Thirty days of bug fixes at no charge after delivery. Who owns the code when the work ends? You do. Full IP transfer of the delivered code on final payment, with a handover that names where it is deployed and what it depends on. Anything beyond the code, such as the deployment itself or the accounts it runs on, is whatever the two of us agreed in the scope. That last part is an example rather than a published term: what transfers beyond the code is per engagement, written into the scope before work starts. Who pays the vendors is published and is separate. Hosting, third-party services and store fees are yours, paid to those vendors directly. The handover assumes I am not available. Documentation that matches the code, and a codebase typed end to end and structured so a second developer can find things. You are not buying a codebase only I can read. If the project only runs while I am on it, I have not finished. What does the $800 floor not cover? A product. The floor describes one small, well-defined feature on a codebase that already exists, with thirty days of bug fixes after delivery — and a product is a different question that a call answers rather than a page. It is not a starting bid, and it is not an estimate of your build. After thirty days nothing is automatic. Maintenance, new features and on-call are a retainer you opt into. You can end it. I would rather you leave because you no longer need me than stay because cancelling was awkward. Three things stay with you in every shape above. The decisions, which a weekly demo can surface and cannot make. The accounts and the access the product runs on, unless the scope says otherwise. The maintenance after day thirty. That one you buy or you do not. Tell me through the contact form what you are building and roughly when you need it. The first call runs thirty minutes and costs nothing. The number that follows it arrives in writing before any work starts. If the question underneath yours is how to charge for the product once it exists, pricing and plans without a payment processor is the next one to read. No page can quote your build. That is what the call is for. What does a written quote actually contain? Scope first, then the number that follows from it. The document names what is being built, what it integrates with and what the timeline is, which are the three things the number comes from. It arrives in writing after the scoping call, not before it. What a scope excludes is written into that document before work starts, not published as a standing term. What happens if the scope changes halfway through the build? It is re-scoped and re-quoted in writing, the same way the first scope was. A change that arrives in month two is a change to the work. It is not a favour either of us owes the other, and there is a document from the start that both of us can point at while deciding what to do about it. The alternative is the one I will not take: absorbing it quietly and delivering something smaller than what was agreed. What does the $800 floor not include? Anything that is not one small, well-defined feature on a codebase that already exists. The floor covers that shape of work with thirty days of bug fixes after delivery. A product is a different question, and a call answers it. The floor is not a starting bid, and it is not an estimate of your build. The defined packages carry their own floors on the pricing page. Who owns the code when the engagement ends? You own the delivered code, with full IP transfer on final payment. The handover names where it is deployed and what it depends on, and the code is typed end to end and structured so a second developer can find things. Anything beyond the code, such as the deployment itself or the accounts it runs on, is whatever the two of us agreed in the scope, which is a per-engagement question rather than a standing term. Who pays the vendors is published and is separate: hosting, third-party services and store fees are yours, paid to those vendors directly. Which shape should the work take: fixed price, milestone, retainer or fractional? Whichever fits the shape of the work, which is a scope question, not a preference. All four are available and the scoping call is where the right one gets chosen. A retainer is a set number of days a month for ongoing work, and it is also what maintenance after delivery is bought as; it is cancellable and does not auto-renew. Can anything be quoted before a scoping call? No: a floor can be published, and a quote cannot. $800 is the entry floor for a small, well-defined feature on a codebase that already exists, and the pricing page carries a floor for each defined package. A floor is where a number starts rather than where it lands, so the number for your work still comes from scope, integrations and timeline. ## Can one codebase ship web, Android and a browser extension? URL: https://aoneahsan.com/blog/one-codebase-web-android-and-a-browser-extension Yes. One React and TypeScript repository becomes a web app, a Capacitor Android app, and a Manifest V3 extension, packaged three times for three stores. Yes — one React and TypeScript repository becomes a web app, an Android app packaged with Capacitor, and a browser extension listed on the Chrome Web Store, Firefox Add-ons and Edge Add-ons. The browser extension governs the other two. No remote code. Manifest V3 forbids it, so the shared code is written to the extension's rule from the first commit, long before anything is packaged for a store. My name is Ahsan Mahmood and I work alone. Most of what I ship follows that one pattern rather than three teams and three repositories that drift. Why do three codebases drift before anybody notices? Because drift is silent until a fix has to land twice. Three separate builds of the same product each look correct on their own. Each passes its own tests. The gap usually shows up the first time a bug is reported on one surface, fixed there, then reported again a week later on another. Nobody decides to drift. It arrives one small divergence at a time: a validation rule tightened in the web app, a date format corrected in the Android build, a permission renamed in the extension. By the time the three disagree enough to matter, no single commit caused it and there is nothing to revert. What the fix costs then is a reconciliation rather than a change. What does a React app give up to ship as a Chrome extension? Remote code. Manifest V3 allows none of it. Everything the extension runs has to sit inside the package the reviewer downloaded, so nothing is fetched at run time and executed. That is not a detail of the extension build. It is a constraint on the shared code. A shared layer that reaches for a script at run time works in a browser tab and works on Android. It fails review as an extension. So either the shared code is written to the extension's rules from the start, or the third surface turns out to be a rewrite rather than a packaging step. No remote code is both the store rule and the reason these listings keep passing review. What is shared, and what is built three times? The product is shared and the packaging is not. One React and TypeScript codebase carries the interface, the data layer and the tests. Three thin packaging layers turn that into a web build, a Capacitor Android build, and a Manifest V3 extension. ZTools is the one I would open first. It started as a web app with 20 tools for Zaions, my own company. There was no extension and no Android build. Then I rebuilt it rather than extending it: the redesign brought the Android app, the browser extension and a docs site, all in that same pass. That rebuild was the cost. A third surface is not a packaging decision made at the end. The web app is past 550 tools. Roughly two years separate the first version from the one running now. ZTools has a page on this site and the live app is at ztools.zaions.com . The Android half is the shorter story, and it has a post of its own. Google Play listings are packaged with Capacitor from the same web codebase rather than rewritten. Two npm packages carry the pattern between products instead of being copied into each one: native-update and strata-storage . The first delivers over-the-air web-bundle updates to Capacitor apps, with checksum and signature verification and automatic rollback when a bundle fails to boot. A package is how a decision stops being made twice. How does one codebase reach the Chrome, Firefox and Edge stores? Packaged three times, submitted three times. The three packages come out of one repository with no separate source tree behind any of them, and the differences between them are the manifest and the packaging layer. The product is written once; the package and the listing are made three times. Check that on Video Controls Plus . It is listed on Chrome , Firefox and Edge . One repository sits behind all three. Each listing is read by a reviewer who has not seen the other two. That is the part that does not compress, and it is the honest answer to anyone expecting one submission to cover three stores. Writing those three listings and answering three reviewers is part of what browser-extension engineering covers. Why are there no install numbers on this page? No download or install counts, anywhere on this site. They fluctuate, nobody can verify them, and the number is never the interesting part. An earlier version of this site made claims about my own work that a reader could not check. One was that a browser extension of mine had thousands of users. Another was that I was an expert in several things. None of them were verifiable. What that costs is not the one unchecked sentence; it is every true sentence sitting beside it. Those claims came down. What replaced them, when I rebuilt this site, is the rule the rest of this page runs on: a claim resolves to something you can open, or it is not made. So the three listings above are linked and the install counts behind them are not. What does one codebase not give you? There is no single release. A web fix is live when I deploy it. An Android build is live when the Play listing updates, and an extension update is live once each store has approved it, which is three clocks running on one commit. So a fix lands everywhere at once in the repository and reaches people at three different times. That gap on the Capacitor side is what native-update exists for. It does not give you three separate arguments with three reviewers. Because all three packages come out of one repository, a change asked for by one store is a change made in the code the other two ship. The strictest reviewer edits the product that every user of the other two gets. Nor does it give you one set of store paperwork. Android brings its own, named in the post that owns that half. The extension brings a listing on each of three stores. None of that is shared just because the codebase is. That price is paid up front. One limit points the other way. The web app inherits the extension's rules even where a browser tab would have allowed more. Which of your surfaces is strictest is usually what to settle first, because that is the surface your shared code obeys for the life of the product. When the Android half is what you are sizing, what Capacitor actually costs is the next thing to read. Tell me through the contact form what you are building and which of the three surfaces it has to reach. The first call is thirty minutes and costs nothing. Can a Chrome extension and an Android app really share a codebase? Yes, and the sharing is real, not partial. Both are packaged out of one React and TypeScript repository: the Android app through Capacitor, the extension through Manifest V3. What is shared is the interface, the data layer and the tests. What stays separate is each surface's packaging layer, its entry point and its store listing. What does Manifest V3 forbid? Remote code. Nothing an extension runs may be fetched at run time, so everything it executes sits inside the package the store reviewed. In a shared codebase that rule does not stop at the extension: it sets the ceiling for the web app too, which is written to the extension's limit even where a browser tab would have allowed more. Does a browser extension need its own backend? No. The extension is the same codebase as the web app, so it talks to whatever the web app already talks to. The Manifest V3 rule is about code rather than data: the extension may read from an interface it does not ship, and it may not download something to run. What happens when one store rejects the build? Nothing happens to the other two listings; they are separate queues. A rejection is answered in the shared codebase, so the correction reaches all three packages the next time they are submitted. How long a review takes is not a figure I publish. The store sets that clock, and I do not. Which extension stores do you actually publish to? The Chrome Web Store, Firefox Add-ons and Edge Add-ons. Video Controls Plus is listed on all three; ZTools is on the Chrome Web Store, alongside its Google Play listing and its web app. Both resolve from their pages on this site, so the claim is checkable rather than asserted. Each listing is written separately, for a reviewer who has not read the other two. ## What is actually changing in web development URL: https://aoneahsan.com/blog/future-of-web-development-2026 Not predictions. Four shifts I have already had to design around: crawlers that do not run JavaScript, the platform absorbing dependencies, types as the… I am not going to predict anything. What follows are four shifts that have already changed how I build, in the sense that I have had to go back and modify shipped products because of them. 1. A second audience that does not run your JavaScript For twenty years the answer to "who reads my page" was people and search engines, and search engines learned to render JavaScript. Now a meaningful share of the traffic that matters is an answer engine reading your page to summarise it — and those crawlers largely do not execute JavaScript at all. The practical consequence is blunt: if your content only exists after hydration, it does not exist for them. A single-page app with an empty root div is invisible to the fastest-growing channel there is. This is why I prerender everything now — real HTML per route, written at build time, with the app hydrating over it. Not server rendering, which needs a server. A post-build step that renders each route once and writes the file. It also changes how I write. The opening sentence of every section has to answer that section's heading, standalone, because an engine lifting one sentence out needs it to make sense with no context. Writing that builds toward a point never gets quoted. 2. The platform is absorbing the dependency list Intl replaced most of what a date library did at display time and all of what a pluralisation library did. structuredClone replaced a deep-clone helper. crypto.randomUUID replaced a package. Object.groupBy deleted a utility file from four of my projects. Container queries replaced most JavaScript-driven responsive logic. The dialog element replaced a category of modal library, at least for the simple cases. The habit worth forming is checking the platform first. The list of things that genuinely need a dependency has got shorter every year, and every dependency removed is one fewer thing to keep current, audit and eventually migrate off. What has not moved: rich text, charting, date arithmetic across timezones. Those are real problems and the platform has not solved them. 3. Types became the primary gate, and gates became the primary discipline This one is less about TypeScript and more about where correctness gets enforced. The pattern I trust now is replacing a convention with a constraint. Not "remember to paginate" but a helper whose limit is a required argument. Not "remember to use the translation layer" but a lint rule that fails the build on a hardcoded string. Not "the rebuild should match the design" but a test that parses the design's own manifest. Each of those started as a note everybody agreed with and nobody followed — not through carelessness, but because a rule you have to remember decays, and the person who forgets is usually the person who wrote it. The corollary nobody says out loud: watch every gate fail before trusting it. I have shipped a typecheck that exited zero on genuinely broken code because the configuration compiled nothing at all. 4. Constraints came back, and they improved the output For a decade the answer to a hard problem was a bigger instance or another managed service. That is still available and it is still frequently right. But the free tiers got good enough that an entire product can run on them, and choosing that constraint deliberately changes the engineering in ways I did not anticipate. A quota makes an unbounded query a production incident instead of a line item, so the read budget stops being a code-review preference and becomes architecture. No server-side compute pushes work into the browser, which is worse for old phones and genuinely better for privacy — the safest place to process someone's health data turns out to be their own device. I am not claiming constraint is virtue. Plenty of products need a server and should have one. What I am claiming is that the constrained versions of my own products have more careful data layers than the unconstrained ones did, and the constraint is why. What I think is overrated Being early. I have adopted things at version 0.x and paid for it in migrations, and I have adopted things two years late and paid nothing. The cost of being late is almost always smaller than it feels. And framework arguments. I have shipped production work in Angular, React, React Native, Laravel, Flutter and plain JavaScript, and the transfer between them is nearly total once you understand which problems each is solving. The decisions that actually determined how those projects went were about data shape, authorization and where correctness gets enforced — none of which the framework choice touched. The part I got wrong, and expect to get wrong again Worth including, because a piece like this is worthless without it. I spent too long treating accessibility as a pass you do near the end. It is not a pass; it is a set of decisions made while building, and retrofitting means auditing every component for focus handling, keyboard interaction and relationships that should have been there from the first version. The work is the same size either way — it is just far more unpleasant, and far more likely to be cut, when it arrives as a task at the end of a schedule. The same has been true of internationalisation. I have built the mechanism late twice, and both times the cost was not the mechanism — it was extracting several hundred strings from components that had grown around them. The pattern is identical in both cases: the cheap moment is the first one, and the thing that makes it feel expensive then is that its value is invisible until much later. That is exactly the shape of decision I expect to keep getting wrong, so the only defence I have found is to make the cheap version mandatory from the start even when it looks like ceremony. What I would tell someone starting a project this year Decide where correctness is enforced before you decide what framework enforces it. Every project of mine that went well had that answered early — the database refuses, the types refuse, the build refuses — and every one that went badly had it answered by convention and good intentions. Make your content readable without JavaScript, because a growing share of your most valuable audience will never run it. And pick your constraints on purpose. The ones I chose — no servers, no recurring cost, one codebase across surfaces — have shaped my architecture more than any framework I have used, and mostly for the better. Half-choosing them would have given me the awkwardness with none of the benefit, which is the one outcome genuinely worth avoiding. The one I would bet on That the gap between "it builds" and "it works" keeps getting more expensive, and the teams that do well are the ones who close it with mechanisms rather than with care. Care does not survive a deadline. A gate does. ## Pricing and plans without a payment processor URL: https://aoneahsan.com/blog/saas-pricing-stripe-custom-redirects Why my products take payment through a redirect and an admin grant instead of a checkout integration, what that costs, and the entitlement bugs that bite… The slug on this post mentions Stripe. What I want to explain is why my products do not use it, or any other processor — and, more usefully, the entitlement problems that are identical whichever way you take the money. What I do instead Payment happens through a single redirect to one payment page shared across every product I run. Someone pays, and then I set their plan from the admin panel. There is no webhook, no callback, no checkout session, no processor SDK in any bundle. That is a real trade and I will state both sides. What it costs: it is manual. Somebody pays and waits for me. It does not scale past the volume one person can handle, and it means an upgrade is not instant. What it buys: no processor integration to maintain across a portfolio, no webhook endpoint that has to be idempotent and correct or a paying customer silently gets nothing, no PCI surface, no per-transaction fee, and no dependency whose API version I have to track in twenty codebases. At the scale I operate — one person, many products — the second column wins comfortably. At a different scale it would not, and I would not pretend otherwise. A grant is three facts, not one This is the design point that transfers regardless of how you charge. An entitlement is not "this user is on Pro". It is three things: the plan they are on now, the plan they fall back to, and the date it happens. A grant with no end date is a free tier with extra steps, and it is the most common bug in a manual system. The second most common: expiring by a scheduled job only. One missed run and a lapsed plan silently continues, which nobody reports because nobody complains about getting more than they paid for. So the downgrade resolves on read . The user's effective plan is computed from those three facts every time it is asked for, and any job that tidies up is a convenience rather than the mechanism. null is unlimited, and Infinity is a bug The one that has bitten me hardest, and it is two lines: JSON.stringify({ entries: Infinity }) // '{"entries":null}' Store a limit as Infinity , round-trip it through JSON or a JSONB column, and it comes back as null . If your code reads a missing limit as "blocked", the tier you meant to be unlimited blocks every write. If it reads it as "unlimited", you have a different problem. The resolution has to be asymmetric, and it took me a while to see why: // Client: null means unlimited. const withinLimit = (count: number, limit: number | null) => limit === null || count < limit; Server-side it inverts — absence must fail closed, and only an explicit null means unlimited. A typo in a limit key and a genuinely unlimited tier look identical to jsonb ->> 'key' , and one of those two readings hands out unlimited access on a misspelling. A tier is a row, not an enum value Storing the plan as a Postgres enum makes adding a plan a migration and a deploy, and enum values can never be removed. Worse, it forces every policy to hardcode the limits, and those copies drift. A row in a plans table — key, rank, active flag, limits as JSONB — gives the same validity guarantee through a foreign key, and lets the database read the limits directly. Order by rank rather than by spelling, never rename a shipped key, deactivate rather than delete, and resolve an unknown key to the lowest active plan. The one people miss: do not hardcode the plan key set downstream. A deployed z.enum([...]) in a validation schema starts rejecting a user's own profile the day you add a tier. The client explains, the server refuses Both always exist. The client hides the button and explains the limit, which is the user experience. The server refuses the write, which is the boundary. A client-only gate is bypassed with devtools; a server-only gate is correct and hostile, because the person finds out by being rejected after doing the work. And the pricing page renders from the same records the enforcement reads. A hand-written comparison table is a second source of truth, and it lies by omission the first time a limit is added and nobody updates it. Downgrade never deletes When somebody stops paying, content over the new limit becomes read-only and the app says so. It does not disappear. Deleting somebody's data because they stopped paying may be destroying their only copy, and no reasonable reading of a subscription lapse includes that. It also makes the upgrade path trivial: everything is still there, so paying again restores access rather than requiring recovery. Metered features, and the rule I hold about bringing your own key Anything that costs money per use — AI features above all — needs a different treatment from a storage limit, because the cost is mine and it scales with enthusiasm. The shape I use: the free tier gets a real allowance rather than a taste, paid tiers get a larger one, and anyone using their own API key is not metered at all, on any tier. A call somebody else is paying for costs me nothing, and rationing it would be charging rent on their own credit card. Two implementation details that are easy to get wrong. The allowance is a value on the plan row, never a literal in feature code — a hardcoded number is a bug even when it is currently the right number, because it does not move when the plan does. And enforcement increments and tests in a single statement, then refunds on failure with a floor at zero; a read-then-write pair lets two concurrent requests both see the same remaining count and both proceed. The refusal message says it is a limit and says what to do about it. "Something went wrong" for a quota is the single most frustrating error in software, because the person cannot tell whether to retry, wait, or pay. Say the price honestly, and never write a never-claim One rule I follow absolutely: never write "there is no paid tier", or any permanent claim about money, anywhere — not in the interface, not in structured data, not in a machine-readable file, not in a store listing. That kind of sentence outlives the release that falsifies it. It ends up in a documentation site nobody re-reads, in a JSON-LD block nobody looks at, in a cached answer an AI gives about your product a year after it stopped being true. And because it is a claim about pricing, being wrong about it is not a stale detail — it is the product appearing to have changed its terms quietly. The same applies to the free tier's description. Describe what it includes today, and let the pricing page render from the same records the enforcement reads, so it cannot lie by omission the first time a limit is added. The store rule, if you ship a mobile app Purchase is web-only. The Android app shows plan status and gates features, and it never sells and never links out to a payment page — because the boundary is selling , and "upgrade on our website" is itself the anti-steering violation people get rejected for. The app can know what plan you are on. It cannot take you to the till. ## How this site is built, and what I rebuilt to fix URL: https://aoneahsan.com/blog/portfolio-architecture-react-19-vite-7 React 19, Vite, TanStack Router, Supabase and a click dummy that is the visual specification. Including the outlet bug that made nine service pages render… This site is a portfolio, which sounds like it should be a static page. It is not: sixty-nine project pages, nine service pages, a blog, a pricing page, an account area, twenty legal documents and a sixteen-screen admin panel — all reading from a database, all editable without a deploy. The slug on this post says Vite 7. The tree is on Vite 8. The URL is indexed and frozen, and its history is worth more than the version number in it, so it stays — but I would rather say that plainly than quietly write around it. The stack React 19 with the compiler, TypeScript, Vite, TanStack Router for file-based routing with typed search params, TanStack Query for server state, Zustand for the little client state that is left, Tailwind v4 with CSS-first tokens, React Aria for headless primitives, and Supabase for the database with row-level security doing the authorization. The rule underneath all of it is the same one that governs everything I run: it costs nothing to operate. That is not an afterthought — it is why there is no server rendering, why the read layer is built the way it is, and why storage and email go to my own platform. The bug that justified a rebuild The previous version had nine service pages at /services/ . Every one of them rendered the services list . The three-hundred-line detail component never mounted. The cause was a layout route rendering the list directly instead of an outlet, with the detail route nested underneath it. Two lines. What made it survive is the part worth learning from: the prerendered HTML was correct, because it was generated from data rather than from the rendered app. Every crawler saw nine distinct pages with the right titles and descriptions. Every human saw the same list nine times. Monitoring was clean. Search console was clean. The layout route now renders nothing but its outlet, with a comment saying why, and every subsequent layout route in the codebase does the same. The design is a specification, not a mood board The biggest process change: before any application code existed, the entire interface was built as static HTML and Tailwind — every page, every state, wired together so it could be clicked through. Sixty-one screens. Then the important part. It is not a reference somebody glances at; it ships a manifest declaring each page's band sequence, and a test parses that manifest and asserts the real application matches. A page that quietly restructures fails a gate instead of shipping. That has already caught things. When the assertion was first written it went red on fifteen routes that had been approved on their own screenshots — pages rendering fewer sections than the approved design, which nobody had noticed because each one looked fine on its own. That debt is now a two-way ratchet: a route not on the list that mismatches fails, and a route on the list that starts matching also fails, so the baseline has to be struck off as it is fixed and cannot rot into a permanent exemption. Three gates that exist because of a specific defect Every class is styled. An early wave invented a component-class vocabulary that typechecked, linted, built and rendered as unstyled markup. It looked deliberate. Now every class in the markup must have a rule in the stylesheets or appear in the generated utility output. Reachability. Every route must have at least one inbound link, and the check prints its denominator. A link checker cannot find this defect — a page nobody links has no dead href to detect. No unbounded read. Every function that reads rows must bound them. This started as a rule people were supposed to remember, until an unbounded query on another of my products exhausted a daily quota and took it offline for real users until midnight. Every one of them gets watched failing against a planted defect before it is trusted. I have shipped a typecheck script that exited zero on genuinely broken code, because in a project-references layout the root config compiles nothing and --noEmit reports success over an empty set. A gate nobody has seen go red is a gate nobody has verified. The database does the authorization Row-level security on every table, and the client holds a publishable key that is public by design — the key is meant to be in the browser, and the policies are the boundary. The five plan-administration capabilities are database functions rather than client updates, each checking admin status inside the function and writing its audit row in the same transaction, so there is no path that mutates without one. The columns those functions touch are revoked from the client role entirely. And the trap that makes all of this need real verification: a policy filters . It does not refuse. A forbidden update returns 200 having changed nothing, so every refusal has to be proven by reading the row back rather than by looking at a status code. The content model, and why almost nothing is hardcoded A portfolio is usually a repository full of markdown, which is fine until you want to reorder something from a phone. Here everything a visitor reads is a row: projects, services, skills, the home page copy, the payment options, the legal documents. The consequence I did not anticipate is how much it changes what "publishing" means. Adding a project is not a deploy, so the friction of keeping it current disappeared — and the previous version of this site had a projects list that was eight months stale, entirely because updating it meant opening an editor. The exception is interesting. Interface text — labels, buttons, empty states, validation, error messages — is not in the database. It is in a translation catalogue in the repository, keyed and typed, so a missing key is a compile error rather than a blank space on a page. The line between the two is whether the words describe the product or are content in the product, and getting it wrong in either direction hurts: catalogue text in the database cannot be translated, and content in the catalogue cannot be edited without a deploy. English only, and the mechanism complete anyway There is one language, and there will be one language for a while. The layer underneath is finished regardless: typed keys, interpolation rather than concatenation, plurals through the platform's own rules, dates and numbers formatted for the locale, and the language and direction attributes applied before first paint. The acceptance test is a single sentence — is adding a second language one new file and nothing else? If it needs a component edited, a string extracted or a plural form invented, the foundation is not built, however complete it looks. I know this needs enforcing rather than intending, because another of my products shipped three complete waves with a correct translation layer sitting in the tree and not one call to it. A hardcoded string passes typecheck, lint, build, and every review that reads for correctness. So this project carries a lint rule that fails the build on user-visible text that never reached the translation layer — landed as a warning over three hundred and twenty-eight findings, swept to zero, then switched to an error and watched rejecting a planted string before I trusted it. What is still ahead Prerendering and the machine-readable files, the Android build, and the cutover itself. The site you are reading this on is still served by the previous tree until that last step — which is its own kind of honesty: writing about a rebuild while the thing being rebuilt is still the thing serving you. ## The JavaScript features I actually reach for now URL: https://aoneahsan.com/blog/modern-javascript-2026-updates Intl for anything a person reads, structuredClone, at(), Object.groupBy, and the platform APIs that replaced dependencies. Plus the number formatting that… The useful question about new JavaScript is not "what shipped" but "what can I now delete". These are the ones that removed a dependency or a helper file from my projects. Intl, which is doing more work than any library I removed Anything a person reads that involves a number, a date, a currency, a list or a plural should go through Intl . It is built in, it is correct, and it is the single biggest source of quietly wrong output in most applications. The one that catches everyone: String(1234) // "1234" — wrong everywhere new Intl.NumberFormat().format(1234) // "1,234" in en, "1.234" in de A page that mixes hand-stringified numbers with formatted ones looks broken in every locale — including the one it was written in , because the two appear side by side and disagree. Then plurals, which is where hand-rolled logic is not merely inelegant but impossible: // This ternary is not a shortcut, it is a design that cannot be translated. const label = `${n} ${n === 1 ? 'post' : 'posts'}`; // Intl.PluralRules knows English has 2 forms, Polish 3, Arabic 6. new Intl.PluralRules(locale).select(n); // 'one' | 'few' | 'many' | 'other' | ... The ternary happens to cover English. No ternary can express Arabic, and by the time you find that out the pattern is in four hundred places. Also worth knowing: Intl.RelativeTimeFormat for "2 days ago", Intl.ListFormat for "a, b and c", and Intl.DateTimeFormat for everything a date library was doing at display time. I still use a date library for arithmetic. Formatting is the platform's job now. structuredClone const copy = structuredClone(original); A real deep clone, built in, handling Date , Map , Set , typed arrays and cyclic references. It replaced the JSON.parse(JSON.stringify(x)) trick, which silently drops functions and undefined , turns Date into a string, and throws on a cycle. That trick was in every codebase I have ever worked on and it was a bug in most of them. at(), and the end of a small annoyance items[items.length - 1] // before items.at(-1) // now Small, and it removes an off-by-one you can write while tired. Works on strings too. Object.groupBy const byYear = Object.groupBy(posts, (p) => new Date(p.publishedAt).getFullYear()); This one deleted a helper from four of my projects — every one of which had its own slightly different groupBy , and one of which handled an undefined key differently from the rest. Platform APIs that replaced packages crypto.randomUUID() — a proper UUID, no dependency. Secure-context only, which is the one thing to check. The Web Crypto API — real hashing and encryption. I use it to encrypt tokens at rest rather than shipping a crypto library into a browser bundle. AbortController — cancel a fetch, and it works for event listeners and observers too, which is the underused half. IntersectionObserver — lazy loading, infinite scroll, scroll-spy for a table of contents. It replaced every scroll-handler-with-a-throttle I ever wrote, and it does not fire on the main thread on every pixel. navigator.clipboard — with the important caveat that it rejects rather than throwing synchronously, and is undefined outside a secure context. Both paths must report failure honestly, because a false success tick is worse than no feedback: the person pastes whatever was in the clipboard before. The one I use most and would defend hardest Not new, but underused: satisfies in TypeScript, paired with as const . It gives you literal types and checking at the same time, so a typo in a table of navigation entries fails at the declaration rather than becoming a broken link three files away. CSS took over jobs JavaScript used to do Not JavaScript features, but they belong in the same accounting, because each one deleted code from my projects. Container queries are the big one. A component that adapts to the width of its container rather than the viewport is a component that works in a sidebar and in a full-width layout without knowing which it is in — which is what every design system wanted and what viewport media queries could never express. It removed a real amount of resize-observer plumbing from my component library. Logical properties — margin-inline-start rather than margin-left , text-align: start rather than left — cost nothing to adopt and mean right-to-left support is a language setting rather than a rewrite. I use them everywhere now, even where no second language is planned, because the alternative is auditing every stylesheet later. :has() removed a category of class-toggling: styling a card because of what is inside it, or a label because its input is invalid, used to require JavaScript adding a class and remembering to remove it. prefers-reduced-motion , prefers-color-scheme and color-scheme mean the browser tells you what the person wants, and honouring it is a media query rather than a settings screen. The habit, rather than the list The list above will be out of date. The habit will not: before adding a dependency, check whether the platform does it, and check again in a year for the ones you already added. I have removed four dependencies from projects this year purely by re-checking, and each removal is permanent — one fewer package to audit, to update, to have a breaking change in, and to eventually migrate away from when it is abandoned. A dependency is a subscription, and the platform is the part that never sends an invoice. What I still reach for a package for Date arithmetic across timezones — Temporal is not universally available yet and the edge cases are genuinely hard. Rich text, because a document model is a real problem. Charting, where I use D3 rather than a chart component library, because the customisation always arrives eventually. Everything else, I check the platform first now. The list of things that genuinely need a dependency has got noticeably shorter, and every one removed is one fewer thing to keep current. Two things I check before adopting anything First: does it work in the browsers my users actually have? Not the ones in the compatibility table — the ones in this product's own analytics. For products that ship as an Android WebView, the answer is bounded by the system WebView on devices several years old, which lags the desktop browsers by a noticeable margin. Second: what happens when it is missing? A formatting API degrading to a slightly less pretty date is fine. A storage API missing and silently discarding a write is not, and the difference is whether the failure is visible. Anything whose absence fails silently gets an explicit check rather than optimistic use, because the alternative is a bug that only exists on hardware I do not own. Neither question is exciting, and between them they have stopped more production problems than any feature in this post has solved. ## Micro-animation: the 100ms rule, and everything else is… URL: https://aoneahsan.com/blog/micro-animations-framer-motion-tips Motion that earns its place versus motion that is decoration. Why acknowledgement beats a toast, why the browser gives you two free properties, and the… Most writing about interface animation is about how to make things move. The harder question is which things should, and the answer is narrower than the tooling suggests. My working rule: motion earns its place if it explains something. Where something came from, that it is loading, that a press registered, that this element is now that element. Everything else is decoration, and decoration is the first thing to feel dated. The rule that matters more than all the rest Every action is acknowledged at the control, within about a tenth of a second. Not somewhere else on the page. At the control the person just pressed, in the moment they pressed it. A hundred milliseconds is roughly where an interface stops feeling like it responded and starts feeling like it is thinking. The failure I see most: a copy button that copies successfully and shows nothing. It is indistinguishable from a copy button that failed, so the person presses it again, and still does not know. The fix is not a toast in the corner — it is the icon becoming a tick, right there, for two seconds. A toast is the fallback, not the default. It answers a question in a place nobody was looking. And the visual change is not enough on its own. A swapped icon is invisible to a screen reader, so the state change is announced separately in a live region. Those are two different mechanisms for two different people, and shipping one is shipping half. Two properties are cheap, the rest are not transform and opacity can be handled by the compositor without recalculating layout or repainting. Everything else — width, height, top, left, margin, padding — forces layout on every frame. /* janky: layout on every frame */ .card:hover { margin-top: -4px; } /* smooth: compositor only */ .card { transition: transform 180ms ease-out; } .card:hover { transform: translateY(-4px); } On a desktop with a fast machine you will not see the difference. Inside a WebView on a mid-range Android phone — which is where a large share of my users actually are — you absolutely will, and it reads as the app being slow rather than the animation being wrong. Durations, and why long ones are the giveaway What I use: 100–150ms for a hover or press, 180–250ms for something entering, up to 400ms for a full-page or route transition. Entrance animations cap at 600ms including any stagger. Anything longer is an animation being admired rather than used. The test is the tenth time: a 600ms flourish is delightful once and is a tax every time after, and your users are on the four hundredth time, not the first. Easing follows physics rather than taste. Things entering decelerate — ease-out . Things leaving accelerate — ease-in . Linear reads as mechanical for anything spatial, and is correct for a progress indicator, which is not spatial. reduced-motion is not a preference to respect politely For some people, parallax and large motion cause actual nausea and vertigo. This is a medical setting, not a taste setting. @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } } The blunt version above is a floor rather than a finished answer. The better version keeps the acknowledgement — a colour change, an instant state swap — and removes the movement, because the person still needs to know their press registered. Removing all feedback is a different bug wearing an accessibility badge. Pointer-dependent life is marketing-only Hover lifts, cursor followers, magnetic buttons. All of these are lovely on a laptop and completely absent on a phone, which is most of your traffic. That is fine on a landing page, where the goal is impression. It is not fine as the only feedback in an application, because a touch user then gets an interface where nothing responds. Every hover effect in an app has a :active or focus counterpart, or it does not exist for half the people using it. The related trap I have seen ship: hiding the native cursor to show a custom one, where showing and hiding are two separate pieces of code. Get one wrong and the page has no pointer at all — which is unusable, and only on someone else's machine. Skeletons, spinners, and the layout jump Loading states are where motion does the most work and gets the least thought. A centred spinner tells you something is happening and nothing else. When the content lands it appears at a different size in a different place, and the page jumps. A skeleton shaped like the thing it is replacing — same grid, same card, same three text rows — does two jobs: it says what is coming, and it reserves the space so nothing moves when the data arrives. The rule I follow is that the skeleton has the same layout as the content, not merely a similar spirit. A skeleton with three rows standing in for content with five is a layout jump with extra steps. Above roughly a second, a skeleton stops being enough and the interface should say what it is waiting for. Below about two hundred milliseconds, showing anything at all is worse than showing nothing — a flash of skeleton that appears and vanishes reads as a glitch, and the honest fix is a short delay before the loading state appears at all, so a fast response never triggers it. Where I use a library, and where I do not Almost all of the above is CSS transitions and a handful of keyframes. I reach for an animation library in two situations: shared-element transitions, where something has to appear to be the same object in a new position, and gesture-driven motion that has to follow a finger and settle physically when released. Both are genuinely hard and both are worth a dependency. A hover lift is not. Adding a runtime dependency, and the maintenance it implies for as long as the project lives, to replace four lines of CSS is a trade that only looks good on the day you make it. What I actually ship A short entrance for content as it arrives, with a small stagger so a list does not appear as one block. A hover lift on cards, with a focus equivalent. A visible :focus-visible ring on everything interactive, because removing outlines because they are ugly is how a keyboard user loses track of where they are. Skeletons shaped like the thing they replace, so nothing jumps when the data lands. Almost all of that is CSS transitions. I reach for an animation library when there is shared-element movement or gesture-driven motion, and not before — a dependency for a hover lift is a dependency you maintain forever for something four lines of CSS already did. ## From junior to senior: what actually changed URL: https://aoneahsan.com/blog/junior-to-senior-developer-journey Eight years from a frontend job in Lahore to running twenty-three products alone. The three shifts that mattered, and the one I was slowest to make. I started as a frontend developer in January 2018, building pixel-perfect interfaces from designs. Since March 2026 I have worked independently on my own products — twenty-three of them live across the web, Google Play and the extension stores, and twenty-five packages published to npm. In between: WordPress themes, Laravel and Angular for an education platform, then several years leading delivery on full-stack products and finally as a senior engineer on a SaaS team, owning features across web and mobile. What changed over those eight years was not the amount I knew. Three things changed, and the last one took the longest. 1. From "does it work" to "how does it fail" Early on, done meant the feature worked when I used it. The shift is to asking what happens when it does not — when the network is slow, the response is empty, the user is on a phone, the token expired mid-action, two people edit the same thing. The concrete version, which took me embarrassingly long: a green build is not evidence. I have personally shipped a typecheck that exited zero on genuinely broken code, because the configuration compiled nothing at all. It reported safety it had never checked, for weeks, and every one of us reading it saw a passing gate. Now I plant a defect and watch a gate fail before I trust it. A gate nobody has seen go red is a gate nobody has verified. That habit alone has caught more real problems than any amount of care while writing. 2. From writing code to removing decisions A junior asks how to build the thing. A senior asks whether the thing needs building, and what it will cost in two years when someone else has to change it. Most of the value I add now is in reducing what has to be true for the system to keep working. Fewer moving parts, fewer places a rule can be forgotten, fewer things a new person must know before their first change is safe. The pattern I trust most: replace a convention with a constraint. Every discipline in my codebases that survived contact with reality is one that got a mechanism. "Do not fetch unbounded lists" became a helper whose limit is a required argument, so the failure mode is a compile error rather than an outage. "No console.log " became a lint rule at error level with one exempted file. "Every user-visible string goes through the translation layer" became an AST lint rule — because a hardcoded string passes typecheck, lint, build and every review that reads for correctness. Each of those started as a note in a document that everybody agreed with and nobody followed. Not because they were unreasonable, but because a rule you have to remember is a rule that decays, and the person who forgets is usually the person who wrote it. 3. From avoiding blame to owning outcomes This was the slow one. The junior instinct when something breaks is to establish that it was not your fault. It is a completely understandable instinct and it is corrosive, because it optimises for looking careful rather than for the system working. What changed it for me was working alone, where there is nobody else for it to have been. Then the only useful question is what would have caught it, and that question produces gates, checks and constraints instead of explanations. Every mechanism I described above exists because something broke and the honest answer to "what would have caught this?" was "nothing". The most useful sentence I learned to say: "I do not know, let me find out." It costs nothing, and pretending is the expensive alternative. What did not matter as much as I expected Frameworks. I have shipped production work in Angular, React, React Native, Laravel, Flutter and vanilla JavaScript, and the transfer between them is close to total once you understand the problems they are each solving. Titles. I was a team lead before I was, by any real measure, a senior engineer. The title arrived years before the judgement did. Being the smartest person in the discussion. Almost never the constraint. The constraint is usually that nobody has written down what "done" means. What working alone teaches that a team cannot Since March 2026 I have worked entirely on my own products, and it has been the most educational period of the eight years — not because it is better, but because it removes every buffer. On a team, a bad architectural decision is absorbed. Somebody notices in review, or the person who inherits it works around it, or it becomes a known quirk everybody routes around. Alone, every decision comes back to you personally, at the exact moment it becomes expensive, and you cannot argue with it because you are the only one who could have made it. That is a brutally efficient feedback loop. I have changed more of my habits in a year of this than in the four years before it, and almost all of the mechanisms I described above date from it. What it does not teach is anything about working with people, which is most of the job for most engineers. Code review as a craft, disagreeing productively, explaining a trade-off to somebody who will make the decision, knowing when your objection is important and when you should let it go — none of that improves when there is nobody there. I got those years from leading delivery on a team, and I would not have got them any other way. The advice I would actually give Read code you did not write, deliberately, on purpose, as an activity. Most engineers only read code when they have to change it, which means they only ever see it under time pressure. An hour spent reading a well-built codebase with no obligation to modify it is worth several tutorials. Keep a list of the bugs you caused. Not to feel bad about — to notice the pattern. Mine was overwhelmingly about trusting that something worked because it looked like it worked, which is why almost every discipline I now hold is about producing evidence rather than about being careful. And write for other people, including future you. Half of what I know is in comments explaining why something is the way it is, and those comments have saved me more time than any tool I have adopted. What I would tell myself in 2018 Ship things and maintain them. Maintenance is where you learn — a codebase you have carried for two years teaches you things no tutorial can, because it shows you the consequences of your own decisions at a distance where you cannot argue with them. And write down the reasoning, not just the result. Every "why is this like this?" I cannot answer is a decision I have to make again, and I will probably make it worse the second time, because the first version at least had the context. ## A feature-request board on Firestore, and the rules that… URL: https://aoneahsan.com/blog/feature-request-system-firestore Vote counts that cannot be forged, a status workflow that means something, and the security-rule subtlety that makes a list query fail while the… A feature-request board looks like a weekend feature. Requests, votes, statuses, comments. The interesting parts are all in the rules, because everything a user touches here is something they have an incentive to lie about. The count cannot live where the client can write it The naive schema puts voteCount on the request document and increments it when someone votes. Anybody can write any number, and a rule that allows the increment cannot distinguish "plus one" from "plus four hundred" without reading the previous value and doing arithmetic in the rule language. The shape that works: a vote is its own document, with a deterministic id built from the request and the user — {requestId}_{userId} . Now the rule is trivial, because the id is the constraint: match /votes/{voteId} { allow create: if request.auth != null && voteId == request.resource.data.requestId + '_' + request.auth.uid && request.resource.data.userId == request.auth.uid; allow delete: if request.auth != null && resource.data.userId == request.auth.uid; } One person cannot vote twice, because the second write is the same document. They cannot vote as somebody else, because the id would not match their uid. And the count is derived from the votes rather than asserted next to them, which means it cannot disagree with them. Where a running total is needed for sorting, it is maintained by the database on write, never by the client — and it is outside the client's write permission entirely, so the only path to it is the one that computes it. The rule subtlety that costs people days This one is worth the whole post. A rule like this reads perfectly: allow read: if resource.data.authorUid == request.auth.uid; It works for a single-document read. It fails for every list query, and the error message is not obviously about this. The reason is that on a list, rules are not evaluated per returned document. The engine must be able to prove from the query's own filters that nothing it could return would violate the rule. A query with no matching where clause cannot be proven safe, so it is refused outright. So the client query must carry the filter the rule depends on: query(collection(db, 'requests'), where('authorUid', '==', uid), // not for filtering — for provability orderBy('createdAt', 'desc'), limit(20)); I have seen this exact class of defect produce twenty production failures on a single project, with every static check green — because the code is correct, the rule is correct, and only the pair is wrong. Status is a workflow, not a label Open, under review, planned, in progress, shipped, declined. The two that matter are the two people skip. Declined needs a reason , and the reason is public. A request that quietly disappears teaches everyone that the board is decoration. A request declined with "this conflicts with how permissions work, here is why" teaches them it is real, and it is read by more people than the ones who voted. Shipped needs a link. To the release, the changelog entry, the version. Closing the loop is the entire reason anyone votes a second time. Status is admin-only, enforced in the rules rather than by hiding the control. That is the difference between authorization and decoration, and it is one line either way. Moderation is not optional and it is not a phase two Anything with public text needs a report path, an admin queue, and the ability to hide content immediately. Building it later means building it during an incident. The minimum: a report writes a document, an admin screen lists them, hiding is a flag the read rules respect, and the original is retained rather than deleted so a mistaken hide is reversible. Reads, because this is where a board gets expensive A board is read constantly and written rarely, which is exactly the profile that exhausts a free tier if you are careless. Every list is paginated at twenty, sorting and filtering happen in the query rather than in the browser, and a user's own votes are fetched once for the visible page rather than per card. That last one is the classic: a card that checks "have I voted for this?" with its own read turns one screen into twenty-one reads, and it looks completely reasonable in the component that does it. Ranking, and why "most votes" is the wrong default Sorting purely by vote count sounds neutral and is not. It permanently favours whatever was posted first, because an old request has had months to accumulate votes and a good new one starts at zero and is on page four where nobody will see it to vote for it. Any ranking that decays with age fixes it — votes weighted against how long the request has been open, so something new and well received can reach the top within days. The exact formula matters much less than having one at all; the failure mode being avoided is a board whose top ten has not changed in a year, which reads as abandoned even while people are still voting. I also keep the shipped items visible rather than filtering them out. A board where the top of the list is things that got built is the single most effective argument that voting is worth the effort. Reading it honestly The thing to remember about vote counts is that they measure the people who found the board, which is a small and unrepresentative slice of the people using the product. A request with four hundred votes and one with six may be equally important; the second may simply be from a group who never look at a feature board. So I treat it as one input among several rather than as a roadmap. Its real value is not the ranking at all — it is that the requests arrive written in the user's own words, describing the problem rather than the solution they imagined. Half the time the useful information is in a request that got two votes and described something I had misunderstood completely. The thing I would build differently I would put a per-user rate limit on request creation from the beginning. Voting is naturally limited by the one-document-per-user rule; creating requests is not, and the first time somebody discovers that, you are moderating rather than building. One last practical note. Give every request a stable public URL from the start. People link to them — in support threads, in emails, in other people's issue trackers — and a board where a request's address changes when it is reordered or filtered is a board whose links all rot within a month. ## Reflectify 3-3-1: a journaling app that deliberately has… URL: https://aoneahsan.com/blog/reflectify-ai-journaling-project Three gratitudes, three wins, one focus. Why the entries never leave the phone, why there is no model reading them, and what a constraint does to a product… Reflectify 3-3-1 is an Android journaling app built around one structure: three things you are grateful for, three wins, one focus for tomorrow. That is the whole format, and the constraint is the product. It is also the one app in my portfolio where I decided against adding AI, and I want to explain that properly rather than dress it up as a philosophy. The obvious feature, and why it is not there The obvious feature writes itself: send the entries to a model, get back mood trends, themes over time, a summary of your year. Every journaling app that raised money has it. It requires shipping somebody's private journal to a third party. Not "anonymised", not "we do not train on it" — actually sending it, over the network, to a machine you do not control, in exchange for a summary they could mostly have written themselves. The privacy policy for this app, hosted on my own site because the app has no website of its own, says the entries you write are used only to provide the feature you asked for and stay on your device. That sentence is either true or it is not. Adding sentiment analysis would have made it not true, and I would have had to rewrite the honest version into the vague version that every app in this category ships. The feature was not rejected because AI is bad. It was rejected because I could not add it and keep the sentence in the privacy policy true. Where AI would genuinely help, and where it would not Being fair to the idea: there is a real version. On-device inference, models small enough to run locally, entries never leaving the phone. That is coming and I would use it. But it is worth asking what the summary is actually for. The value in a gratitude practice is the act of writing it, not the analysis afterwards. A weekly digest telling you that you mentioned your family a lot is a feature that markets well and changes nothing about whether you journal tomorrow. What the app does instead is much less impressive and more useful: it shows you what you wrote a year ago today. No model required, and the reaction it produces is the one the summary was trying to manufacture. The 3-3-1 constraint is the whole design Seven fields, fixed. Not a blank page. A blank journal is the most intimidating interface in software, and it is why most journaling apps are downloaded, opened twice and abandoned. Three specific prompts and a limit is a five-minute task rather than an open-ended one, and the difference between a five-minute task and an open-ended one is whether it happens on a bad day. The design consequence: no rich text, no attachments, no folders, no tags. Every one of those was considered and every one makes the blank page bigger. A product whose value is a constraint has to defend the constraint against its own feature requests, including the reasonable ones. What being local actually costs Honest accounting, because "your data stays on your device" has a price and most apps that say it do not mention theirs: No sync. Two devices means two journals. That is a real limitation and the most common request. A lost phone is a lost journal unless the user exported. So export is prominent rather than buried in settings, and it produces a plain readable file rather than a proprietary blob. No analytics on content. I have no idea what people write, which means product decisions come from interface behaviour and from asking, not from data. The streak problem, and why I mostly avoided it Every habit app has a streak, and streaks work — right up to the day somebody misses one. Then the counter resets to zero, and a person who journalled ninety times out of ninety-two is shown a number that says they have done nothing. That is a design that punishes the exact user it should be encouraging, and it is the most common reason people abandon this category of app permanently. Missing a day is a reason to open it tomorrow; being told you are back to zero is a reason not to. What the app shows instead is the count of entries and the year-ago entry. Both go up and to the right, neither can be lost, and neither creates a reason to fake an entry at 11:58pm to protect a number — which is the other thing streaks reliably produce, and which quietly corrupts the data the app exists to collect. What "no account" removes, and what it costs There is no sign-up. Nothing to create, no email to verify, no password to forget, and nothing to leak, because there is no server holding anything to leak. The onboarding consequence is significant: the first screen is the first entry. Most journaling apps spend three screens on account creation and a tour before a person writes anything, and the drop-off happens in those three screens rather than in the writing. The cost is the one I named earlier and will not soften: no sync, no recovery, and a lost phone is a lost journal unless the person exported. That is a real trade, and the mitigation is making export prominent and its output plain and readable — a file somebody can open in any text editor in ten years, rather than a proprietary blob that needs this app to mean anything. A journal that outlives the app that recorded it is the actual goal. Very little else about the product matters more than that. The lesson I took to other products Decide what you will not collect before you build, and write it down where a user can read it. It is much easier to decide you do not want a piece of data than to remove it after a feature depends on it — and once it is in a published privacy policy, the decision defends itself against every future feature that would quietly contradict it. That is the actual mechanism here. Not discipline. A sentence I would have had to go back and edit, which turned out to be a stronger constraint than any intention. ## PregnancyPal: building health software when the data is… URL: https://aoneahsan.com/blog/project-pregnancypal-health-tech Week-by-week tracking, vitals, fertility and a community — on a free tier, with no cloud functions. What health data changes about every architectural… PregnancyPal is a maternal wellness companion: week-by-week pregnancy tracking, nutrition, prenatal exercise, daily vitals and symptom and mood logging, period and fertility tracking, appointments, a community and a blog. It ships as a web app, a Capacitor Android app and a browser extension. Technically it is the same stack I use everywhere. What is different is that every decision has a second question attached to it, and the second question is usually the one that matters. Health data changes what "good enough" means In most products, a bug that shows one user's data to another is embarrassing. Here it is a fertility log. The severity ceiling is completely different, and it changes three things: The rules are the product. Not a layer over it. Every collection is owner-scoped, and every list query carries the filter its rule depends on so the engine can prove the query is safe before it runs. Verification is adversarial. Every screen gets exercised by a second account that should not be able to see it. A feature verified only as its owner is a feature verified only on the branch that always passes. Deleting means deleting. A user who removes an entry expects it gone, not flagged. Client-side processing turned out to be the privacy feature The architecture is strictly zero-cost: client logic, the Firebase free tier, and my own platform for uploads. No cloud functions, no cloud storage. That constraint was financial. Its effect was privacy. Cycle predictions, fertile-window estimates and the health report are all computed in the browser from data the user already has, which means that computation never leaves the device and there is no server-side log of it to secure, subpoena or leak. I did not design it that way for privacy reasons and I am not going to pretend I did. But it is a real property of the result, and it is the strongest privacy argument the product has: the safest place to process someone's fertility data is on their own phone. The PDF export is the feature people actually use The most-valued feature is the one that lets someone hand a doctor a printed summary. That was a late addition and it reframed the product for me — the app is not the destination, the appointment is. Generated in the browser, so the report is assembled from data that never goes anywhere. And it taught me something about health UX: a doctor has four minutes. A report that is comprehensive is a report that gets skimmed. Ruthless summarising, with the raw log behind it, beats completeness. Week-by-week content is a data problem pretending to be a content problem Forty weeks of guidance sounds like a content task. It is a modelling task, because pregnancy weeks are computed from a due date that can be revised, and revising it must move everything consistently — content, appointments, milestones — without touching a single logged vital, which is dated in real time rather than in pregnancy time. Two clocks in one product. Getting that wrong is how a user's logged symptoms end up attached to the wrong week after a due-date change, and it is very hard to notice, because every individual value still looks plausible. Prerendering, because health searches happen before an install Somebody searching "what happens in week 19" is not in the app. They are in a search result or in an AI answer, and neither of those runs JavaScript reliably. So the build emits real static HTML per route, with the content in it, and the app hydrates over it for people who arrive with a browser. This is the single highest-leverage thing I have done for discovery on any of these products, and it costs a post-build step. The theme customiser is not decoration here A full appearance customiser — dark mode included — for signed-out visitors as well as signed-in ones, with the choice applied before first paint so there is no flash of the wrong theme. In a health app used at 3am by somebody who is not sleeping, dark mode is not a preference, it is the difference between using the app in bed and not. And a flash of white before the dark theme lands is precisely the moment it matters most. Offline is a requirement here, not a feature People log symptoms in waiting rooms, in hospital corridors and at three in the morning, which are three of the places phone signal is worst. An app that loses an entry because the network was down is not a minor inconvenience in this category — it is the moment somebody stops trusting it with anything. So writes are optimistic and queued. The entry appears immediately, is persisted locally, and syncs when there is a connection. The interface says which state it is in rather than pretending, because a silent queue is its own kind of lie: a user who cannot tell whether something saved will save it twice. The hard part is not the queue, it is conflict. Two devices, both offline, both logging. My resolution is deliberately unclever: entries are additive and timestamped, so both survive and neither overwrites the other. Duplicates are a much better failure than a lost entry, and they are visible enough that a person can remove one. Notifications, and why most of them are wrong The obvious feature is a daily reminder to log. The version that gets turned off inside a week is the one that fires at nine every morning regardless of whether you already logged at seven. What works is reminding only when there is something outstanding, at a time the person chose, and stopping entirely if it has been ignored several times running. That last rule is the one nobody builds, and it is the difference between a notification the user tolerates and one that costs you the permission permanently — because a person who disables notifications for your app disables them for the appointment reminders too. Android also wants the notification permission requested behind a deliberate opt-in with an explanation, and every permission in the merged manifest justified in the data-safety form. In a health category, a vague declaration is not a slow review. It is a rejection. What I would tell someone building in this space Write down what you will not collect, before you build. It is much easier to decide that you do not want a piece of data than to remove it once a feature depends on it. And be exact in the store listing and the privacy policy — every permission in the merged Android manifest, including ones a plugin injected without asking, needs a justification a reviewer can read. In this category, a vague data-safety declaration is not a slow review. It is a rejection. ## FilesHub: building the storage backend instead of paying… URL: https://aoneahsan.com/blog/fileshub-storage-utility-case-study A self-hosted Laravel platform that replaced Firebase Storage and every transactional-email vendor across an entire portfolio. What it took, and the… Firebase Storage needs the paid plan. So does anything that sends email at volume. Multiply either by a portfolio of products and the zero-cost constraint I run everything under stops being achievable — unless one platform serves all of them. FilesHub is that platform: a Laravel application with a Nova admin panel, exposing object storage, transactional email, image processing, PDF, QR and barcode generation, URL shortening, and a catalogue of developer utilities across a versioned API. One key, with granular per-key permissions, unlocks the parts a given project is allowed to use. It is the canonical upload backend behind everything I ship. This is what building it actually involved. Multi-tenancy is the first decision and it constrains everything after Every object belongs to a project; every project belongs to an account; every key belongs to a project and carries its own permissions. That sounds obvious written down, and it is the decision that is expensive to change later, because it appears in every route, every policy and every query. The rule I settled on: tenancy is resolved once, at authentication, and everything downstream operates inside it. Not "add a where project_id to each query" — the query cannot see outside the tenant at all. A filter you have to remember is a filter that gets forgotten in the one endpoint nobody reviewed. Per-key permissions, and the origin restriction I got wrong A key that can do everything is a key you cannot ship in a browser bundle. So keys carry scopes and, for browser use, an origin allowlist: the key only works when the request comes from a registered origin. Here is the part I want on record, because I believed the wrong thing for a while. I checked whether a key was restricted by reading it back from the management API, which reported its allowed origins as null — no restriction. That looked like a misconfiguration, so I went to fix it. Then I probed the key directly instead of reading its record: a request with no Origin header at all — 403 a request with a junk origin — 403 a request with its registered origin — 202 The key was restricted, correctly, the whole time. The listing was reporting a field that did not reflect enforcement. Probe the behaviour; do not read the configuration. A record describing a security control is not the control, and I nearly "fixed" a working restriction into a broken one. And a second thing worth being blunt about: origin restriction defends against a hostile page , never against a person. One curl with the registered origin in a header goes straight through, because a header is not a proof of anything. So a restricted key is safe to ship in a bundle in the sense that another website cannot use it — and is not a secret. Anything that must actually stay secret is server-side, always. Why the utilities ended up in the same platform This was not the plan. It happened because every product kept needing the same small things — a QR code, a PDF, a thumbnail, a hash, a slug — and each one was either a dependency in every project or a service with a bill. Putting them behind the same authenticated API meant one implementation, one place to fix a bug, and no new dependency in twenty codebases. The cost is that the platform is now large enough to need its own discipline: versioned routes, so a change cannot break a consumer that has not been touched in a year. A 200 is not a success, and I learned that from my own API The batch endpoints return HTTP 200 with a per-item status array. A caller checking only the HTTP code sees success while half the items failed — and the failure is completely silent, because the response body was never read past the status line. That is not a quirk of my platform; it is the shape of every batch API. But finding it in my own was useful, because it turned into a rule I now apply as a consumer: for any endpoint that accepts more than one thing, the transport status tells you the request arrived, and nothing else. Versioning the API was the decision that aged best Every route is versioned, and consuming projects pin the version they were built against. That felt like ceremony when there were two consumers. With an entire portfolio depending on it, it is the only reason I can change anything. The rule I hold to: within a version, changes are additive only. A new optional field is fine. Removing a field, renaming one, tightening a validation rule or changing an error shape is not — and that last one catches people, because an error format feels like an implementation detail right up until a consumer is parsing it. The corollary is that a consumer must never be broken by a deploy it did not ask for. A project I have not touched in a year has to keep working, because the alternative is that every platform change becomes a coordinated release across twenty repositories, which is precisely the coupling a shared platform was supposed to remove. Credentials belong in one place, and it should not be a chat message The operational lesson, less glamorous than the architecture and more useful day to day: every project's keys live in one vault with a known shape, and the tooling reads them from there. Not pasted into a message, not remembered, not sitting in a file called keys.txt on one machine. What that bought me is that setting up a project on a new machine is a fetch rather than an archaeology exercise, and rotating a key is one operation rather than a hunt through twenty repositories hoping the search terms match. What it demands is discipline about what counts as a secret. An origin-restricted browser key is not one, and treating it as though it were leads to elaborate handling of a value that is in the bundle anyway. A key that authorises server-side operations absolutely is one, and it never appears in a client-prefixed environment variable, because those are compiled into the JavaScript that everyone downloads. Was it worth building Yes, and the reason is not the money. It is that storage and email became a solved problem across every product simultaneously. A new project wires one key and has uploads, email, image processing and PDF generation on the first day. The honest cost: it is a server I maintain, which is the one piece of infrastructure the zero-cost constraint does not eliminate — it consolidates it. One platform serving an entire portfolio is a different proposition from a per-product bill, but it is not nothing, and pretending otherwise would be the kind of claim I try not to make. ## Trizlink: one React codebase as a web app and an Android app URL: https://aoneahsan.com/blog/case-study-trizlink Two surfaces from one repository, with a third planned. What actually got shared, what could not be, and the free-tier limits that shaped the data model. Trizlink started as a URL shortener and stopped being one fairly quickly. It now does branded short links, a link-in-bio page, click analytics, custom domains, team workspaces, and a suite of fifty-plus utilities — QR and barcode generators, colour tools, PDF tools, text and SEO and code tools. The part worth writing about is not the feature list. It is that the same React 19 codebase ships as a web app and a Capacitor Android app, with a Chrome and Firefox extension on WXT planned as the third surface — and how much of that is genuinely shared. What is shared, honestly Almost all of it, and that surprised me. The data layer, the domain logic, the validation schemas, the design system and the entire authenticated application are one implementation. The differences are at the edges: The shell. The web app has a header and a footer; the Android app has native back-button handling and safe-area insets; the planned extension gets a popup with a fixed size and no routing to speak of. Storage. Three different persistence surfaces behind one interface, so nothing above it knows which one it is on. Auth. This is the one that genuinely cannot be shared, and it is the subject of half of this post. The extension will be a different product wearing the same clothes Manifest V3 forbids remotely-hosted code. Not discourages — forbids, and enforcement is a rejected submission rather than a console warning. That single rule removes: the Firebase Auth web SDK, Google Analytics, any CDN script, any analytics library that fetches its own payload, and eval in every form including the ones a bundler generates if you let it. So the extension will need a different authentication path — the browser's own identity API for a token — with ownership on the data side verified against a user-id field on the document rather than against the request's auth context. Everything else can be bundled at build time. The practical lesson: check this on day one. Discovering it at submission means unpicking your auth layer with a rejection already on record, and an extension's identity is permanent once it is published. The free tier shaped the data model, and that was good for it The whole product runs on Firebase's free tier plus my own FilesHub platform for uploads. Click analytics is the interesting constraint: every redirect is a write, and every analytics screen wants to aggregate them. The naive design is to store one document per click and count them when someone opens the dashboard. That works beautifully with two hundred clicks and exhausts a daily read quota with fifty thousand — which is a thing I have watched happen on a different product of mine, where one unbounded list query took production down until the quota reset. So the aggregates are maintained as the clicks arrive rather than computed on read, and the dashboard reads a handful of summary documents instead of a collection. It is what you would do anyway at scale; the free tier just makes "anyway" arrive on day one. Every list read is paginated, twenty by default and fifty at most, and the database does the counting. Not a guideline — a helper function whose limit is a required argument. Custom domains are where the real complexity lives Nothing about a short link is hard. Letting somebody point their own domain at your service is hard, and it is entirely operational rather than algorithmic: verification records, certificate issuance, the window where DNS has propagated for some resolvers and not others, and what the product shows during it. The thing I would tell anyone building this: the states are not "working" and "broken". They are unverified, verified but no certificate, certificate issued but DNS still propagating, and live — and a user staring at a domain that is in state three needs to be told that specifically, or they will delete it and start again. The link-in-bio page is a different product with different rules Worth separating, because it is the part that behaves least like the rest. Short links are a redirect: no rendering, no SEO, no design, just speed. A link-in-bio page is a public landing page somebody puts their name on, and it inherits every concern the redirect does not have. It has to load fast on a phone on mobile data, because that is where every single visit comes from — a social profile on a phone. It has to be themeable enough that people feel it is theirs, without becoming a page builder. And it has to be crawlable, because a person's link page is frequently the most-linked page they own. The tension is that the two halves want opposite architectures. The redirect wants to be as close to nothing as possible. The landing page wants prerendered HTML with real content in it. Building them as one thing meant the redirect carried weight it had no use for, and the fix was to stop pretending they were the same feature. What I would build differently, in one sentence each Extension constraints first , because every constraint it imposes is one the other surfaces can live with, and leaving them until last means the shared layer has already assumed otherwise. Analytics aggregated on write from day one , rather than computing on read and retrofitting it once the numbers got real. The utilities behind a lazy boundary , so somebody who came to shorten one link does not download fifty tools first. Custom-domain states named explicitly in the interface , because "not working yet" and "propagating" look identical to a user and only one of them is worth waiting through. What I would do differently I would design for the extension first. It is the most constrained surface, and every constraint it imposes — no remote code, a tiny bundle, no routing — is one the other two can live with comfortably. Leaving it until last means discovering those constraints after the shared layer has already assumed otherwise, which is where Trizlink stands today. And I would put the utilities behind a lazily loaded boundary from the start rather than retrofitting it. Fifty tools is a lot of code for someone who came to shorten one link. ## Refactoring a monolith you cannot stop shipping URL: https://aoneahsan.com/blog/clean-code-refactoring-monoliths The rewrite that loses a third of your features, the seams worth finding first, and why "delete the dead code" is the highest-value refactor nobody… Every large codebase eventually reaches the point where a small change takes a week. The instinct at that point is to rewrite. I have done both — incremental refactors and full rebuilds — and the thing I wish I had understood earlier is that they fail in completely different ways. A rewrite silently loses about a third of what you had Not the features you talk about. The ones nobody wrote down: the redirect that keeps an old link working, the edge case in the export, the setting three customers depend on, the page that gets forty visits a month from a search result you forgot ranks. Nobody notices for months, because the people who used those things do not file a ticket. They just stop using the product. So when I do rebuild, the rebuild is gated on two artifacts written before any new code exists. A frozen public contract — every URL the old system serves, enumerated from the running system rather than from memory. And a parity ledger — one row per route, marked keep, merge, or drop, with the rule that a drop on an indexed URL is not allowed. Merge is fine; a merged feature keeps its address. One row per route , not per feature. Features have gaps between them and routes vanish into those gaps. Find the seams before you find the abstractions For an incremental refactor, the useful question is not "what is the right architecture" but "where does this code already almost separate". Seams tend to be: Anywhere data crosses a boundary — an API response, a database row, a form submission. Anywhere a comment says "this is temporary". Anywhere two features share a file for no reason other than history. Start there, because those changes are reviewable. A refactor whose diff cannot be read is a rewrite wearing a smaller hat, and it will be approved on trust rather than on inspection. Delete first The highest-value pass, and the one nobody schedules: remove everything that is not used. Unused exports, dead branches, components nothing renders, feature flags for features that shipped two years ago, commented-out code that someone was going to come back to. It is high-value for a specific reason — every one of those is something a person has to read and decide about before they can change the thing next to it. Deleting a hundred lines that do nothing makes the surrounding hundred easier to reason about, at zero behavioural risk. My rule is: unused code is deleted, not renamed with an underscore and not commented out. Version control is the archive. A file full of commented-out code is a file where nobody can tell what runs. The one measure of a refactor I trust: can somebody who did not write it make a small change safely? Not "is it elegant" — elegance is not observable from outside. Extract behind the boundary you already have Once the dead weight is gone, the useful move is usually to give a tangle one door. Not to split it into six clean modules — to make everything outside it call one function instead of reaching into three. The inside stays ugly for now, and that is fine. What changes is that the ugliness stops spreading, because nothing new can couple to internals it can no longer see. That is the whole benefit, and it is available immediately. Constraints beat conventions The lesson I have learned most expensively: a rule people are supposed to remember is a rule that decays. A rule the tooling enforces does not. Every discipline in my codebases that actually survived is one that got a mechanism: "Do not fetch unbounded lists" became a helper whose limit is a required argument. "Do not use console.log " became a lint rule at error level with one exempted file. "Every string goes through the translation layer" became an AST lint rule — because a hardcoded string passes typecheck, lint, build and any review reading for correctness. "The rebuild must match the approved design" became a test that parses the design's own manifest. And each one gets watched failing before it is trusted. A gate nobody has seen go red is a gate nobody has verified — I have shipped a typecheck script that exited zero on genuinely broken code because the configuration compiled nothing at all. It reported safety it never checked, for weeks. Working in a codebase you did not write Most refactoring advice assumes you understand the system. The realistic case is that you do not, and the first job is to build a map cheaply without reading everything. What works for me, in order. Start with the routes or entry points, because they tell you what the system does from outside, which is the only description guaranteed to be current. Then follow one feature all the way through — route, handler, data access, storage — rather than reading each layer horizontally. One vertical slice teaches you the conventions; four horizontal layers teach you four vocabularies with no idea how they connect. Then read the version history rather than the code, for the file you are about to change. A file that changes every week is a hot spot and probably a seam; a file untouched for two years is either stable or abandoned, and the commit messages usually say which. This is the cheapest source of architectural information in any repository and almost nobody uses it. The trap I would flag specifically: a recursive search from the top of a project can silently skip a subdirectory that is its own repository with its own ignore rules, returning zero results for a string the code plainly contains. That is worse than an error, because a "no references anywhere" sweep passes for the wrong reason and you delete something that is still used. If a search returns nothing at all, verify the search before believing the answer. And write down what you learn as you go, in the repository rather than in a notebook. The map you build in your first week is the map the next person needs, and by month two you will not remember which parts were non-obvious. What I would say to someone about to start If you can improve it in place, do that. A rewrite is justified when the platform is genuinely dead, or when the product's shape has changed so much that the old model is actively wrong — not when the code is merely unpleasant. And if you do rewrite: enumerate the contract first, from the running system, and treat that list as law. Everything else is recoverable. A URL that quietly stopped existing is not. ## Firebase Auth and social sign-in: the parts that are… URL: https://aoneahsan.com/blog/firebase-auth-social-logins-security Social login is easy to add and easy to get subtly wrong. Custom claims that lie, rules that trust the client, and why an extension cannot use the Auth SDK… Adding Google sign-in takes about twenty minutes. Getting the authorization behind it right takes considerably longer, and the gap between the two is where most of the real bugs live. Authentication is not authorization, and the confusion is expensive Authentication answers "who is this". Authorization answers "what may they do". Social sign-in solves the first completely and the second not at all — and because the first feels like the hard part, the second frequently gets whatever the client can be persuaded to enforce. The concrete version: a signed-in user object with an isAdmin field on it, read from the profile document, checked in the UI. That is not a boundary. It is a rendering hint. The request still works if you send it yourself. Where the role actually lives The failure I keep meeting is a role the user can write. A profile document with an isAdmin field, and a rule permitting a user to update their own profile — which is reasonable, until you notice that "their own profile" includes the field that decides whether they are an administrator. Two ways out, and the choice matters: Custom claims on the token. Set server-side only, arriving inside the ID token, so rules can read them without a document lookup. The trap is staleness — a claim changed on the server does not appear in a client's token until it refreshes, so a revoked admin stays an admin for up to an hour unless you force it. A role on a row the user cannot write. The column is outside the user's update grant, and every policy reads it through a helper. Slower by a lookup, and immediate — which for revocation is the property you want. I use the second. I would rather pay a lookup than explain why someone I removed still had access after I removed them. A rule must be able to prove itself from the query This is the subtlety that produced twenty production defects across one project I worked on, with every static check green. A rule like resource.data.ownerUid == request.auth.uid reads fine and is correct for a single-document read. On a list query it does not evaluate per document — the engine must be able to prove from the query's own filters that every document it could return satisfies the rule. A list with no where('ownerUid', '==', uid) is refused outright, and the failure surfaces as a permission error on a screen whose code looks perfect. So: every list, count and aggregation carries the filter its rule depends on. Not for filtering — for provability. Row-level security has the mirror-image trap On the Postgres side the same idea has the opposite failure mode, and it is worse because it is silent. A policy filters . It does not refuse. So a read that should return nothing returns 200 and an empty array, and an update the user is not allowed to make returns 200 having changed zero rows. Which means a probe that reports success proves nothing on its own: Seed data first. A 200 over an empty table passes vacuously — zero rows satisfy any rule. Send Prefer: return=representation and read the row back. A bare 204 is not evidence. Test as a user who should be refused, not only as one who should succeed. Extensions cannot use the Auth SDK at all A browser extension is a separate case and it catches people out. Manifest V3 forbids remotely-hosted code, and the Firebase Auth web SDK loads code at runtime — so shipping it is a store rejection, not a warning. The route that works is the browser's own identity API to obtain a token, and then verifying ownership on the data side through a field on the document rather than through the request's auth context. Everything else in the extension is bundled: no CDN scripts, no analytics that fetches its own library, no eval . The small things that are actually the common ones Set the site URL and the redirect allowlist. A provider left at its factory default sends every completed sign-in to a dead address, and the fallback is silent rather than an error. I have found this configured wrong on a live project. Handle the cancel. A user closing the provider popup is a normal outcome, not an error. An error toast for a deliberate cancellation teaches people your app is broken. Decide account linking deliberately. Same email, two providers: either link them or refuse clearly. The default behaviour is rarely the one your product wants, and users who signed up with Google in March and press the other button in June will find whichever you chose. Never trust the email as an identifier. It is verified by the provider, it is not a primary key, and it can change. Sessions, refresh, and the moment a signed-out user is not signed out The part of authentication that gets the least attention is what happens after sign-in, and it produces the failures that are hardest to reproduce. Tokens are short-lived and refresh in the background. That is mostly invisible, until a request goes out with a token that expired between being read and being sent, and the user sees a permission error on an action that should have worked. Retrying once on an authentication failure, after forcing a refresh, removes an entire category of intermittent bug that otherwise gets filed as "happens sometimes". Sign-out is the one worth being careful about. Clearing the session is not enough on its own, because a client-side cache still holds everything the previous user was looking at. If the next person to use that browser signs in and the cache has not been cleared by key prefix, they are served the previous person's data from memory — not stale data, somebody else's account details rendered as their own. So every query keyed to a user has its prefix listed in one place, and sign-out removes exactly those prefixes. The list is the kind that rots, which is why adding a user-scoped query means adding its prefix in the same change; a query whose prefix is missing survives the sign-out silently, and nothing about the resulting screen looks wrong. The last one: decide what a signed-out visitor can see, deliberately. A large share of these products are useful before sign-in, and gating the whole application behind an auth check is both bad for discovery and dishonest to a crawler, which will index the sign-in page as though it were the content. The test that finds all of this Sign in as an ordinary user and try to do an administrator's job. Every layer that stops you is real; every layer that merely hides the button is decoration. It takes ten minutes and it is the only check in this entire post that cannot be faked by a green build. ## Accessible design systems: what React Aria gives you… URL: https://aoneahsan.com/blog/accessible-design-systems-radix-ui Moving from a styled component library to headless primitives plus Tailwind. What accessibility work is genuinely free, what is not, and the rich-text rule… There are two ways to get accessible components: adopt a library that ships both behaviour and appearance, or adopt one that ships only behaviour and write the appearance yourself. I have shipped products both ways, and I now default to the second — not because the first is bad, but because of what happens six months in. The problem with a styled library, stated fairly A library that ships its own look is the fastest possible start and it is genuinely accessible. Its focus management is better than what you would write, its dialog traps focus correctly, its menus implement the full keyboard interaction, and none of that is work you have to do. The cost arrives when the design is not the library's design. Every project I run has its own type scale, its own spacing rhythm, its own colour treatment. Overriding a library's styling means fighting specificity, and there is a point in every such project where a component gets reimplemented locally because bending it was harder than rewriting it. That local reimplementation has none of the accessibility work, and nobody notices, because it looks right. Headless primitives invert that. You get the behaviour — focus, keyboard, ARIA, screen-reader announcements — and you own every pixel from the start. There is nothing to fight, so there is no incentive to defect. What you genuinely get for free Worth being concrete about, because "accessible" is a word that gets used without content: Focus management in overlays. A dialog traps focus, restores it on close, closes on escape, and locks scroll behind it. All four are things a hand-rolled modal gets wrong, and a mouse user notices none of them while a keyboard user is trapped. Full keyboard interaction on composite widgets. A radio group is one tab stop with arrows moving inside it. A row of buttons looks identical and gives each option its own tab stop, no checked state, and no group semantics for a screen reader to announce. Correct relationships. Label, description and error are wired to the control with real ids, which is what makes an error announced instead of merely visible. The pattern I would highlight is the last one. An error message that turns a border red communicates nothing to somebody who cannot see the border, and a describedby that points at an element which does not exist announces nothing at all — which is indistinguishable from having no error. What is not free Everything visual, which is the deal you signed. The two that surprise people: Focus visibility is yours. Headless primitives give you the focus state ; making it visible is a styling decision, and the default of removing outlines because they are ugly is how a keyboard user loses the ability to tell where they are. Style :focus-visible deliberately, on everything interactive. Contrast is yours. No library can know that your muted text on your surface colour is 3.1:1. That is a palette decision, made once, and checked with a tool rather than an eye. The rule that costs me every time, and why I keep it Every multi-line text input in my products is a rich-text editor. Not the long ones — every one. Somebody writing about their own work gets to emphasise a word. That rule has a real accessibility cost, and I want to state it rather than wave it away: a contenteditable region is measurably harder for a screen reader than a native textarea , and Android IME and autocorrect misbehave with it in ways a textarea never does. Since these ship as Capacitor apps, that second one is not theoretical. So the rule carries its own mitigations, and a field without all of them is not finished: The editable element exposes role="textbox" and aria-multiline , bound to a real label. The toolbar is keyboard reachable, every button a real