Junkyard Watchdog keeps a pulse on self-service junkyards across the nation so you never miss the donor you have been chasing. It ingests inventory on a tight cadence, normalises the data, and delivers actionable alerts when something hitting your watchlists lands on the yard.
What's inside
Laravel 12 + MySQL backend with Sanctum auth, notification scheduling, and real-time ingest health dashboards
Expo/React Native client (iOS + web) with garage management, watchlist creation, and digest controls
Admin tooling for sources, locations, digests, and operational telemetry
Stripe-powered monetization, Mailgun email, and push notifications wired through Expo
Launch track
Currently build 1.0.0 is being review in App Store Connect, and hope to be live asap!
~/timeline
2025
Hit Submit!
Oct 02, 2025
Wrapped up the last blockers and finally shipped the 1.0 build off to App Review. Knocked out the 12.9" iPad screenshots, answered the App Privacy questionnaire in detail (every data type, why we store it, and where...
Hit Submit!
Wrapped up the last blockers and finally shipped the 1.0 build off to App Review. Knocked out the 12.9" iPad screenshots, answered the App Privacy questionnaire in detail (every data type, why we store it, and where), and set the pricing/category combo to Tier 0 under Shopping with Utilities as a backup.
## Contact Info
- **Purpose:** Account management & support
- **Linked:** Yes
- **Tracking:** No
Along the way I wrote docs/launch/app-store-privacy.md and a device-specific screenshot README so future capture sessions aren’t a scavenger hunt. As soon as the privacy matrix was done, App Store Connect accepted the build for review. Now it’s just a waiting game—monitor for questions, prep the canned responses, and get the launch checklist ready for go time.
App Store Prep Marathon
Oct 01, 2025
Today was all about teeing things up for App Review. Locked in the App Store copy (subtitle, keywords, privacy/support URLs) and mirrored the final text in docs/launch/app-store-prep.md so we don’t lose it later. Seeded...
App Store Prep Marathon
Today was all about teeing things up for App Review. Locked in the App Store copy (subtitle, keywords, privacy/support URLs) and mirrored the final text in docs/launch/app-store-prep.md so we don’t lose it later. Seeded staging with a ScreenshotDemoSeeder so every tab looks hero-shot ready, and double-checked the reviewer account details in App Store Connect.
Push credentials, Expo tokens, the works—they all got verified so TestFlight matches production. The two lingering tasks are the iPad 12.9" screenshots and the App Preview video storyboard, but at least the list is honest now.
Release plan is to auto-launch once review approves; no manual hold. Privacy answers stay conservative until the telemetry stack ships, which keeps things simple. All in all, a solid paperwork day.
Launch Freeze Mode
Sep 30, 2025
Pulled the lever on the feature freeze today. Logged the policy, ran every automated test we have (Laravel, lint, Jest, Playwright against staging and prod), and rotated the last batch of production secrets for good meas...
Launch Freeze Mode
Pulled the lever on the feature freeze today. Logged the policy, ran every automated test we have (Laravel, lint, Jest, Playwright against staging and prod), and rotated the last batch of production secrets for good measure. Schedulers got a quick health check to confirm LKQ, Pick-n-Pull, Pull-A-Part, and UPAP are still on cadence.
Manual QA on TestFlight surfaced a few UX splinters—forgot-password handing off to Mail felt rough, the change-password form needed a visibility toggle, and Cloudinary squawked once—so those went straight onto the punch list.
We also launched the /support landing page for App Review, and Playwright learned how to skip webhooks via STRIPE_SKIP_WEBHOOK=1. The instructions from here are clear: finish the manual passes, prep the reviewer account package, and keep the freeze tight until we get our green light.
Production Housekeeping
Sep 29, 2025
Checklist day! I spun up the jw-prod-01 droplet, installed the usual suspects (PHP 8.4, nginx, MySQL, Redis, Supervisor), and pointed the domain so we’ve got real HTTPS before launch week kicks in. Secrets got rotated, t...
Production Housekeeping
Checklist day! I spun up the jw-prod-01 droplet, installed the usual suspects (PHP 8.4, nginx, MySQL, Redis, Supervisor), and pointed the domain so we’ve got real HTTPS before launch week kicks in. Secrets got rotated, templates scrubbed, and I dropped a nightly backup script that ships encrypted dumps to Spaces just in case.
While snapshots ran I wrote up the mobile regression plan so QA has receipts, and logged every infra decision in the launch docs. There are still a few follow-ups—Spaces creds, DNS rollback notes—but the foundation is ready. Nothing flashy, just responsible adulting for prod.
Watchlist Polish Party
Sep 26, 2025
Spent the day sanding down the last rough edges on watchlist and garage cards. The old tinted backgrounds were fighting dark mode, so I rolled them back to the base surface palette and let a bold unread border do the hea...
Watchlist Polish Party
Spent the day sanding down the last rough edges on watchlist and garage cards. The old tinted backgrounds were fighting dark mode, so I rolled them back to the base surface palette and let a bold unread border do the heavy lifting. Laravel Pint is now wired into CI too, which means my future pull requests don’t get roasted for style drift.
Theme mocks picked up explicit MD3 fallbacks so snapshot tests chill even if Paper updates, unread micro-interactions survived the refactor, and the to-do list is down to tab highlights and telemetry work. Felt like a productive design cleanup kind of day.
Making The Watchlist Form Feel Nice
Sep 22, 2025
The watchlist dialog had been giving off total panic energy—every time I typed a name the cursor hopped around like it drank six espressos. On Sep 22 at 9:06 PM EDT (commit b8957ec) I finally called it: time to build my...
Making The Watchlist Form Feel Nice
The watchlist dialog had been giving off total panic energy—every time I typed a name the cursor hopped around like it drank six espressos. On Sep 22 at 9:06 PM EDT (commit b8957ec) I finally called it: time to build my own text input wrapper so the UI would stop gaslighting me.
I threw together an UncontrolledTextInput that just… leaves people alone while they type. It keeps the “real” value in a ref, pokes the native field with setNativeProps, and only syncs React state when it’s ready. No more jitter, no more ghost deletes.
Once the dialog behaved, I rode that momentum. The watchlist tab picked up the new component, the Expo badge updates stayed in sync, and I wrote a tiny QA checklist so anyone can sanity-check the floating labels. The polishing pass wrapped on Sep 29 at 9:11 PM EDT (commit 689c1f3) when I tweaked the focus handling one last time.
The whole decision chain was basically: “Does this feel gross?” → “Yes” → “Okay, build the thing that makes it feel human.” Code’s just the vehicle. The fun part is when it stops fighting us and starts behaving like an actual app you’d be proud to hand to a friend.
My Car-Part Rabbit Hole
Sep 21, 2025
I went into Sunday promising myself “just a little recon.” By 4:05 PM EDT (commit a61e61d) I had accidentally built an importer. Classic. The plan was to poke around Car-Part’s mobile site, scribble some notes, and keep...
My Car-Part Rabbit Hole
I went into Sunday promising myself “just a little recon.” By 4:05 PM EDT (commit a61e61d) I had accidentally built an importer. Classic.
The plan was to poke around Car-Part’s mobile site, scribble some notes, and keep things light. I dumped their parts.js file into .codex/carpart/parts_catalog.json, sketched the flow in my notebook, and realized the hierarchy was begging to live in our database. Decision made: if we’re going to talk to this data later, might as well make it comfortable now.
Watching the importer climb through each nested category felt like untangling a thrift-store necklace. I added a --dry-run switch because I know future Trever likes to peek without messing anything up. The tests kept me honest: if a part code shows up twice, it only gets one seat at the table.
The casual call I made: store the real Car-Part payloads under backend/resources/catalog so staging doesn’t care whether my hidden folder exists. That keeps deployments boring (in a good way). It’s not glamorous, but now when we finally wire a UI for picking catalog parts, the data is already waiting with labeled name tags.
Moral of the day: sometimes “I’ll just explore” ends with a brand-new command, and that’s fine. Exploration is the fun part anyway.
Mailgun & Deploy Triage
Sep 21, 2025
I thought the day would be quiet, then staging decided digest emails should fail in the queue. Turns out the deploy script was writing literal "null" strings into the SMTP env vars—good times. I rolled the work...
Mailgun & Deploy Triage
I thought the day would be quiet, then staging decided digest emails should fail in the queue. Turns out the deploy script was writing literal "null" strings into the SMTP env vars—good times. I rolled the workflows back to the known-good version, pointed the staging DNS at the right droplet, and pulled in the Mailgun client so Laravel could hit the API instead of pretending SMTP was fine.
The action items were simple: fix the env writer, restart the queue, and watch Mailgun’s logs like a hawk. Not glamorous, but now the digest pipeline is ready for the next release without surprise failures. Sometimes ops days are just detective work with a side of DNS.
Stripe Checkout, But Make It Chill
Sep 21, 2025
I woke up thinking I’d sneak in a “quick Stripe tweak” before lunch. You already know how that goes. Around 1:10 PM EDT I pushed commit 4bf5cc7, which basically means I finally admitted the native app needed a friendly l...
Stripe Checkout, But Make It Chill
I woke up thinking I’d sneak in a “quick Stripe tweak” before lunch. You already know how that goes. Around 1:10 PM EDT I pushed commit 4bf5cc7, which basically means I finally admitted the native app needed a friendly landing page when Stripe bounces back. The rule of the day: if it isn’t a junkyardwatchdog:// link, it doesn’t get through. Everything else gets politely turned away before it can cause trouble.
The vibe I wanted was “you’re done, go back to the app” with a little sparkle. That meant a glassy, coral-gradient button and just enough copy to let folks know if they hit cancel, we won’t guilt-trip them. It’s seriously just a fancy trampoline for deep links, but it feels good.
Once that page was live, I started worrying about forgetting to test it next week. Commit bd7ac2a landed at 2:51 PM EDT and brought a Playwright script that signs up a fake account, spins up a checkout session, and replays Stripe’s webhooks so our plan states flip from Free → Pro → back again. The big call looked like this:
Was it overkill? Maybe. But future-me loves rolling up to a deploy with receipts. The decision tree was simple: if I can’t explain what happens when someone closes Stripe on their phone, I can’t ship it. Now I can chill. Stripe hands the user back to us, the smoke test keeps me honest, and I still got tacos for dinner.
Cashier Weekend Warrior
Sep 20, 2025
Spent Saturday getting serious about billing. Pulled in Laravel Cashier, let Stripe own the subscriptions, and added the migrations so users carry real plan records instead of our homemade enums. The new BillingManager h...
Cashier Weekend Warrior
Spent Saturday getting serious about billing. Pulled in Laravel Cashier, let Stripe own the subscriptions, and added the migrations so users carry real plan records instead of our homemade enums. The new BillingManager handles checkout sessions, customer portal links, and even the webhook handshake that flips plan and plan_renews_at automatically.
public function handleSubscriptionCreated(Event $event): void
{
$customer = $this->users->findByStripeId($event->customer);
$customer->markAsSubscribed($event->subscription, $event->currentPeriodEnd);
}
On the Expo side, the account screen now launches hosted checkout and the portal, updates state when you bounce back, and shows a friendly status pill. Tests cover the happy path end-to-end, so the next step is wiring plan gating and migrating the staging users into Stripe. Not bad for a weekend sprint.
Admin Refresh & Future Plans
Sep 17, 2025
Gave the admin area a well-deserved spa day. Every page—from Sources to Client Logs—now shares the same layout, and the quick actions are where your thumb expects them. While the polish dried, I queued up the welcome and...
Admin Refresh & Future Plans
Gave the admin area a well-deserved spa day. Every page—from Sources to Client Logs—now shares the same layout, and the quick actions are where your thumb expects them. While the polish dried, I queued up the welcome and day-seven lifecycle emails so staging drips feel real once Mailgun credentials land.
Most of the afternoon was spent writing: monetization notes, telemetry roadmap, vehicle catalog expansion. Not the most glamorous work, but having the plans saved in MONETIZATION.md and friends means I can revisit them without digging through my brain. Next up is wiring the telemetry pipeline for real and getting SMTP verified, but today felt like a nice reset.
Staging, Steady As She Goes
Sep 11, 2025
Quick check-in day. Staging’s humming on DigitalOcean with the deploy workflow rolled back to the stable script, production deploys now require a manual green light, and the admin UI finally feels like it belongs to the...
Staging, Steady As She Goes
Quick check-in day. Staging’s humming on DigitalOcean with the deploy workflow rolled back to the stable script, production deploys now require a manual green light, and the admin UI finally feels like it belongs to the same product. Runs auto-refresh, timeout hints surface, and the docs call out how to use the new diagnostics tools without guessing.
The real focus was planning the next punch list: finish the iOS push credential dance, run a staging smoke with the new workflows, and maybe sketch out the Pull-A-Part source. Nothing flashy, just making sure the pipes are tight before moving on.
Staging Sprint
Sep 10, 2025
Infrastructure day. I wired up Terraform apply/destroy workflows, scripted the “provision an existing droplet” flow, and taught GitHub how to run diagnostics on demand. Staging now knows how to restart Supervisor, renew...
Staging Sprint
Infrastructure day. I wired up Terraform apply/destroy workflows, scripted the “provision an existing droplet” flow, and taught GitHub how to run diagnostics on demand. Staging now knows how to restart Supervisor, renew TLS, and double-check Redis without me SSH’ing in like a goblin.
Meanwhile Playwright finally sails through against staging, and the EAS docs explain how to spin up a dev client without cursing Expo. The README even reads like a real runbook. It’s the kind of ops work that doesn’t make screenshots prettier, but it keeps release week from melting down, which is the whole point.
Digest Controls & Dev Rhythm
Sep 09, 2025
Today was short and sweet. I knocked out the TypeScript warning in the skeleton loader (React Native’s percent units are picky), then jumped into the admin digest tools. You can now preview a digest for any user, resend...
Digest Controls & Dev Rhythm
Today was short and sweet. I knocked out the TypeScript warning in the skeleton loader (React Native’s percent units are picky), then jumped into the admin digest tools. You can now preview a digest for any user, resend it on the spot, and see the latest ingest runs without diving into the database.
The rest of the notes were about cadence—ship unblockers first, keep commits small, and stay focused on one epic unless something’s on fire. It sounds obvious, but writing it down keeps me honest. Tests passed, PR merged, and I actually shut the laptop on time.
Dialing In Radius & Digests
Sep 08, 2025
Started the day babysitting the U-Pull-&-Pay job—added friendlier logging, cached the make IDs, and made sure we can recover from weird HTML without nuking memory. Once that calmed down, I turned to location stuff: A...
Dialing In Radius & Digests
Started the day babysitting the U-Pull-&-Pay job—added friendlier logging, cached the make IDs, and made sure we can recover from weird HTML without nuking memory. Once that calmed down, I turned to location stuff: Account got manual lat/lng fields (or ZIP geocoding if you don’t feel like guessing), and the Home view now respects your radius when it shows recent matches.
The real win was consolidating email digests so you only get one message per ingest instead of a spam waterfall. Grouped everything by watchlist and knocked the send volume way down.
Still need to pretty up the template and add scheduling knobs, but the plumbing’s in. The to-do list is mostly radius cleanup and input polish now, which feels like progress.
Garage Love & Better Logs
Sep 07, 2025
Today was all about making the garage tab feel like a place you actually want to hang out. I tightened up the list layout, styled the matches drill-down, and dropped in a lightweight Home screen that bubbles up nearby fi...
Garage Love & Better Logs
Today was all about making the garage tab feel like a place you actually want to hang out. I tightened up the list layout, styled the matches drill-down, and dropped in a lightweight Home screen that bubbles up nearby finds without rearranging the main tab order.
On the ops side, I finally wired client-side errors into the backend so when Expo throws a fit we see it in /admin/client-logs. Everything flows through console overrides with some throttling so the logs stay readable. Not deploying an iOS app since my HoldMyTicket days made me want a make-shift admin access to users errors, and I think this shall do for the time being on a shoe-string budget.
Add in the new Expo ticket storage, a scheduled receipts poller, and a fresh batch of tests (make test-all is happy again), and the app feels way more trustworthy. That’s the kind of Sunday sprint I can get behind.
Night Shift Notifications
Sep 06, 2025
Couldn’t sleep, so I wired up the rest of the notification stack. Account now shows your devices, lets you yank stale tokens, and even fires a local test push so you know Expo heard you. On the backend I started pruning...
Night Shift Notifications
Couldn’t sleep, so I wired up the rest of the notification stack. Account now shows your devices, lets you yank stale tokens, and even fires a local test push so you know Expo heard you. On the backend I started pruning invalid tokens and added a queue-friendly way to retry matches without spamming.
While I was there I cleaned up the watchlist dialog (hello, MD3 surfaces), made sure the list refetches after saves, and added little snackbars for error/success. Tossed in a Theme Debug gate for good measure and smoothed out the inventory detail view so it stops choking on nested resources. It’s the kind of work no one notices when it’s right—which is exactly the point.
Types, Clarity, and Calling It
Sep 06, 2025
After the Tamagui experiment, I put the UI back on Paper and went hunting for the TypeScript warnings that CI kept grumbling about. TanStack Query v5 changed how keepPreviousData works, so I swapped it for placeholderDat...
Types, Clarity, and Calling It
After the Tamagui experiment, I put the UI back on Paper and went hunting for the TypeScript warnings that CI kept grumbling about. TanStack Query v5 changed how keepPreviousData works, so I swapped it for placeholderData, tightened the FlashList props, and the red squiggles finally gave up.
The rest of the day was about strategy. I wrote down the plan for push tokens, theme polish, and the watchlist toggle work so we stop ping-ponging between epics. It wasn’t a flashy session, just the kind where you clean your tools and line up the next few moves. Needed that.
Autocomplete Jam Session
Sep 05, 2025
Coffee in hand, I dove straight into the make/model picker. The goal: make it feel like a native app, not a busted spreadsheet. I sketched out aliases so “Chevy” and “Chevrolet” finally hug it out, added indexes so the s...
Autocomplete Jam Session
Coffee in hand, I dove straight into the make/model picker. The goal: make it feel like a native app, not a busted spreadsheet. I sketched out aliases so “Chevy” and “Chevrolet” finally hug it out, added indexes so the search feels instant, and let the API return all the years in one shot when the UI asks nicely.
if ($request->boolean('include_years')) {
$models = $models->withAggregate('years as years', 'GROUP_CONCAT(DISTINCT year ORDER BY year DESC)');
}
Once the backend behaved, I gave the autocomplete a makeover—dark mode friendly popovers, a chill FAB on mobile, and themed hover states so drifting through suggestions doesn’t feel like spelunking. The plan from here is to keep merging aliases and get keyboard navigation dialed, but for today I’m just happy the picker finally matches the vibe of the rest of the app.
Afternoon UI Glow-Up
Sep 05, 2025
Post-lunch energy went straight into sanding rough edges. Every screen got the compact PageHeader treatment so titles stop screaming, the inventory list moved to Paper’s List.Item with that little NEW chip tucked in the...
Afternoon UI Glow-Up
Post-lunch energy went straight into sanding rough edges. Every screen got the compact PageHeader treatment so titles stop screaming, the inventory list moved to Paper’s List.Item with that little NEW chip tucked in the corner, and the watchlist picked up inline delete actions so the overflow menu can chill.
I also finally gave the autocomplete input the love it deserved: a tiny spinner when the network wakes up, an obvious clear button, and keyboard navigation that actually tracks which row you’re on. The Paper theme tokens got broadened too—now error states and outline variants have some personality without blowing up dark mode.
Still on the docket: sweep the app for stray Pressables pretending to be buttons and tune ripples/accessibility labels. But for a Friday afternoon pass, everything feels lighter and more consistent, which was the whole mission.
The Tamagui Detour
Sep 05, 2025
I couldn’t resist seeing what Tamagui felt like in the app, so I took a hard left and ported everything for a day. New tokens, new provider, new buttons—the whole nine. The dialog turned into a barebones modal, autocompl...
The Tamagui Detour
I couldn’t resist seeing what Tamagui felt like in the app, so I took a hard left and ported everything for a day. New tokens, new provider, new buttons—the whole nine. The dialog turned into a barebones modal, autocomplete got rebuilt from scratch, and I even added a make dev-web-clear script because Metro caches do not play nice with constant rewires.
In the end I backed out—Paper still fits our needs better right now—but the detour wasn’t wasted. We gained notification toggles on Account, the watchlist dialog logic is cleaner, and I know exactly what it’ll take if we ever run the migration for real. Sometimes you’ve gotta scratch the itch to move on.
Late-Night Wrap-Up
Sep 05, 2025
I should’ve closed the laptop, but the app was so close to feeling cohesive that I kept going. I ripped out the goofy Theme tab, tucked the switcher onto the Account screen where it actually belongs, and made sure every...
Late-Night Wrap-Up
I should’ve closed the laptop, but the app was so close to feeling cohesive that I kept going. I ripped out the goofy Theme tab, tucked the switcher onto the Account screen where it actually belongs, and made sure every major surface was running on Paper components with our brand colors mapped into MD3.
Autocomplete finally scrolls the active item into view, the watchlist dialog looks like it was designed on purpose, and the inventory list’s right column now stacks the row badge and NEW label without fighting for space. I jotted down the remaining lint/a11y cleanup so Monday-me knows what’s left.
No huge fireworks—just the satisfaction of seeing everything line up. Worth the extra hour.
Kicking The Tires
Sep 04, 2025
First day back in the garage and everything creaked. The admin sync endpoint was wide open, the scraper still had a rogue dd() hiding inside, and the frontend was glued to a hard-coded API URL. I spent the morning doing...
Kicking The Tires
First day back in the garage and everything creaked. The admin sync endpoint was wide open, the scraper still had a rogue dd() hiding inside, and the frontend was glued to a hard-coded API URL. I spent the morning doing the unglamorous stuff: wrapping /admin/* in real auth, swapping in Laravel’s HTTP client so the job actually respects timeouts, and teaching CORS to read from env vars instead of guessing.
By lunch the login flow finally returned { token }, Expo pulled its API base from EXPO_PUBLIC_API_URL, and Sail commands were short enough to remember. I even slapped together a Makefile so spinning up the stack feels like flipping a switch.
Afternoon was all polish: Paper tabs started looking like a real app, inventory rows finally showed make/model/year, and the NEW badge stopped shouting in the wrong place. The mental sticky note was simple—get the foundation solid so future-me can focus on the fun bits instead of chasing auth ghosts.