[{"data":1,"prerenderedAt":270},["ShallowReactive",2],{"post-client-side-tools-that-earn-trust":3,"blog-posts":14},{"_path":4,"title":5,"description":6,"date":7,"tags":8,"readingTime":12,"body":13},"\u002Fblog\u002Fclient-side-tools-that-earn-trust\u002F","Client-Side Tools That Earn Trust","How to design browser tools that feel private by default—local processing, clear data boundaries, and UX that never asks people to guess where their files go.","2026-07-20",[9,10,11],"privacy","frontend","tools",5,"# Client-Side Tools That Earn Trust\n\nPeople open a “runs in your browser” tool hoping it will stay local. The job is to make that promise obvious, keep it true under pressure, and never leave someone wondering where their data went.\n\n## Start with a boundary people can see\n\nTrust is not a privacy policy paragraph. It is a boundary you can point at in the UI. Before you write a single fetch call, decide what leaves the machine and what never does.\n\nFor tools that process personal exports, screenshots, or pasted logs, the safest default is: files stay in memory, work happens with Web APIs, and nothing is uploaded unless the user explicitly chooses a share or save action. Say that in the first screen, not buried under “How it works.” A short line—“Your file never leaves this tab”—beats a long explanation people skim past.\n\nMake the boundary visible in the flow too. If the user picks a file, show the filename and a clear “nothing uploaded” state. If they paste text, show character count and keep the action labels honest: “Analyze locally,” not “Submit.” Ambiguous verbs train people to assume a server is involved even when it is not.\n\nWhen you do need a network call—for example storing an encrypted blob—spell out the exception. “Encrypted on your device, then stored until expiry” is a different contract than “fully local.” Mixing those modes without labeling them is how trust leaks.\n\n## Design the happy path around consent, not convenience\n\nConvenience features are where privacy tools quietly break their own rules. Auto-upload on drop, analytics that ship file names, “helpful” cloud sync toggled on by default—each one trades clarity for speed.\n\nPrefer explicit steps when the data is sensitive:\n\n1. Choose or paste input\n2. Confirm what will be processed\n3. Run the analysis\n4. Offer download or copy of results\n5. Clear state when they leave or ask\n\nThat sequence is slightly slower than a one-shot drop zone that immediately POSTs. It is also easier to audit and easier to explain. If you want drag-and-drop, keep processing local and reserve network activity for a separate, labeled control.\n\nConsent also means reversible mistakes. Let people clear the current file without refreshing. Let them wipe results. If you hold anything in `sessionStorage` or IndexedDB for refresh survival, document it and provide a one-click purge. Silent persistence feels clever until someone shares a laptop and the last export is still sitting there.\n\n## Build for failure without leaking data\n\nError handling is a privacy feature. Stack traces that include path fragments, toast messages that echo raw file contents, and console logs full of PII are common in early prototypes—and easy to leave in production.\n\nKeep failures boring:\n\n- Prefer typed error categories (“Could not parse export,” “Unsupported format”) over dumping the first 200 characters of the file.\n- Log structure and counts in development; strip payloads before any remote reporting.\n- If parsing fails mid-way, discard partial structures that still contain usernames or message bodies.\n\nPerformance matters for the same reason. Huge JSON exports can freeze the main thread and tempt you to “just send it to a worker service.” Web Workers and streaming parsers are usually enough. If a file is too large for the browser, say so and stop—do not quietly fall back to a server path unless that path was part of the product promise from the start.\n\nTesting should include adversarial cases: truncated ZIPs, HTML disguised as JSON, extremely nested objects, and files with unexpected encodings. Each of those should fail closed without uploading anything.\n\n## Make the architecture match the marketing\n\nMarketing copy and code paths diverge over time. A landing page that says “100% local” while a stray analytics SDK phones home is worse than a quieter product that tells the truth.\n\nAudit the shipping surface regularly:\n\n- Network tab on a cold run with a real sample file—expect zero requests for pure local tools\n- Third-party scripts: fonts, analytics, error trackers, ads\n- Service workers that might cache sensitive responses\n- Paste or share features that accidentally include raw input in a URL\n\nPrefer first-party hosting and minimal dependencies for tools that touch personal data. Every CDN script is another party in the room. If you need syntax highlighting or ZIP parsing, vendor the library and review what it does on error.\n\nRun the audit after dependency upgrades too. A minor version bump that “only” adds telemetry is how quiet regressions sneak in. Keep a checklist in the repo and treat it like a release gate for privacy-sensitive apps—same seriousness you would give a broken deploy.\n\nDocument the threat model in a short page linked from the tool. You do not need a legal novel. State what you collect (usually nothing), what the browser can still do (extensions, malware, shoulder surfing), and what expiry or encryption means if you store anything. Honest limits build more trust than absolute claims. If you change the model—say you add optional cloud sync—update the page in the same PR as the feature. Stale privacy copy is a trust bug.\n\n## Wrap-up\n\nClient-side tools earn trust when the UI, the verbs, and the network tab all tell the same story. Draw a visible boundary, require consent for anything that leaves the device, fail without leaking payloads, and keep architecture aligned with the promise on the page. If those four stay true, people will bring their messiest exports—and come back.",[15,17,27,36,43,51,58,66,73,80,87,95,102,109,116,124,130,138,147,155,161,168,174,182,190,197,203,209,216,222,228,234,241,248,255,263],{"_path":4,"title":5,"description":6,"date":7,"tags":16,"readingTime":12},[9,10,11],{"_path":18,"title":19,"description":20,"date":21,"tags":22,"readingTime":26},"\u002Fblog\u002Fusb-c-cables-that-actually-matter\u002F","USB-C Cables That Actually Matter","How to buy USB-C cables by wattage and data speed so you stop guessing which cord charges slowly or fails a file transfer.","2026-07-14",[23,24,25],"gadgets","hardware","productivity",4,{"_path":28,"title":29,"description":30,"date":31,"tags":32,"readingTime":12},"\u002Fblog\u002Fcolor-contrast-people-can-read\u002F","Color Contrast People Can Actually Read","Quick checks for text and UI colors that stay legible in light mode, dark mode, and on mediocre displays.","2026-07-07",[33,34,35],"accessibility","css","design",{"_path":37,"title":38,"description":39,"date":40,"tags":41,"readingTime":12},"\u002Fblog\u002Fportable-ssds-worth-carrying\u002F","Portable SSDs Worth Carrying","When a pocket SSD beats cloud sync for travel, backups, and large media—and how to pick one that stays fast and durable.","2026-06-26",[23,42,25],"storage",{"_path":44,"title":45,"description":46,"date":47,"tags":48,"readingTime":12},"\u002Fblog\u002Fwireless-earbuds-that-last-the-day\u002F","Wireless Earbuds That Last the Day","Simple charging and fit habits that stretch earbud battery life without babysitting the case every few hours.","2026-06-18",[23,49,50],"audio","habits",{"_path":52,"title":53,"description":54,"date":55,"tags":56,"readingTime":26},"\u002Fblog\u002Fwhen-a-second-monitor-helps\u002F","When a Second Monitor Actually Helps","A practical take on dual screens for coding, writing, and research—plus when a single good display is still the better desk.","2026-06-10",[23,25,57],"workspace",{"_path":59,"title":60,"description":61,"date":62,"tags":63,"readingTime":26},"\u002Fblog\u002Fshort-session-game-picks\u002F","Short-Session Games That Respect Your Evening","Genres and design patterns that fit a 30-minute window without the “one more hour” trap.","2026-06-04",[64,50,65],"gaming","indie",{"_path":67,"title":68,"description":69,"date":70,"tags":71,"readingTime":12},"\u002Fblog\u002Fphone-charging-habits-that-age-better\u002F","Phone Charging Habits That Age Better","Practical charging routines that keep a phone useful longer—without obsessing over the perfect battery percentage.","2026-05-23",[23,72,50],"mobile",{"_path":74,"title":75,"description":76,"date":77,"tags":78,"readingTime":12},"\u002Fblog\u002Ffinishing-side-projects-without-burning-out\u002F","Finishing Side Projects Without Burning Out","A practical playbook for shrinking scope, shipping thin vertical slices, and keeping personal projects fun long enough to actually launch.","2026-05-14",[25,79,50],"side-projects",{"_path":81,"title":82,"description":83,"date":84,"tags":85,"readingTime":12},"\u002Fblog\u002Faccessible-forms-people-finish\u002F","Accessible Forms That People Actually Finish","Practical accessibility checks for labels, errors, and focus so your forms work for more users.","2026-05-07",[33,86,10],"html",{"_path":88,"title":89,"description":90,"date":91,"tags":92,"readingTime":12},"\u002Fblog\u002Fvue-composition-api-patterns\u002F","Practical Vue Composition API Patterns","Reusable patterns for composables, shared state, and cleaner component logic in Vue 3.","2026-04-27",[93,94,10],"vue","javascript",{"_path":96,"title":97,"description":98,"date":99,"tags":100,"readingTime":12},"\u002Fblog\u002Fdesigning-empty-states-that-teach\u002F","Designing Empty States That Teach the Product","Empty screens are not placeholders—they are onboarding. How to write, layout, and sequence first-run UI so people know what to do next.","2026-04-19",[101,10,35],"ux",{"_path":103,"title":104,"description":105,"date":106,"tags":107,"readingTime":12},"\u002Fblog\u002Fwhy-indie-games-keep-winning\u002F","Why Indie Games Keep Winning Attention","How small teams punch above their weight with focused design, personality, and smart scope.","2026-04-13",[64,65,108],"industry",{"_path":110,"title":111,"description":112,"date":113,"tags":114,"readingTime":12},"\u002Fblog\u002Fkeeping-component-props-simple\u002F","Keeping Component Props Simple","When to split a component, when to use slots, and how to avoid prop objects that grow forever.","2026-04-03",[93,10,115],"best-practices",{"_path":117,"title":118,"description":119,"date":120,"tags":121,"readingTime":12},"\u002Fblog\u002Fcalm-frontend-performance\u002F","A Calm Approach to Frontend Performance","Performance work that sticks: measure what users feel, fix the critical path first, and avoid optimization theater on personal and portfolio sites.","2026-03-26",[122,10,123],"performance","web",{"_path":125,"title":126,"description":127,"date":128,"tags":129,"readingTime":12},"\u002Fblog\u002Fcss-layout-without-the-hacks\u002F","CSS Layout Without the Hacks","Modern Flexbox and Grid techniques that replace brittle floats, magic numbers, and overflow tricks.","2026-03-17",[34,10,35],{"_path":131,"title":132,"description":133,"date":134,"tags":135,"readingTime":12},"\u002Fblog\u002Fcloud-gaming-what-gets-in-the-way\u002F","Cloud Gaming: What Still Gets in the Way","Latency, input feel, and library lock-in—why streaming games is impressive and still uneven.","2026-03-10",[64,136,137],"cloud-gaming","tech",{"_path":139,"title":140,"description":141,"date":142,"tags":143,"readingTime":12},"\u002Fblog\u002Fshipping-static-sites-with-nuxt\u002F","Shipping Static Sites with Nuxt","How I build and deploy a Nuxt site to Cloudflare Pages with predictable routes and content.","2026-03-02",[144,145,146],"nuxt","ssg","devops",{"_path":148,"title":149,"description":150,"date":151,"tags":152,"readingTime":12},"\u002Fblog\u002Fdebugging-faster-in-the-browser\u002F","Debugging Faster in the Browser","A short toolkit of DevTools habits—breakpoints, network filters, and DOM inspection—that cut debug time.","2026-02-24",[94,153,154],"devtools","debugging",{"_path":156,"title":157,"description":158,"date":159,"tags":160,"readingTime":12},"\u002Fblog\u002Fsmall-javascript-habits-that-scale\u002F","Small JavaScript Habits That Scale","Everyday habits—naming, early returns, and clearer async flow—that keep front-end codebases maintainable.","2026-02-18",[94,115,10],{"_path":162,"title":163,"description":164,"date":165,"tags":166,"readingTime":26},"\u002Fblog\u002Fhealthier-gaming-routine\u002F","Building a Healthier Gaming Routine","Simple habits for sessions that stay fun—breaks, goals, and knowing when to stop.","2026-02-02",[64,50,167],"wellness",{"_path":169,"title":170,"description":171,"date":172,"tags":173,"readingTime":12},"\u002Fblog\u002Fresponsive-images-without-layout-shift\u002F","Responsive Images Without Layout Shift","Width, height, srcset, and lazy loading patterns that keep pages stable while images load.","2026-01-22",[86,122,10],{"_path":175,"title":176,"description":177,"date":178,"tags":179,"readingTime":12},"\u002Fblog\u002Fgit-habits-for-small-teams\u002F","Git Habits for Solo and Small Teams","Branch naming, commit messages, and PR hygiene that keep history useful without slowing you down.","2026-01-09",[180,181,25],"git","workflow",{"_path":183,"title":184,"description":185,"date":186,"tags":187,"readingTime":12},"\u002Fblog\u002Fwhat-makes-a-game-stream-worth-watching\u002F","What Makes a Game Stream Worth Watching","Commentary, pacing, and community—why some streams click even when the gameplay is messy.","2025-12-14",[64,188,189],"streaming","content",{"_path":191,"title":192,"description":193,"date":194,"tags":195,"readingTime":12},"\u002Fblog\u002Fwhen-to-reach-for-typescript-vue\u002F","When to Reach for TypeScript on a Vue App","A pragmatic take on where TypeScript pays off in Vue—and where plain JS is still fine.","2025-11-30",[196,93,10],"typescript",{"_path":198,"title":199,"description":200,"date":201,"tags":202,"readingTime":12},"\u002Fblog\u002Fenvironment-variables-for-static-sites\u002F","Environment Variables for Static Sites","How to use build-time env vars in Nuxt and other static generators without leaking secrets or breaking CI.","2025-10-05",[144,146,10],{"_path":204,"title":205,"description":206,"date":207,"tags":208,"readingTime":12},"\u002Fblog\u002Fpicking-a-side-project-stack\u002F","Picking a Side Project Stack Without Overthinking It","How to choose tools for a personal project when the goal is shipping, not building the perfect architecture.","2025-09-18",[25,10,115],{"_path":210,"title":211,"description":212,"date":213,"tags":214,"readingTime":26},"\u002Fblog\u002Fco-op-games-for-busy-friends\u002F","Co-op Games That Work When Everyone Is Busy","Async-friendly and drop-in co-op picks for friend groups that rarely share the same free evening.","2025-08-22",[64,215,50],"co-op",{"_path":217,"title":218,"description":219,"date":220,"tags":221,"readingTime":12},"\u002Fblog\u002Fsemantic-html-still-matters\u002F","Semantic HTML Still Matters","Landmarks, headings, and native elements that make pages easier to use, style, and maintain.","2025-07-06",[86,33,10],{"_path":223,"title":224,"description":225,"date":226,"tags":227,"readingTime":12},"\u002Fblog\u002Fgit-without-the-jargon\u002F","Learning Git Without the Jargon","A plain-language mental model for commits, branches, and merges when tutorials assume you already know Git.","2025-05-14",[180,181,25],{"_path":229,"title":230,"description":231,"date":232,"tags":233,"readingTime":12},"\u002Fblog\u002Fwhy-loading-states-matter\u002F","Why Loading States Matter More Than Animations","Skeletons, disabled buttons, and honest feedback beat flashy spinners when data takes time to arrive.","2025-03-28",[101,10,35],{"_path":235,"title":236,"description":237,"date":238,"tags":239,"readingTime":12},"\u002Fblog\u002Fnaming-things-so-future-you-survives\u002F","Naming Things So Future You Survives","Practical rules for naming variables, components, and files so a codebase stays readable months after the clever jokes fade.","2025-03-10",[94,115,240],"maintainability",{"_path":242,"title":243,"description":244,"date":245,"tags":246,"readingTime":12},"\u002Fblog\u002Fsave-systems-that-respect-time\u002F","Save Systems That Respect the Player’s Time","Checkpoints, autosaves, and manual slots are design choices. How thoughtful saving reduces rage-quits without deleting tension from the game.","2025-02-28",[64,247,101],"game-design",{"_path":249,"title":250,"description":251,"date":252,"tags":253,"readingTime":12},"\u002Fblog\u002Fmicrocopy-that-makes-interfaces-human\u002F","Microcopy That Makes Interfaces Feel Human","Button labels, error lines, and helper text are product design. How to write UI words that reduce hesitation without sounding like a chatbot.","2025-01-22",[101,254,35],"writing",{"_path":256,"title":257,"description":258,"date":259,"tags":260,"readingTime":12},"\u002Fblog\u002Freadmes-that-get-projects-running\u002F","READMEs That Get Projects Running","How to write a README that helps someone—including future you—install, run, and understand a project without opening a support chat.","2024-12-18",[261,262,50],"documentation","open-source",{"_path":264,"title":265,"description":266,"date":267,"tags":268,"readingTime":12},"\u002Fblog\u002Fprogressive-enhancement-still-pays-off\u002F","Progressive Enhancement Still Pays Off","Build the core experience in HTML first, then layer CSS and JavaScript so your site stays useful when networks, scripts, or devices misbehave.","2024-11-05",[86,10,269],"architecture",1784620504459]