PulseStatus is a static site. Configuration is read during the data build and published as JSON/JavaScript under public/data/; the browser never reads config/statuspage.yml directly.
Keep source configuration in config/ and keep generated browser data in public/data/. Do not edit generated JSON by hand: the next synchronization or build will replace it. A useful ownership split is:
- Operations owns
config/services.json, monitoring thresholds, trusted maintainers, incident labels, and maintenance data quality. - Brand or web owners own the
sitesection, favicon asset,src/styles/custom.css, and localized service text. - Contributors own page behavior, tests, and the generated data pipeline.
Every configuration change should be checked with npm run validate-config, npm run typecheck, npm test, and npm run build.
Edit config/statuspage.yml:
| Key | Required | Description |
|---|---|---|
site.name |
no | Name shown in the browser title and header. Defaults to PulseStatus. |
site.locale |
no | Publishing locale metadata. Browser language selection currently supports English and Chinese. |
site.timezone |
no | Operational timezone metadata. Daily uptime remains UTC so reports are deterministic. |
site.favicon |
no | File path relative to public/. Defaults to favicon.png. |
site.theme |
yes | Administrator-selected built-in template: modern, compact, vivid, or mono. |
site.companyName |
no | Company or organization name shown in the footer. |
site.companyUrl |
no | Optional link for the company name. |
site.headerText |
no | Optional text displayed below the global header. |
site.footerText |
no | Optional operational or legal text in the footer. |
site.footerLinks |
no | YAML list of { label, url } links shown in the footer. |
Example:
site:
name: Acme Service Status
locale: en
timezone: America/Los_Angeles
theme: modern
favicon: brand/acme-favicon.svg
companyName: Acme Inc.
companyUrl: https://acme.example
headerText: Live availability and incident communication
footerText: Maintained by the Acme Operations team.
footerLinks:
- label: Support
url: https://acme.example/support
- label: Privacy
url: https://acme.example/privacyPlace brand/acme-favicon.svg at public/brand/acme-favicon.svg. Do not use a filesystem path outside public/.
The build emits this configuration as public/data/site.js. Each HTML entry point loads it before its page module, allowing the same identity and footer to appear on overview, history, incident, and service pages. If site.js is missing in a local build, run npm run build-data.
The theme is a deployment setting, not a visitor preference. All visitors receive the same site.theme value. For organization-specific styling, edit src/styles/custom.css; it is bundled after the built-in stylesheet and is therefore the supported override layer.
Edit config/services.json. Each service must have a stable unique id; the ID is used in GitHub labels (service/<id>), generated data, service URLs, and uptime records. nameI18n and descriptionI18n can provide localized text. monitorMode can be scheduled, manual, or hybrid.
Service cards link to:
service.html?id=<id>
The detail page filters incidents and maintenance by that same ID. Renaming an ID is therefore a data migration: update labels, existing issue labels, and any links together.
Recommended service configuration:
{
"id": "api",
"name": "Public API",
"nameI18n": { "en": "Public API", "zh": "公共 API" },
"group": "Core services",
"description": "The API used by public clients.",
"descriptionI18n": {
"en": "The API used by public clients.",
"zh": "供公共客户端使用的 API。"
},
"publicUrl": "https://api.example.com",
"checkUrl": "https://api.example.com/health",
"monitorMode": "scheduled"
}Use publicUrl for a human-facing link and checkUrl only for a bounded health check. Services with monitorMode: manual are never automatically probed, even when checkUrl or checkIntervalMinutes is present. They do not increment failure/recovery counters and cannot trigger or automatically close an Incident. Manual services can omit checkUrl; they should not be presented as probe-verified.
The GitHub Actions workflow runs every five minutes. monitoring.probeIntervalMinutes sets the default number of minutes between real requests:
monitoring:
probeIntervalMinutes: 5Individual services can override it:
{
"id": "docs",
"monitorMode": "scheduled",
"checkUrl": "https://docs.example.com/health",
"checkIntervalMinutes": 10
}The check script persists lastProbeAt in public/data/monitor-state.json. When an interval has not elapsed, it reuses the last result and does not increment consecutive failure or recovery counters. Counters therefore represent real requests, not workflow invocations. Values below five minutes cannot create more checks with GitHub-hosted schedules; use a dedicated external monitor for sub-five-minute requirements.
Every real request also appends a record to public/data/probe-history.json. Existing records are not edited when a new check runs. Each record contains checkedAt, the resulting service status, optional statusCode, and optional latencyMs; manual services are intentionally absent from this history. The service detail page displays the most recent probe records separately from incident-derived uptime.
Incidents come from valid GitHub Issues with:
statuspage/incident- at least one known
service/<id> - exactly one
severity/minor,severity/major, orseverity/critical
Maintenance comes from Issues with statuspage/maintenance, at least one known service label, and parseable Starts at, Ends at, and optional Timezone fields. The synchronizer writes incidents-all.json, incidents-active.json, and maintenance.json.
The maintenance parser accepts labels such as Starts at: and Ends at: followed by ISO timestamps. Always include an explicit offset:
Starts at: 2026-11-01T02:00:00+08:00
Ends at: 2026-11-01T03:30:00+08:00
Timezone: Asia/Singapore
Invalid windows are skipped with a warning. A closed maintenance Issue is exported as completed; a state/cancelled label is exported as cancelled.
Automatic publication is opt-in through config/statuspage.yml:
monitoring:
autoPublishIncident: true
failuresToIncident: 3
recoverySuccessesToCloseIncident: 2
issues:
autoIncidentMentions: [PythonSmall-Q]
autoIncidentSeverity: majorThe check-services step writes the current service result. The auto-publish-incidents step then:
- Loads the previous per-service counters from
public/data/monitor-state.json. - Increments consecutive failures for a
downmonitored service and resets its success counter. - Creates one Issue after
failuresToIncidentfailures, withstatuspage/incident,service/<id>, the configured severity, andstate/investigating. - Adds each configured username as an
@mentionin the Issue body. - Records an HTML marker containing the service ID so later runs do not create duplicates.
- Increments consecutive successes after recovery and closes the generated Issue after
recoverySuccessesToCloseIncidentsuccessful checks.
Automatic Issues are deliberately normal GitHub Issues, not fabricated browser data. Maintainers should verify customer impact, add context, and close or adjust the Issue as appropriate. Setting autoPublishIncident: false prevents new automatic Issues and prevents the automation from closing existing ones.
The workflow commits only the monitor counter file with [skip ci], preventing a state persistence commit from recursively starting another workflow. It requires:
permissions:
contents: write
issues: writeIf repository policy disallows workflow pushes, use an external durable state store or disable automatic publication; without persisted counters, consecutive-failure behavior cannot be guaranteed across scheduled runs.
buildDailyUptime() calculates each UTC day independently. It finds incidents mentioning a service, clips each interval to the day, sorts and merges overlapping intervals, and subtracts the union of downtime from the elapsed day. Resolved incidents use closedAt (falling back to updatedAt); active incidents extend to the generated snapshot timestamp.
The output is rounded to three decimal places and capped between 0 and 100. It is intentionally incident-derived and should not be described as synthetic monitoring coverage.
npm run validate-config
npm run sync-issues # requires GitHub Actions environment variables
npm run check-services
npm run build-data
npm run buildbuild-data reads the synchronized files and generates summary, incident history, site configuration, and 90 daily uptime records. A failed synchronization must stop the workflow rather than publish a false healthy state.
The publishing job needs permission to read Issues and write the Pages artifact. The synchronization step requires GITHUB_TOKEN, GITHUB_REPOSITORY_OWNER, and GITHUB_REPOSITORY; service configuration is supplied through the repository's existing environment variables. Keep PULSE_TRUSTED_MAINTAINERS restricted to maintainers who are allowed to publish operational updates.
If the repository uses a project Pages URL, pass the repository path as VITE_BASE_PATH. Verify links from the deployed subdirectory rather than only from http://localhost:5173/.