Poll only what can change, and deploy production from a release - #154
Merged
Conversation
Live updates defaulted on and polled /api/manifest every 5s for the app's lifetime regardless of what was loaded. That only makes sense for a working tree: the poll compares content_signature, which tracks the tree's mtime/size/dirty state, and a remote source is cloned once via ensure_clone and never re-fetched, so its signature cannot move. Every poll against a clone was a scan that could not report anything. CURRENT_SOURCE_IS_LOCAL says whether the applied source is a tree on disk; LIVE_UPDATES_ACTIVE combines it with the toggle. The timer now never starts for a clone rather than ticking and returning early, and because the effect tracks the source, switching between a clone and a tree starts or stops it without a reload. The footer's heartbeat and the Auto-refresh section read the same signal, so neither can advertise watching that isn't happening. The section also says which case you are in, since its controls look identical either way. Section descriptions widen to ComponentChildren so a section can explain itself from state rather than only carrying static prose. Closes #153 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A tagged release built and published the image and stopped there; the deploy was a manual click in Forgejo's UI. release.yml now dispatches the deploy workflow itself, in a job that needs the release job, since the image has to exist before the deploy pulls it. It no-ops with a notice when the Forgejo target isn't configured, so a fork's release still succeeds. `just deploy` is the same dispatch by hand, for redeploying without cutting a release. It deliberately isn't wired into `just release`: that recipe only pushes a tag, so calling it there would fire the deploy before the image was built. Host, repo and default app go in .env, which is tracked; the token goes in .local/deploy.env, which is not. The recipe names whichever is missing and where it belongs rather than failing on an unset variable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FORGEJO_HOST and FORGEJO_REPO were in .env, which is committed. This repo is public, so together they would have published the host and layout of a private homelab. They move to .local/deploy.env alongside the token; .env keeps only which app to deploy, which is this project's own name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Host and repo were repository variables, which aren't masked, and the step echoed both. This repo is public, so its Actions logs are too, and a release would have printed the homelab's host and repo path on every tag. They're secrets now, and nothing echoes either one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
deploy.env.example is committed as the template for .local/deploy.env, which isn't, and the README's Release section gains a Deploy subsection: what a tagged release does on its own, how to redeploy without one, and which of the four values is safe to track. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
One less thing to remember after a clone: setup copies the example into .local/ when it isn't already there, and leaves a filled-in one alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Owner
Author
|
Manually verified in a dev container with local repos enabled: the live-updates behaviour reads correctly for a local working tree and for a clone, including switching between them. The Forgejo dispatch is still untested end to end — the API path, token scope and |
A release publishes an image and now deploys production, so a v* tag from anyone with write access would ship to prod. The release job runs only for the repo owner, and the deploy job needs it, so one guard covers both. This is the second line of defence, not the first: a tag ruleset restricting who can create v* stops the tag itself rather than the run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two things: the live-updates fix from #153, and the Forgejo deploy path.
Closes #153
Live updates only where they mean something
Live updates defaulted on and polled
/api/manifestevery 5s for the app's lifetime regardless of what was loaded.That only makes sense for a working tree. The poll compares
content_signature, which tracks the tree's mtime / size / dirty state, and a remote source is cloned once viaensure_cloneand never re-fetched — so its signature cannot move. Every poll against a clone was a scan that could not report anything, and the UI implied otherwise: the footer dot heartbeated, and the Auto-refresh settings promised the city would re-render "whenever the project's files change".CURRENT_SOURCE_IS_LOCALsays whether the applied source is a tree on disk;LIVE_UPDATES_ACTIVEcombines it with the toggle. The timer never starts for a clone rather than ticking and returning early, and because the effect tracks the source, switching between a clone and a tree starts or stops it without a reload. The footer and the settings section read that same signal, so neither can advertise watching that isn't happening.The Auto-refresh section also says which case you're in, since its controls look identical either way. That needed
SectionNode.descriptionto widen fromstringtoComponentChildren, so a section can explain itself from state rather than only carrying static prose.Deploy
A tagged release built and published the image and stopped there; deploying was a manual click in Forgejo's UI.
release.ymlnow dispatches the deploy workflow itself, in a job thatneedsthe release job — the image has to exist before the deploy pulls it. It no-ops with a notice when the deploy secrets aren't configured, so a fork's release still succeeds, and so does this repo's before the secrets are set.just deployis the same dispatch by hand, for redeploying without cutting a release. It is deliberately not wired intojust release: that recipe only pushes a tag, so calling it there would fire the deploy before the image was built.Two things I got wrong first and corrected, both the same mistake:
FORGEJO_HOST/FORGEJO_REPOstarted in the tracked.env. This repo is public, so that would have committed the host and layout of a private homelab. They live in.local/deploy.envnow, seeded from a committeddeploy.env.examplebyjust setup.Setup this PR does not do
Settings → Secrets and variables → Actions → Secrets:
FORGEJO_HOST,FORGEJO_REPO,FORGEJO_TOKEN. The token needsrepository → Read and Writeon Forgejo and nothing else.Verification
just test(370 pytest, 2899 vitest) andjust lintgreen; the pre-push gate passed. 7 new tests cover the predicate and the combined gate, including that a switch flips it and that the toggle still wins for a local source.just deploy's guard path is exercised: with nothing configured it names all three missing values and where each belongs, rather than failing on an unset variable.just setupis idempotent — a second run leaves a filled-in.local/deploy.envuntouched.Not verified: the dispatch itself. I have no access to the Forgejo instance, so the API path, the token scope, and the
appinput name are reasoned from the pasted workflow and Forgejo's API shape.just deploylocally is the cheap way to confirm the chain before trusting it on a real release.🤖 Generated with Claude Code