Repository navigation
feat(tokens): add colors for pull requests' states and checks - #16
Merged
Merged
Conversation
GitHub's own colors for a pull request's state (open, draft, queued, merged, closed) and its checks (passed, pending, failed), the same in both themes, so that fctl shows them as people know them from GitHub. Unknown, for a pull request nobody looked up yet, is muted text: #9198a1 on dark, #59636e on light. The playground shows them with the other tokens.
3 tasks done
kalaspuff
approved these changes
Oct 21, 2026
kalaspuff
deleted the
fctl/108-i-want-to-be-able-to-listen-to-webhooks
branch
October 21, 2026 17:03
fctl-sh Bot
added a commit
to hyperstructure/fctl
that referenced
this pull request
Oct 21, 2026
The release with the pull request and checks colors (hyperstructure/design#16), in place of its branch's commit.
kalaspuff
pushed a commit
to hyperstructure/fctl
that referenced
this pull request
Oct 21, 2026
…176) Sessions learned what became of their pull requests only from their agents, which look each one up when a turn ends, until it merges. A pull request merged or closed after the session's last turn, as most are, stayed "open" in the lists and on the session page for good, and nothing said whether its checks passed. Now GitHub tells the control plane, through the GitHub App's webhook, and session lists, the session page and `fctl show` show each pull request's current state and whether its checks are green ([ADR 0032](docs/adr/0032-githubs-webhook-keeps-sessions-pull-requests-current.md)). ## How it works - **The webhook**, `POST /api/v1/github/webhook`, takes deliveries signed with `GITHUB_WEBHOOK_SECRET` (HMAC-SHA256, constant time; 401 otherwise, 501 without the secret) for the events Pull request, Check run, Check suite and Status (`fctl_domain::webhooks`). - **A delivery is a cue, a lookup the answer.** D1's new `pull_request_sessions` (migration 0019, backfilled from every session's list) says which sessions list which pull request, with its head commit, which commit statuses name. For each listed pull request a delivery concerns, the control plane asks GitHub with one GraphQL query (title, state, draft, merge queue entry, times, and the head commit's checks counted by state), with a token of the installation that sent it, cached per isolate while valid. When the lookup fails, a `pull_request` delivery's payload gives the state. - **The sessions** get it at SessionDO's new `/pull-requests`, which keeps what is newer (`fctl_domain::pulls::update`): GitHub's `updatedAt` orders what GitHub and agents say, so an agent's stale state file can't reopen a merged pull request; an agent's `open` leaves a queued one queued (`gh` can't see merge queues); checks go by when the control plane asked. Viewers and dashboards see the change at once, D1 within seconds. - **Queued** is the webhook's action `enqueued` (or `queued`), a merge queue entry, or `mergeStateStatus` (else `mergeable_state`) of `queued` or `in_merge_queue`. - **When the state changed** (`state_changed_at`): when GitHub says it merged, closed or joined the queue, or else when fctl saw it change. Agents now ask `gh pr view` for `updatedAt`, `createdAt`, `mergedAt` and `closedAt` too (older `gh` falls back as before). - **Lookups without a delivery:** pull requests are looked up as sessions find them, and the hourly job looks up to 30 that aren't merged or closed and that nobody asked about for six hours. That catches missed deliveries and brings sessions from before the webhook up to date. - Instances without `GITHUB_WEBHOOK_SECRET` look nothing up and behave as before. `PullRequestRef` gains optional `state_changed_at`, `updated_at` and `checks` (`PullRequestChecks`: passed, failed, pending, sha, at), and `state` may be `queued`: older hosts, agents and CLIs ignore them. ## What people see Each pull request gets GitHub's octicon for its state in GitHub's color (open, draft, queued, merged, closed, or unknown until someone looked it up), and while it is open a mark for its checks: all passed ✓, some pending ●, one failed ✕. Tooltips and screen readers say it in words ("fctl#171, open, 1 of 4 checks failed"). The Pull requests section says when it merged, closed or joined the queue, and how many checks passed. `fctl show` prints `app#12 open, 1 of 4 checks failed`. The colors are the new `pr-*` and `checks-*` tokens of @hyperstructure/design v0.6.0 (hyperstructure/design#16); the table's last row, unknown, was cut off in the request, so its colors (`#59636e` light, `#9198a1` dark) and icon (git-pull-request) are my pick. ## Before merging - [x] Merge hyperstructure/design#16, which released v0.6.0, then pin `github:hyperstructure/design#v0.6.0` in `web/package.json` in place of the branch commit `c2c2094`: daa2e12. - [ ] On app.fctl.sh's GitHub App (fctl-sh): under General, make the webhook Active with the URL `https://app.fctl.sh/api/v1/github/webhook` and a new secret (`openssl rand -hex 32`); under Permissions & events, subscribe to Pull request, Check run, Check suite and Status (no new permissions). Then `echo "$SECRET" | cargo xtask secrets fctl-sh --set GITHUB_WEBHOOK_SECRET`. Until then, nothing changes. - [ ] Merge hyperstructure/fctl-site#36, the privacy policy, once this is deployed with the webhook on. ## How I verified it - Unit tests: the merge rules, merge queue signs, state times, check counting and GitHub's time format (`fctl_domain::pulls`); signatures against GitHub's published example, event parsing and the lookup's answer (`fctl_domain::webhooks`); the agent's `gh pr view` times; `fctl show`; the web app's names, labels and colors (`lib/pulls.test.ts`, `row.test.tsx`). `cargo xtask check` passes. - The GraphQL query and the exact request the Worker sends (installation token, headers, body), replayed with curl against #170 while its checks ran: GitHub answers it, and counts a cancelled run as a failure, as the parser does. - The migration's backfill on SQLite with all earlier migrations, both list formats, run twice. - End to end on `cargo xtask dev`, with dev sessions whose pull requests I seeded in their storage (dev projects have no repository), a throwaway app config so the endpoint is on, and deliveries signed as GitHub signs them: unsigned and forged ones get 401, `ping` 200, one about an unlisted pull request reaches no session. A `closed` (merged) delivery turned the open session page and the project's list to "merged just now" without a reload, and D1 followed; `ready_for_review` and `enqueued` moved a draft to open and queued. With the app's credentials refused by GitHub, lookups failed and payloads stood in, as intended; check deliveries changed nothing then. The hourly job looked up only the pull request nobody had asked about. ## Screenshots On the dev stack with seeded pull requests, at e1c7383 (v0.6.0, pinned since, has the same colors). The project's sessions, light, 1440 px: | Before | After | |---|---| |  |  | The same, dark:  A session's header and Pull requests section, light: | Before | After | |---|---| |  |  | Dark, a merged and a closed pull request, and the details sheet on a phone (390 px):  <img alt="After, dark, phone: the details sheet lists the pull requests with their state and checks first" src="https://app.fctl.sh/api/v1/published/ejBGb92yb2ZDCB0igk5MoSigifNUcrcfm3rXYTjtgzB/after-phone-dark.png" width="390"> Refs #8: the same deliveries can wake agents when CI fails or a review comes in. Made in the fctl session https://app.fctl.sh/s/ses_01M5FGTZXMEJVX5CD3MRXQ6XPB --------- Co-authored-by: fctl-sh[bot] <343971779+fctl-sh[bot]@users.noreply.github.com> Co-authored-by: fctl-sh[bot] <fctl-sh[bot]@users.noreply.github.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.
fctl's web app is starting to show each pull request of a session with its state and its checks, as GitHub reports them (hyperstructure/fctl#176). These tokens give it GitHub's own colors for that, so that people recognize the states at a glance:
pr-open#3fb950#3fb950pr-draft#9198a1#9198a1pr-queued#d29922#d29922pr-merged#ab7df8#ab7df8pr-closed#f85149#f85149pr-unknown#59636e#9198a1checks-passed#3fb950#3fb950checks-pending#d29922#d29922checks-failed#f85149#f85149They are not brand colors:
tokens.csssays they are only for pull requests and their checks. Onlypr-unknownchanges with the theme (muted text); the others are the same in both, as the person who asked for them specified.How I looked at it
npm run check,npm testandnpm run buildpass. The playground lists the new tokens with the others (playground/main.ts):Light:
Dark:
Merging releases it (a
feat: v0.6.0); hyperstructure/fctl#176 then pins that tag in place of this branch's commit.Made in the fctl session https://app.fctl.sh/s/ses_01M5FGTZXMEJVX5CD3MRXQ6XPB