My goal in the first few weeks is to get a v0.0.1 release out the door, which means getting the skeleton in a state where it can be released, even if it doesn't do anything yet. That means front-loading all the deployment, CI, testing, and DevOps work, so that the actual development of features can be as smooth as possible. Like, the app will literally do nothing, it's just a skeleton, but it will be a releasable skeleton. Nobody would sign up for it, much less pay, but it will be a real release, with a real version number, and it will be available for anyone to install and run. That way, when I start adding features, I can iterate on a real release, instead of just building in a vacuum.
My personal philosophy is: Ship something, so you can iterate on it.
Not doing that means spending weeks or months building something that you think is great, without ever knowing when to stop and ship. You keep adding features, and tweaking things, and refactoring, and never actually get to the point where you can say "this is a release". By shipping something early, even if it's just a skeleton, there's a clear path to a "v0.0.2", "v0.1.0", and so on. It creates a sense of momentum and progress, and it saves you from getting stuck in an endless loop of "just one more feature". Even if no one signs up for the skeleton, it's still a milestone, and it still gives you a foundation to build on. Releasing a v0.0.2 that fixes a couple of minor bugs and adds a small new feature, is way easier than spending months building up to a big v1.0 and trying to get EVERYTHING "finished". But by the time I get to a v1.0, I will have had 100s of releases, tons of dev logs, tons of documentation on the journey, it won't feel like a project that has just popped up out of nowhere. There'll be history. There may already be active users, and it will be a much more polished product, with a lot of real-world feedback already built in.
Anyway. CI and the deployment workflows are now wired up. Deployments to production are zero-downtime: dual php-fpm replicas behind nginx, rolling restart via SSH-triggered script on the server.
With that in place, I opened the first PR against the skeleton and let CodeRabbit loose on it. It had like 100 findings, it's nuts. Most of today was triaging them, and a fair chunk turned into real hardening work. I am so happy to have something like CodeRabbit at my disposal, just to sense-check everything I'm doing. I treat it almost like an extra "lint" step, but instead of just style issues, it's also security, best practices, and potential bugs. It's a second pair of eyes on the code, which is invaluable when you're working solo. AI code review might not be perfect, but the alternative would be no code review at all, which is undeniably worse.
CI got rewritten to run through Docker compose. This one bit me. The dev environment depends on OrbStack's *.local routing, container hostnames, Playwright baked into the devtools image, and env vars from the compose file. Running bun and PHPUnit natively on GitHub runners was never going to produce the same result. Now every CI job is docker compose exec devtools <cmd>, same entry point as local dev, with the PHP version matrix flowing through a build arg on the devtools Dockerfile.
Path A and Path B are both tested in CI now. It was just Path A before. Every PHP job runs across 8.3/8.4/8.5 x Path A/B, which is 6 combinations per app, but it's worth it to catch any divergence between the two paths immediately. I won't be running every combination locally, so CI is the safety net for that.
Tests are all working. Unit tests in PHPUnit and Vitest, integration tests in Playwright. The skeleton doesn't have any real functionality yet, so the tests are mostly just smoke tests to make sure the app boots up and the pages load without errors. But it's a start.
And AI code review was definitely worth the time, with a liberal amount of judgment applied.