Repository navigation
feat(core): add a "not checked yet" state to the public status contract - #109
Conversation
A configured site with no real result and no previous summary used to be served as `down`, showing an outage nobody observed (a new plugin monitor before its shard runs, a webhook-only Sentry monitor before its first event). The contract now marks such a site with an optional `pending: true` beside a placeholder `status: "down"`, staying on CONTRACT_VERSION 1: consumers that predate the field strip the unknown key and read `down` as before, whereas a new `status` enum value would fail their schema. The key is omitted for checked sites, so existing bodies are byte-identical. - buildSiteSummaries marks the site pending until its first real result - the overall severity and overall badge ignore pending sites - detectStatusChanges does not compare against a pending summary, so the first real result sends no notification (worker ingest; the plugin never had a prior result) - site, uptime and response-time badges read "not checked" in grey - emdash-ui SiteDetail and apps/web StatusList label the site "Not checked yet" (new i18n `status.notChecked` in every locale) Closes #103
There was a problem hiding this comment.
Code Review
This pull request introduces a "pending" state for monitors that have not been checked yet, ensuring they are styled neutrally as "Not checked yet" rather than showing an outage. It updates the core status rollup, badge generation, and notification logic to ignore pending sites, and adds comprehensive test coverage across the packages. The reviewer pointed out a medium-severity issue where a pending site might misleadingly display a "100%" 90-day uptime due to an empty history, suggesting it be set to "—" instead.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
The workerd host suites are test files, but only *.test.ts and *.spec.ts were carved out of sources, so new lines in public.host.ts counted as uncovered source and failed the new-code coverage gate.
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
[Medium impact] The PR appears safe to merge; no new actionable issues were found. SummaryAdds
Reviews (3) · Last reviewed commit: "fix(core): name a timeline with no uptim..." · Reviewed by Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4207aa58a1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
cubic analysis
All reported issues were addressed across 22 files
Linked issue analysis
Linked issue: pleaseai/statusbeam#103: feat(core): add a "not checked yet" state to the public status contract
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Serve unchecked monitors as pending with matching standalone and plugin bodies. | Core marks monitors without results pending, and route tests compare plugin responses with standalone responses. |
| ✅ | Exclude pending monitors from the overall status. | overallSiteSeverity skips pending sites, and contract tests cover mixed and all-pending summaries. |
| ✅ | Show a text label for pending status in both renderers. | SiteDetail and StatusList render the translated “Not checked yet” label with neutral styling. |
| ✅ | Update the contract compatibly for existing consumers. | The optional pending key preserves version 1, and a legacy-schema test confirms old consumers still parse it. |
| ✅ | Update the plugin test for an unprobed monitor. | The existing expectation now checks that an unprobed monitor has pending true beside its placeholder down status. |
Architecture diagram
sequenceDiagram
participant CB as Check Badge
participant CBH as Check Badge Handler
participant WFE as Worker Fetcher
participant WEE as Worker Entrypoint
participant CV as Cloudflare Variant
participant WR as Worker Runtime
participant FA as Fetcher Adapter
participant S3 as S3 Bucket
participant D1 as D1 Database
participant RR as Rollup Routine
participant KVR as KV Read
participant R2A as R2 Access
participant SA as Static Assets
participant KVW as KV Write
participant F as Fetcher
participant R2C as R2 Cache
participant R as Runtime
participant I as Isolate
Note over CB,CBH: Check Badge Handler
CB->>CBH: Evaluate Worker Fetch
CBH->>WFE: Handle Worker Fetch
Note over WFE,WEE: Worker Fetcher
WFE->>WEE: Evaluate Entrypoint
Note over WEE,CV: Worker Entrypoint
WEE->>CV: Convert to Cloudflare
CV->>WR: Start Runtime
WR->>FA: Wrap Fetcher
Note over FA,S3: Fetcher Adapter
FA->>S3: Upload to S3
S3-->>FA: S3 Result
Note over FA,RR: Fetcher Adapter
FA->>RR: Upload to Rollup
Note over RR,D1: Rollup Routine
RR->>D1: Query Database
D1-->>RR: Database Result
RR->>KVR: Read from KV
KVR-->>RR: KV Value
RR->>R2A: Access R2
R2A-->>RR: R2 Value
Note over RR,SA: Rollup Routine
RR->>SA: Read Static Assets
RR->>KVW: Write to KV
Note over KVW,R2C: KV Write
KVW->>R2C: Cache in R2
Note over R2C,F: R2 Cache
R2C->>F: Fetch Data
Note over F,R: Fetcher
F->>R: Run
Note over R,I: Runtime
R->>I: Start Isolate
I-->>R: Isolate Result
R-->>F: Runtime Result
F-->>R2C: Fetch Result
R2C-->>KVW: Cache Result
KVW-->>RR: Write Result
RR-->>FA: Rollup Result
FA-->>WR: Adapter Result
WR-->>CV: Runtime Result
CV-->>WEE: Cloudflare Result
WEE-->>WFE: Entrypoint Result
WFE-->>CBH: Worker Result
CBH-->>CB: Handle Result
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
… uptime A snapshot written before `pending` existed stored a never-checked site as an unflagged `down`/`0`. After an upgrade, a webhook-only Sentry monitor kept that false outage until its first event. The standalone ingest now marks such an entry pending when the site has no row in `checks` at all, before change detection runs, so its first real result is not reported as a change. D1 never deletes check rows, so a real `down` keeps its status however old it is. The renderers showed a pending site's empty history as 100% uptime. The emdash-ui SiteDetail and apps/web StatusList now show an em dash for every uptime window of a pending site, as the uptime badge already does. `SiteSummary.pending` and the contract schema now accept only `true`, so a checked site cannot publish `pending: false`.
|
/gemini review |
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
The 90-day timelines in apps/web and emdash-ui built their accessible name from the window uptime, which reads an empty or all-no-data history as 100%, so a site not checked yet was announced as "100% uptime". A new core helper, `uptimeFigure`, returns the figure a renderer shows, or `null` for a pending site or a window with no checked day. The row header, its breakdown and both timelines now use it, so the visible figure and the accessible name always agree. A timeline with no figure is named by the new `timeline.ariaNoData` label, translated in every locale.
|



Summary
A configured site with no real result and no previous summary was served as
down, so the public page and badges showed an outage nobody observed. This happened for a new EmDash plugin monitor until its shard first ran, and in both distributions for a webhook-only Sentry monitor until its first event.This PR adds an explicit "not checked yet" state to the public contract:
@statusbeam/core/contract).SiteSummary, thesites[]entries ofstatusResponseSchema, andsiteResponseSchemagain an optional booleanpending. A site that has not been checked yet is served with"pending": truenext to a placeholder"status": "down". For every other site the key is omitted.buildSiteSummariesmarks a site pending when it has no result and no previous summary. It stays pending while its previous summary is pending, and its first real result clears it. The plugin'ssummariesFromSnapshotgets this through core, so standalone and the plugin produce the same bodies.overallSiteSeverityskips pending sites; when every site is pending the result isoperational.buildStatusResponse,overallBadgeand theapps/webpage banner all use it.detectStatusChangesdoes not compare a result against a pending summary, so the first real result sends no notification and does not purge the cache. This matters in the standalone worker ingest path, which reads the previousSiteSummaryfrom KV. The plugin's run and webhook paths compare against real results only, so they already behaved this way; new tests now cover both.not checkedinlightgrey, notdownin red or100%in green.@statusbeam/emdash-uiSiteDetailandapps/webStatusListshow a pending site with a text label ("Not checked yet"), a clock icon (emdash-ui), and a neutral tone, so color is never the only signal (NFR-4). A newstatus.notCheckedi18n entry has translations for every locale.Related issue
Closes #103
Backward compatibility: why
pendingand not a newstatusvalueCONTRACT_VERSIONstays1. Existing consumers (the published@statusbeam/emdash-uiandapps/web) parse bodies with zod objects. These strip unknown keys but treatstatusas the closed enumup | degraded | down. A new value such as"not_checked"would fail that enum, and the old client would then report the whole source as unavailable. An optionalpendingkey is stripped silently, so an old consumer reads"down"exactly as it does today, and a new consumer reads the new state. Because the key is omitted for checked sites, bodies for checked sites stay byte-identical to the current ones. A contract test pins this with a pre-pendingschema.Tests
downpackages/core/src/rollup.test.ts› buildSiteSummaries pending statepending; overall ignores pending (all pending → operational)packages/core/src/contract.test.ts› a site not checked yetpendingconsumer schema still parses the body asdowndetectStatusChangesignores a pending priorpackages/core/src/rollup.test.ts› detectStatusChangesapps/worker/src/sentry-webhook.handler.test.tspackages/emdash-plugin/tests/notify.test.ts› status-change notificationspackages/emdash-plugin/tests/webhooks.test.ts› an accepted webhook eventpending(updated expectation, wasdown)packages/emdash-plugin/tests/notify.test.ts› leaves a monitor whose only probes hit the limit out…not checked/lightgrey; overall badge ignores pendingpackages/core/src/badge.test.ts› badges for a site not checked yetpackages/emdash-plugin/tests/public.host.ts› serves a monitor not checked yet as pending…packages/core/src/i18n.test.tsNeither
@statusbeam/emdash-uinorapps/webhas component render tests, so the renderer changes are covered byastro checkand typecheck only.Gate results
bun run build: 7/7 tasks successfulbun run typecheck: 11/11 tasks successfulbun run lint:eslint .cleanbun test: 513 pass, 0 fail (36 files)bun run --cwd packages/emdash-plugin test:host: 4 files, 28 tests passedChecklist
bun run test)bun run lint)Summary by cubic
Adds a "not checked yet" state to the public status contract so configured sites with no real check result are no longer served as
down. New EmDash monitors before their shard first runs, and webhook-only Sentry monitors before their first event, now readpending: truebeside the placeholderdown, and public pages and badges show "Not checked yet" instead of a phantom outage.The change stays on
CONTRACT_VERSION1: old consumers strip the unknown key and readdownas before, while a new status enum value would fail their closed schemas. The schema accepts onlypending: true, neverfalse, and the key is omitted for checked sites, so existing bodies are byte-identical.Contract and rollup
buildSiteSummariesmarks a site pending while it has no result and no previous summary; a real result clears it, and the plugin'ssummariesFromSnapshotgoes through core so both distributions serve identical bodies.down/0as pending when the site has nochecksrow, so its first real result after an upgrade is not reported as a change.overallSiteSeverityskips pending sites in the aggregate status, overall badge, andapps/webbanner; all-pending resolves tooperational.detectStatusChangesignores pending prior summaries, so the first real result sends no notification and does not purge the cache.Rendering and badges
not checkedinlightgrey.@statusbeam/emdash-uiSiteDetailandapps/webStatusListshow a "Not checked yet" label, clock icon, and neutral tone, so color is never the only cue; a pending site's uptime windows read—instead of 100%.uptimeFigurehelper drives every uptime figure: pending or all-no-data histories yield none, so the row header, its breakdown, and both 90-day timelines agree on the visible value and its accessible name (timeline.ariaNoData), never announcing "100%" for an empty window.status.notCheckedi18n entry covers every locale, and the README and EmDash guide document the state.*.host.tssuites as tests so the new parity coverage inpublic.host.tsno longer counts against the new-code gate.Written for commit adcca62. Summary will update on new commits.