Enable prettier prose wrapping for markdown

Add proseWrap: "always" to .prettierrc so markdown prose is
hard-wrapped at 80 columns.
This commit is contained in:
Jeffrey Paul 2026-02-23 00:00:25 +07:00
parent 818accc454
commit cb8d47d7aa
3 changed files with 91 additions and 65 deletions

View File

@ -1,3 +1,4 @@
{ {
"tabWidth": 4 "tabWidth": 4,
"proseWrap": "always"
} }

View File

@ -1,4 +1,7 @@
NetWatch is an MIT-licensed JavaScript single-page application by [@sneak](https://sneak.berlin) that provides real-time network latency monitoring to common internet hosts, displayed with color-coded figures and sparkline graphs, served from a static bucket or Docker container. NetWatch is an MIT-licensed JavaScript single-page application by
[@sneak](https://sneak.berlin) that provides real-time network latency
monitoring to common internet hosts, displayed with color-coded figures and
sparkline graphs, served from a static bucket or Docker container.
## Getting Started ## Getting Started
@ -22,27 +25,42 @@ docker run -p 8080:8080 netwatch
## Rationale ## Rationale
When debugging network issues, it's useful to have a persistent at-a-glance view of latency and reachability to multiple well-known internet endpoints. NetWatch provides this as a zero-dependency SPA that can be deployed anywhere static files are served, with no backend required. When debugging network issues, it's useful to have a persistent at-a-glance view
of latency and reachability to multiple well-known internet endpoints. NetWatch
provides this as a zero-dependency SPA that can be deployed anywhere static
files are served, with no backend required.
## Design ## Design
The application is a single-page app built with Vite and Tailwind CSS v4. All code lives in `src/main.js` with a class-based architecture: The application is a single-page app built with Vite and Tailwind CSS v4. All
code lives in `src/main.js` with a class-based architecture:
- **`CONFIG`**: Frozen configuration object (update interval, timeouts, axis ticks, etc.) - **`CONFIG`**: Frozen configuration object (update interval, timeouts, axis
- **`HostState`**: Per-host state management — history buffer, latency tracking, status transitions ticks, etc.)
- **`AppState`**: Top-level state container — WAN hosts, local hosts, pause state, aggregate stats - **`HostState`**: Per-host state management — history buffer, latency tracking,
- **`SparklineRenderer`**: Canvas 2D sparkline drawing with fixed axes, color-coded line segments, error regions, and DPR-aware scaling status transitions
- **UI functions**: `buildUI()` constructs the DOM, `updateHostRow()` / `updateSummary()` / `updateHealthBox()` handle incremental updates - **`AppState`**: Top-level state container — WAN hosts, local hosts, pause
- **`tick()`**: Main loop — measures all hosts in parallel via `Promise.all`, pushes samples, redraws UI. When paused, pushes blank markers (no probes, no false outage) state, aggregate stats
- **`SparklineRenderer`**: Canvas 2D sparkline drawing with fixed axes,
color-coded line segments, error regions, and DPR-aware scaling
- **UI functions**: `buildUI()` constructs the DOM, `updateHostRow()` /
`updateSummary()` / `updateHealthBox()` handle incremental updates
- **`tick()`**: Main loop — measures all hosts in parallel via `Promise.all`,
pushes samples, redraws UI. When paused, pushes blank markers (no probes, no
false outage)
### Monitoring targets ### Monitoring targets
- **9 WAN hosts**: Google Cloud Console, AWS Console, GitHub, Cloudflare, Azure, DigitalOcean, Fastly, Akamai, datavi.be - **9 WAN hosts**: Google Cloud Console, AWS Console, GitHub, Cloudflare, Azure,
- **1 Local host**: Local Gateway (192.168.100.1), tracked separately from WAN stats DigitalOcean, Fastly, Akamai, datavi.be
- **1 Local host**: Local Gateway (192.168.100.1), tracked separately from WAN
stats
### Latency measurement ### Latency measurement
HEAD requests with `mode: 'no-cors'` and `cache: 'no-store'`, timed with `performance.now()`. 1-second timeout; anything over 1000ms is clamped to unreachable. IPv4 only. HEAD requests with `mode: 'no-cors'` and `cache: 'no-store'`, timed with
`performance.now()`. 1-second timeout; anything over 1000ms is clamped to
unreachable. IPv4 only.
### Color coding ### Color coding
@ -72,31 +90,40 @@ dist/
- Summary stats: reachable count, min/max/avg latency across WAN hosts only - Summary stats: reachable count, min/max/avg latency across WAN hosts only
- Fixed chart axes: Y-axis 01000ms, X-axis 0300s - Fixed chart axes: Y-axis 01000ms, X-axis 0300s
- Color-coded latency figures and sparkline line segments - Color-coded latency figures and sparkline line segments
- Play/pause: pause stops probes but history keeps scrolling (blank gaps, no false outage) - Play/pause: pause stops probes but history keeps scrolling (blank gaps, no
false outage)
- Clickable service URLs - Clickable service URLs
- Canvas-based sparkline rendering with devicePixelRatio scaling - Canvas-based sparkline rendering with devicePixelRatio scaling
- Zero runtime dependencies: all resources bundled into build artifacts - Zero runtime dependencies: all resources bundled into build artifacts
## Deployment ## Deployment
After running `yarn build`, deploy the contents of the `dist/` directory to any static file host (S3, GCS, Cloudflare Pages, Vercel, Netlify, GitHub Pages) or use the Docker image behind a reverse proxy. After running `yarn build`, deploy the contents of the `dist/` directory to any
static file host (S3, GCS, Cloudflare Pages, Vercel, Netlify, GitHub Pages) or
use the Docker image behind a reverse proxy.
The Docker image: The Docker image:
- Listens on port 8080 by default (override with `PORT` env var) - Listens on port 8080 by default (override with `PORT` env var)
- Trusts `X-Forwarded-For` from RFC1918 reverse proxies (10/8, 172.16/12, 192.168/16) - Trusts `X-Forwarded-For` from RFC1918 reverse proxies (10/8, 172.16/12,
192.168/16)
- Sends access logs to stdout - Sends access logs to stdout
- Caches static assets with immutable headers - Caches static assets with immutable headers
## Browser Compatibility ## Browser Compatibility
Requires a modern browser with ES modules, Fetch API, Canvas API, and CSS custom properties. Requires a modern browser with ES modules, Fetch API, Canvas API, and CSS custom
properties.
## Limitations ## Limitations
- **CORS**: Some hosts may block cross-origin HEAD requests. The app uses `no-cors` mode which allows the request but provides opaque responses. Latency is still measurable based on request timing. - **CORS**: Some hosts may block cross-origin HEAD requests. The app uses
- **Local gateway**: The 192.168.100.1 endpoint requires the host to be accessible from your network. `no-cors` mode which allows the request but provides opaque responses. Latency
- **Network conditions**: Measurements reflect browser-to-endpoint latency, which includes your local network, ISP, and internet routing. is still measurable based on request timing.
- **Local gateway**: The 192.168.100.1 endpoint requires the host to be
accessible from your network.
- **Network conditions**: Measurements reflect browser-to-endpoint latency,
which includes your local network, ISP, and internet routing.
## TODO ## TODO

View File

@ -1,23 +1,22 @@
# Development Policies # Development Policies
- Docker image references by tag are server-mutable, therefore using them is - Docker image references by tag are server-mutable, therefore using them is an
an RCE vulnerability. All docker image references must use cryptographic RCE vulnerability. All docker image references must use cryptographic hashes
hashes to securely specify the exact image that is expected. to securely specify the exact image that is expected.
- Correspondingly, `go install` commands using things like '@latest' are - Correspondingly, `go install` commands using things like '@latest' are also
also dangerous RCE. Whenever writing scripts or tools, ALWAYS specify go dangerous RCE. Whenever writing scripts or tools, ALWAYS specify go install
install targets using commit hashes which are cryptographically secure. targets using commit hashes which are cryptographically secure.
- Every repo with software in it must have a Makefile in the root. Each - Every repo with software in it must have a Makefile in the root. Each such
such Makefile should support `make test` (runs the project-specific Makefile should support `make test` (runs the project-specific tests),
tests), `make lint`, `make fmt` (writes), `make fmt-check` (readonly), and `make lint`, `make fmt` (writes), `make fmt-check` (readonly), and
`make check` (has `test`, `lint`, and `fmt-check` as prereqs), `make `make check` (has `test`, `lint`, and `fmt-check` as prereqs), `make docker`
docker` (builds docker image). (builds docker image).
- Every repo should have a Dockerfile. If the repo contains non-server - Every repo should have a Dockerfile. If the repo contains non-server software,
software, the Dockerfile should bring up a development environment and the Dockerfile should bring up a development environment and `make check`
`make check` (i.e. the docker build should fail if the branch is not (i.e. the docker build should fail if the branch is not green).
green).
- Platform-specific standard formatting should be used. `black` for python, - Platform-specific standard formatting should be used. `black` for python,
`prettier` for js/css/etc, `go fmt` for go. The only changes to default `prettier` for js/css/etc, `go fmt` for go. The only changes to default
@ -25,27 +24,27 @@ docker` (builds docker image).
everything except `go fmt`). everything except `go fmt`).
- If local testing is possible (it is not always), `make check` should be a - If local testing is possible (it is not always), `make check` should be a
pre-commit hook. If it is not possible, `make lint && make fmt-check` pre-commit hook. If it is not possible, `make lint && make fmt-check` should
should be a pre-commit hook. be a pre-commit hook.
- If a working `make test` takes more than 20 seconds, that's a bug that - If a working `make test` takes more than 20 seconds, that's a bug that needs
needs fixing. In fact, there should be a timeout specified in the fixing. In fact, there should be a timeout specified in the `Makefile` that
`Makefile` that fails it automatically if it takes >30s. fails it automatically if it takes >30s.
- Docker builds should time out in 5 minutes or less. - Docker builds should time out in 5 minutes or less.
- `main` must always pass `make check`, no exceptions. - `main` must always pass `make check`, no exceptions.
- Do all changes on a feature branch. You can do whatever you want on a - Do all changes on a feature branch. You can do whatever you want on a feature
feature branch. branch.
- We have a standardized `.golangci.yml` which we reuse and is _NEVER_ to be - We have a standardized `.golangci.yml` which we reuse and is _NEVER_ to be
modified by an agent, only manually by the user. It can be copied from modified by an agent, only manually by the user. It can be copied from
`~/dev/upaas/.golangci.yml` if it exists at that location. `~/dev/upaas/.golangci.yml` if it exists at that location.
- When specifying images or packages by hash in Dockerfiles or - When specifying images or packages by hash in Dockerfiles or
`docker-compose.yml`, put a comment above the line and show the version `docker-compose.yml`, put a comment above the line and show the version and
and date at which it was current. date at which it was current.
- For javascript, always use `yarn` over `npm`. - For javascript, always use `yarn` over `npm`.
@ -54,11 +53,11 @@ docker` (builds docker image).
- Simple projects should be configured with environment variables, as is - Simple projects should be configured with environment variables, as is
standard for Dockerized applications. standard for Dockerized applications.
- Dockerized web services should listen on the default HTTP port of 8080 - Dockerized web services should listen on the default HTTP port of 8080 unless
unless overridden with the `PORT` environment variable. overridden with the `PORT` environment variable.
- The `README.md` is a project's primary documentation. It should contain - The `README.md` is a project's primary documentation. It should contain at a
at a minimum the following sections: minimum the following sections:
- Description - Description
- Include a short and complete description of the functionality and - Include a short and complete description of the functionality and
purpose of the software as the first line in the readme. It must purpose of the software as the first line in the readme. It must
@ -68,10 +67,10 @@ docker` (builds docker image).
- the category (web server, SPA, command line tool, etc) - the category (web server, SPA, command line tool, etc)
- the license - the license
- the author - the author
- eg: "µPaaS is an MIT-licensed Go web application by @sneak - eg: "µPaaS is an MIT-licensed Go web application by @sneak that
that receives git-frontend webhooks and interacts with a receives git-frontend webhooks and interacts with a Docker server
Docker server to build and deploy applications in realtime as to build and deploy applications in realtime as certain branches
certain branches are updated." are updated."
- Getting Started - Getting Started
- a code block with copy-pasteable installation/use sections - a code block with copy-pasteable installation/use sections
- Rationale - Rationale
@ -79,28 +78,27 @@ docker` (builds docker image).
- Design - Design
- how is the program structured? - how is the program structured?
- TODO - TODO
- This is your TODO list for the project - update it meticulously, - This is your TODO list for the project - update it meticulously, even
even in between commits. Whenever planning, put your todo list in in between commits. Whenever planning, put your todo list in the
the README so that a separate agent with new context can pick up README so that a separate agent with new context can pick up where you
where you left off. left off.
- License - License
- GPL or MIT or WTFPL - ask the user when beginning a new project - GPL or MIT or WTFPL - ask the user when beginning a new project and
and include a LICENSE file in the root and in a section in the include a LICENSE file in the root and in a section in the README.
README.
- Author - Author
- @sneak (link `@sneak` to `https://sneak.berlin`). - @sneak (link `@sneak` to `https://sneak.berlin`).
- When beginning a new project, initialize a git repo and make the first - When beginning a new project, initialize a git repo and make the first commit
commit simply the first version of the README.md in the root of the repo. simply the first version of the README.md in the root of the repo.
- For Go packages, the module root is `sneak.berlin/go/...`, such - For Go packages, the module root is `sneak.berlin/go/...`, such as
as `sneak.berlin/go/dnswatcher`. `sneak.berlin/go/dnswatcher`.
- We use SemVer always. - We use SemVer always.
- If no tag `1.0.0` or greater exists in the repository, modify the existing - If no tag `1.0.0` or greater exists in the repository, modify the existing
migrations and assume no installed base or existing databases. If migrations and assume no installed base or existing databases. If `>=1.0.0`,
`>=1.0.0`, database changes add new migration files. database changes add new migration files.
- New repos must have at a minimum the following files: - New repos must have at a minimum the following files:
- `README.md`, `.git`, `.gitignore` - `README.md`, `.git`, `.gitignore`