Publish feedback: forge CI status polling with honest no-token fallback #11

Closed
opened 2026-07-11 13:00:40 +02:00 by dominik.polakovics · 2 comments

What to build

What the editor sees after Publish (docs/DECISIONS.md D15, D16): with a forge API token configured (out-of-repo), poll the CI status of the pushed commit on Forgejo/Gitea and GitHub and show real state in the UI — Publishing… → Live ✓, or Build failed with a copyable log link. Without a token, show honestly that the change was handed to deployment and should be live in a few minutes. GitLab is out of scope.

Acceptance criteria

  • With a Forgejo/Gitea token: commit status transitions render live in the UI for success and failure; failure shows a link to the run
  • Same against GitHub Actions
  • Without a token: static honest message, no fake success state, no error noise
  • Token comes from environment/secrets file, never from the repo; polling backs off and stops after a terminal state
  • Forge API errors degrade to the no-token message rather than blocking publish

Blocked by

## What to build What the editor sees after Publish (docs/DECISIONS.md D15, D16): with a forge API token configured (out-of-repo), poll the CI status of the pushed commit on Forgejo/Gitea and GitHub and show real state in the UI — Publishing… → Live ✓, or Build failed with a copyable log link. Without a token, show honestly that the change was handed to deployment and should be live in a few minutes. GitLab is out of scope. ## Acceptance criteria - [ ] With a Forgejo/Gitea token: commit status transitions render live in the UI for success and failure; failure shows a link to the run - [ ] Same against GitHub Actions - [ ] Without a token: static honest message, no fake success state, no error noise - [ ] Token comes from environment/secrets file, never from the repo; polling backs off and stops after a terminal state - [ ] Forge API errors degrade to the no-token message rather than blocking publish ## Blocked by - #10
Author
Owner

This was generated by AI during triage.

Agent Brief

Category: enhancement
Summary: After a publish push, show the editor what actually happened to their change: with a forge API token configured (out-of-repo), poll the pushed commit's CI status on Forgejo/Gitea or GitHub and render Publishing… → Live ✓ / Build failed (with a link to the run); without a token — or when the forge API misbehaves — show an honest static "handed to deployment" message instead of fake certainty.

Dependency note: this issue is hard-blocked by #10 (git save and per-page publish), which provides the publish flow itself — the commit to main and the push whose CI outcome this issue reports, including the pushed commit SHA. The scheduler handles ordering; do not start until #10 is merged. The issue body's docs/DECISIONS.md references (D15, D16) point at a file that is not committed — this brief is self-contained.

Current behavior:
After #10, Publish commits a page's bundle to main and pushes it. From the editor's perspective the story ends at "push succeeded": whether the site's CI pipeline then built and deployed the change — or failed and left the live site stale — is invisible. The editor has no way to distinguish "live in production" from "push accepted, deployment pending or broken" without leaving the tool and reading the forge UI. The SPA already has an established pattern of polling a harness status endpoint and rendering state from it (the Hugo supervisor status), but nothing tracks post-push deployment state.

Desired behavior:

Token-backed CI polling. When a forge API token is configured, the harness — not the browser — polls the forge's commit-status API for the commit each publish pushed, normalizes the result, and exposes it to the SPA. The token comes from the environment or a secrets file outside the site repository; it is never read from the repo, never committed, never sent to the browser, and never logged. Two forge families are supported:

  • Forgejo/Gitea — the combined commit status API for the pushed SHA.
  • GitHub — commit statuses and/or check runs (GitHub Actions surfaces results as check runs; the implementation must see Actions outcomes).

Which forge to talk to, and its API base URL, is derived from the site repository's push remote URL, with an explicit configuration override for ambiguous cases (e.g. a self-hosted Forgejo on a custom domain that URL sniffing cannot classify).

UI states. After a publish, the publish surface in the SPA shows:

  • Publishing… while the forge reports the commit's checks as pending/running (or before the first status appears).
  • Live ✓ when the commit's checks reach terminal success.
  • Build failed when they reach terminal failure/error, together with a copyable link to the failing run/log page on the forge so the editor can hand it to a developer.

State transitions render live as polling progresses — the editor watches Publishing… become Live ✓ without reloading.

Honest no-token fallback. With no token configured, no polling happens at all. The UI shows a static, honest message: the change was handed to deployment and should be live within a few minutes. No fake success state, no spinner that never resolves, no error noise.

Degradation on API errors. Any forge API failure while polling — bad credentials, 404, network error, rate limiting, unparseable response — degrades that publish's feedback to the same honest no-token message. Errors never block or fail the publish itself (which already succeeded), and never spam the editor; at most a log line on the server.

Polling discipline. Polling starts when a publish's push completes, backs off over time (increasing intervals rather than a fixed tight loop), and stops permanently for a commit once a terminal state is reached (success, failure, or degradation). A commit whose checks never report anything (e.g. the repo has no CI configured) must not be polled forever: after a bounded window, degrade to the honest message and stop. Multiple publishes in one session are each tracked; the UI reflects the state of the most recent publish of a page.

Key interfaces:

  • A forge-client abstraction in the Go harness with one implementation per forge family (Forgejo/Gitea, GitHub), each mapping the forge's raw status/check payloads to a small normalized model: state (pending / success / failure / error) plus a human-facing URL to the run. New forges later mean new implementations, not changes to callers.
  • Token/configuration contract: token via environment variable or an out-of-repo secrets file; forge kind + API base derived from the push remote URL with a config override. Absence of a token selects the fallback path cleanly (not an error path).
  • The publish result from #10 — must carry the pushed commit SHA (and page identity) so feedback can be correlated to what was published.
  • A harness API surface the SPA polls for publish feedback (either a dedicated endpoint or an extension of the existing status contract), exposing per-publish: phase (publishing / live / failed / handed-off), and the run URL when failed. The SPA renders purely from this; it never talks to the forge directly.

Acceptance criteria:

  • With a Forgejo/Gitea token configured: after a publish, the UI transitions Publishing… → Live ✓ for a passing pipeline, and Publishing… → Build failed for a failing one, with a copyable link to the failing run
  • The same transitions work against GitHub with checks run via GitHub Actions
  • Without a token: the UI shows the static honest "handed to deployment" message — no polling requests are made, no fake success, no error noise
  • The token is read only from the environment or an out-of-repo secrets file; it is never read from the site repository, never exposed to the browser, and never logged
  • All forge polling happens server-side in the harness; the SPA only polls the harness API
  • Polling intervals back off over time and polling stops permanently once a terminal state (success, failure, or degradation) is reached; a commit with no CI activity degrades to the honest message after a bounded window instead of polling forever
  • Forge API errors (bad token, 404, network failure, rate limit) degrade that publish's feedback to the no-token message; the publish itself is unaffected
  • Forge detection from the remote URL is covered by tests, including the configured override; status normalization for both forge families is tested against representative API payloads (mock/fake servers — no live forge required in CI)

Out of scope:

  • GitLab support — explicitly excluded for v1
  • The publish mechanics themselves — commits, pre-flight build, rebase, discard (#10)
  • Webhook/push-based status delivery — this is polling only
  • Identity, locks, and CSRF (#13); the token here is a forge API credential, not editor identity
  • Scaffolding CI workflows for new sites (#17) — this issue reads CI state, it does not create pipelines
  • Deployment itself (the tool never deploys; it only reports what the forge's CI did)
> *This was generated by AI during triage.* ## Agent Brief **Category:** enhancement **Summary:** After a publish push, show the editor what actually happened to their change: with a forge API token configured (out-of-repo), poll the pushed commit's CI status on Forgejo/Gitea or GitHub and render Publishing… → Live ✓ / Build failed (with a link to the run); without a token — or when the forge API misbehaves — show an honest static "handed to deployment" message instead of fake certainty. **Dependency note:** this issue is **hard-blocked by #10** (git save and per-page publish), which provides the publish flow itself — the commit to main and the push whose CI outcome this issue reports, including the pushed commit SHA. The scheduler handles ordering; do not start until #10 is merged. The issue body's `docs/DECISIONS.md` references (D15, D16) point at a file that is not committed — this brief is self-contained. **Current behavior:** After #10, Publish commits a page's bundle to main and pushes it. From the editor's perspective the story ends at "push succeeded": whether the site's CI pipeline then built and deployed the change — or failed and left the live site stale — is invisible. The editor has no way to distinguish "live in production" from "push accepted, deployment pending or broken" without leaving the tool and reading the forge UI. The SPA already has an established pattern of polling a harness status endpoint and rendering state from it (the Hugo supervisor status), but nothing tracks post-push deployment state. **Desired behavior:** *Token-backed CI polling.* When a forge API token is configured, the harness — not the browser — polls the forge's commit-status API for the commit each publish pushed, normalizes the result, and exposes it to the SPA. The token comes from the environment or a secrets file outside the site repository; it is never read from the repo, never committed, never sent to the browser, and never logged. Two forge families are supported: - **Forgejo/Gitea** — the combined commit status API for the pushed SHA. - **GitHub** — commit statuses and/or check runs (GitHub Actions surfaces results as check runs; the implementation must see Actions outcomes). Which forge to talk to, and its API base URL, is derived from the site repository's push remote URL, with an explicit configuration override for ambiguous cases (e.g. a self-hosted Forgejo on a custom domain that URL sniffing cannot classify). *UI states.* After a publish, the publish surface in the SPA shows: - **Publishing…** while the forge reports the commit's checks as pending/running (or before the first status appears). - **Live ✓** when the commit's checks reach terminal success. - **Build failed** when they reach terminal failure/error, together with a copyable link to the failing run/log page on the forge so the editor can hand it to a developer. State transitions render live as polling progresses — the editor watches Publishing… become Live ✓ without reloading. *Honest no-token fallback.* With no token configured, no polling happens at all. The UI shows a static, honest message: the change was handed to deployment and should be live within a few minutes. No fake success state, no spinner that never resolves, no error noise. *Degradation on API errors.* Any forge API failure while polling — bad credentials, 404, network error, rate limiting, unparseable response — degrades that publish's feedback to the same honest no-token message. Errors never block or fail the publish itself (which already succeeded), and never spam the editor; at most a log line on the server. *Polling discipline.* Polling starts when a publish's push completes, backs off over time (increasing intervals rather than a fixed tight loop), and stops permanently for a commit once a terminal state is reached (success, failure, or degradation). A commit whose checks never report anything (e.g. the repo has no CI configured) must not be polled forever: after a bounded window, degrade to the honest message and stop. Multiple publishes in one session are each tracked; the UI reflects the state of the most recent publish of a page. **Key interfaces:** - A forge-client abstraction in the Go harness with one implementation per forge family (Forgejo/Gitea, GitHub), each mapping the forge's raw status/check payloads to a small normalized model: state (pending / success / failure / error) plus a human-facing URL to the run. New forges later mean new implementations, not changes to callers. - Token/configuration contract: token via environment variable or an out-of-repo secrets file; forge kind + API base derived from the push remote URL with a config override. Absence of a token selects the fallback path cleanly (not an error path). - The publish result from #10 — must carry the pushed commit SHA (and page identity) so feedback can be correlated to what was published. - A harness API surface the SPA polls for publish feedback (either a dedicated endpoint or an extension of the existing status contract), exposing per-publish: phase (publishing / live / failed / handed-off), and the run URL when failed. The SPA renders purely from this; it never talks to the forge directly. **Acceptance criteria:** - [ ] With a Forgejo/Gitea token configured: after a publish, the UI transitions Publishing… → Live ✓ for a passing pipeline, and Publishing… → Build failed for a failing one, with a copyable link to the failing run - [ ] The same transitions work against GitHub with checks run via GitHub Actions - [ ] Without a token: the UI shows the static honest "handed to deployment" message — no polling requests are made, no fake success, no error noise - [ ] The token is read only from the environment or an out-of-repo secrets file; it is never read from the site repository, never exposed to the browser, and never logged - [ ] All forge polling happens server-side in the harness; the SPA only polls the harness API - [ ] Polling intervals back off over time and polling stops permanently once a terminal state (success, failure, or degradation) is reached; a commit with no CI activity degrades to the honest message after a bounded window instead of polling forever - [ ] Forge API errors (bad token, 404, network failure, rate limit) degrade that publish's feedback to the no-token message; the publish itself is unaffected - [ ] Forge detection from the remote URL is covered by tests, including the configured override; status normalization for both forge families is tested against representative API payloads (mock/fake servers — no live forge required in CI) **Out of scope:** - GitLab support — explicitly excluded for v1 - The publish mechanics themselves — commits, pre-flight build, rebase, discard (#10) - Webhook/push-based status delivery — this is polling only - Identity, locks, and CSRF (#13); the token here is a forge API credential, not editor identity - Scaffolding CI workflows for new sites (#17) — this issue reads CI state, it does not create pipelines - Deployment itself (the tool never deploys; it only reports what the forge's CI did)
Author
Owner

Closing as superseded: the maintainer has decided on a clean-slate reset of the project (issue #29, PR #30). The v1 implementation this issue builds on is being removed and the plan will be re-drawn from scratch; this spec is retired rather than actioned.

Closing as superseded: the maintainer has decided on a clean-slate reset of the project (issue #29, PR #30). The v1 implementation this issue builds on is being removed and the plan will be re-drawn from scratch; this spec is retired rather than actioned.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle#11
No description provided.