# Development FlareWatch uses Vite+ (`vp`) for installs, checks, tests and builds. ```bash vp install vp check # format, lint and types vp run test # unit tests vp run --filter status-page doctor # React Doctor: fails on any finding vp run build # both Workers ``` Browser tests start two local status pages, one public and one private: ```bash cd apps/status-page && vp exec playwright test ``` FlareWatch and flarewatch-proxy share three fixture files. The proxy keeps them in `cmd/flarewatch-proxy/testdata`, and each copy here must match it byte for byte. A change to a file, or to the code it tests, needs the same change in both repos. | File | Copy in | Says | Run by | | ---------------------- | -------------------------------- | --------------------------------------- | ------------------------------------------------- | | `http-assertions.json` | `packages/shared/tests/fixtures` | which error an HTTP reply gives | `packages/shared/tests/http-assertions.test.ts` | | `requests.json` | `packages/shared/tests/fixtures` | which monitors the proxy accepts | `packages/shared/tests/proxy-requests.test.ts` | | `verdicts.json` | `services/worker/tests/fixtures` | how a check ends against a given target | `services/worker/tests/checkers/verdicts.test.ts` | The last two tests each hold a short table of the cases where FlareWatch and the proxy differ, with the reason. The verdicts test runs the Worker's own check in a local workerd, started with wrangler's `unstable_startWorker`. wrangler's test harness would send the Worker's requests out through Node, and Node handles redirects and gzip differently. ## Layout | Path | Holds | | ------------------------ | ------------------------------------------------------------------------------------------------------ | | `services/worker` | The monitor Worker: checks, the hub, alerts, heartbeat pings. | | `apps/status-page` | The status page Worker, a TanStack Start app. | | `packages/config` | The user's config: monitors, alerts, page settings. | | `packages/shared` | Types and helpers both Workers use. Tests import its test helpers from `@flarewatch/shared/testing/*`. | | `packages/vitest-config` | The Vitest options and setup file every package's tests use. | | `.github/workflows` | CI and the deploy workflow. | The monitor Worker keeps incidents, latency, heartbeat pings and maintenance windows in the hub, a SQLite Durable Object in `services/worker/src/hub`. The status page reads and edits it through its `MONITOR_WORKER` service binding. KV (`FLAREWATCH_STATE`) holds sign-in sessions. ## Run it locally Run both Workers in one process, the status page first: ```bash vp run status-page-build cp apps/status-page/.dev.vars.example apps/status-page/.dev.vars vp exec --filter status-page -- wrangler dev --local --config dist/server/wrangler.json --config ../../services/worker/wrangler-dev.toml --port 3000 --persist-to .wrangler/state ``` Open . The first page load starts a check run, and data shows a few seconds later. For UI work alone, `vp run dev-status-page` is faster, but without the monitor Worker it shows no data. ## Lint rules Linting includes the anti-slop rules in `tools/oxlint/anti-slop/`, from [dmmulroy/anti-slop](https://github.com/dmmulroy/anti-slop) plus a few local ones. They live with their tests in another repository, so don't edit the copy here. If a rule misfires, open an issue. `vite.config.ts` lists the rules that run. `@shadcn/lint` checks how pages use the components in `apps/status-page/src/components/ui`. Each of its rules is an error. When a page needs a style a component doesn't offer, add a variant to the component instead of passing the class through `className`. Every rule also runs inside `components/ui`, with two exceptions that `vite.config.ts` lists by file name. A few composites, such as the calendar, skip `no-restyle` because they build on other primitives. Three files skip `require-static-classes` because they pass on their own `cva()` output, which the rule can't read. A new file gets every rule, so an odd value goes into `styles.css` as a theme token. Oxlint's type-aware `no-unsafe-*`, `no-floating-promises` and `no-misused-promises` checks run as errors, tests included. Fix findings by restructuring first. A type assertion that survives review needs a `SAFETY:` comment stating the checked invariant. ## SSR and hydration safety The status page renders on the server, then React hydrates it in the browser. Different output on the two sides causes hydration errors such as minified `#418`. Keep server output deterministic: - Don't render `Date.now()`, `new Date()`, `Math.random()` or locale formatting during SSR. - Take one "now" from the loader or the state, such as `state.lastUpdate`, and pass it down. - Use `formatPageTime` or `formatPageDate` in `src/lib/date.ts` for displayed timestamps. Calendar day labels use `formatUtc`. Date-entry fields stay in UTC. - Only `formatDateTime` in `packages/shared/src/utils.ts` can read numeric locale parts for the configured page zone. It builds all text in code. - Do not import `@flarewatch/config` in client code: its groups name private monitors. `vite.config.ts` puts only the zone in the bundle as `__PAGE_TIMEZONE__`, and `src/lib/page-zone.ts` holds it. - Gate browser-only code (`localStorage`, `window`) on `useHydrated()`. ## Changelog A pull request that changes behavior adds an entry to `CHANGELOG.md` under the next version. Write it for someone upgrading. ## Release 1. Look for implicit state machines: flags that gate each other, nullable columns that mean a state, and rules spread across SQL statements. The checks only protect machines that already exist, so finding a new one takes reading the code. [ADR 0003](docs/adr/0003-lifecycles-are-explicit-state-machines.md) says what counts and what doesn't. Do this before every minor or major release. 2. If the architecture changed, update `docs/assets/how-it-works.svg`. 3. Rename `## Unreleased` in `CHANGELOG.md` to `## X.Y.Z - YYYY-MM-DD` and merge that to main. The merge is the release. When CI and Deploy passes on main and the newest dated section of `CHANGELOG.md` has no tag, the Release workflow tags that commit `vX.Y.Z`, publishes the section as the GitHub release "FlareWatch X.Y.Z" and rebuilds flarewatch.app. If CI and Deploy fails, fix it on main: the next passing run releases. Don't push a release tag by hand: the workflow skips a version that already has a tag. ## Deploy by hand GitHub Actions deploys normally (see [docs/deploy.md](docs/deploy.md)). To deploy from your machine: ```bash vp exec --filter worker -- wrangler kv namespace create flarewatch-state ``` Put the namespace ID in place of `__FLAREWATCH_STATE_KV_NAMESPACE_ID__` in `apps/status-page/wrangler.jsonc`, then deploy: ```bash vp exec --filter worker -- wrangler deploy --config wrangler.toml vp run --filter status-page build vp exec --filter status-page -- wrangler deploy --config dist/server/wrangler.json ``` The status page deploys from `dist/server/wrangler.json`, which the build generates from `wrangler.jsonc`.