Building the project detail view for Greenhouse
Greenhouse is my creative-project manager, the one that forces cooldowns and rotation instead of letting me pile up half-finished ideas forever. The dashboard already listed active projects in three zones: ripe today, worklist, cooling. But you couldn’t click into a project. There was no way to see its cooldown countdown, its touch history, or actually move it forward through the pipeline.
The plan
Two things needed deciding before I could write any code, and neither was obvious from the code alone.
First: does advancing a project to the next stage count as a “touch”? Touching is the only thing that starts the 7-day cooldown in this app, and the existing move_project_to_stage function deliberately does not record one. I had to pick a side. I went with yes, advancing implies a touch, on the theory that moving a project forward is itself evidence of a completed work session.
Second: what happens at the last stage? The schema already had a Released status and a released_at timestamp sitting unused. Nothing set them. I added a “Publish” action at the terminal stage that finally wires those up.
The detour
Partway through, I stopped and asked myself whether the 7-day cooldown was even the right number. Should it be shorter? Should cooldown exist at all, or should the app just enforce touch order and trust me to touch things honestly?
Working it through surfaced something I’d tangled up: rotation (least-recently-touched project floats to the top) and cooldown (a minimum time floor after a touch) aren’t competing ideas. Rotation is already how the worklist sorts. Cooldown is just a floor bolted onto it. So the real question wasn’t “cooldown vs rotation,” it was “how long should the floor be.” And pure rotation with no floor falls apart exactly where it would hurt me: with only one or two active projects, there’s nothing to rotate to, so the “gap” between touches is just however fast I click back and forth.
That reframe made the actual decision easy: lower the default from 7 days to something shorter, keep the enforced-cadence idea intact, and file the “what if honor code instead” question as a separate spike rather than let it hijack the feature I was actually building.
Building it
The Rust side needed two new engine functions: advance_stage (touch, then move the folder to the next stage in the config’s ordered stage list) and release_project (flip status to Released, stamp the timestamp). Both compose existing pieces rather than reinventing them. On the Svelte side, a project card’s name became a real button, opening a new detail view with the stage name, cooldown countdown, touch history, and the advance/publish controls.
The part I actually want to write up properly some day: verifying it. This app has a WebDriver harness that drives the real compiled binary through real clicks and real IPC calls, not mocks. The catch is that a live WebDriver session can’t fast-forward wall-clock time, and idea maturity and cooldown are both multi-day timers. So there’s no way to get from “capture an idea” to “click through it” inside one test run.
The fix was to seed the SQLite database directly with sqlite3 before launching the app, dropping in an active project already at whatever stage the test needed. That’s not cheating; every command still runs for real once the app boots, I just skipped past the part where I’d otherwise be waiting seven days for a timer.
Where it landed
Both engine functions have unit tests, the Svelte component has mockIPC tests and an automated accessibility check, and the WebDriver script clicks through advancing a project’s stage and publishing one at the terminal stage against a real binary. Screenshot proof and all the usual checks (clippy, fmt, type-check, build) are clean.
The best part of the session wasn’t the code, though. It was catching myself about to change a core app mechanic on a tangent, stepping back, and realizing the actual complaint had a much smaller fix sitting right next to it.
Related reading
Seed tray: the ideas that vanished for a week
Capture an idea in Greenhouse and it disappeared entirely until day seven. The fix was a zone with a countdown - and the discipline of not adding a skip button.
Harvest shelf: giving Greenhouse's released items somewhere to land
Releasing a project was write-only: the database stamped a timestamp, a badge branch could never render, and the entire payoff of the pipeline was invisible.
The callback that couldn't say what happened
Fixing one dashboard-refresh bug exposed a second hiding in the same shared component - and a third bug that turned out not to exist at all.