DEVELOPMENT.md
Rama main · réplica de lectura del repositorio original.
# 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 <http://localhost:3000>. 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`.