Add a --host option to both dev-server.py entry points so the dev
server can be reached from other devices on the LAN (e.g. a headless
Raspberry Pi behind a firewall). Default stays 127.0.0.1 (localhost);
pass --host 0.0.0.0 (or ::) to expose it. When bound to a wildcard,
the script also prints the machine's LAN address to open.
Both scripts move to argparse, so --help documents the options and bad
input errors cleanly instead of tracebacking. The positional [port] is
unchanged for backwards compatibility (python3 dev-server.py 8080).
web/dev-server.py previously bound "" (all interfaces) by default; it
now defaults to 127.0.0.1 to match the safe-by-default principle and
the other entry point. Use --host 0.0.0.0 for the old behavior.
stdout is flushed before serve_forever() so the startup lines (and the
LAN hint) land immediately in a redirected log.
README documents the new option.
Auto mode generated fins as one flat triangle soup in a single mesh, so a
user who wanted fewer had to scrap all of them and redraw the ones they
wanted by hand. Now each fin carries its triangle segment(s) so you can
drop just the one you don't want.
Data model (additive): every generator already had a `before = out.length`
hook, so each fin record now carries `triRanges` (vertex-indexed segments
into built.triangles), an `id`, a `kind` ('prop' | 'wedge'), and its
contact `line`. `buildFins` assembles a unified `fins` array where each
entry owns its geometry. The merged `built.triangles` is byte-identical.
UI (app.js + index.html): a "Remove fins" button (Auto mode, fins on)
arms a click-to-remove mode -- hover highlights a fin red, click drops
just that one. "Restore all" + undo bring it back. Removals are keyed by
a spatial signature (rounded line midpoint + bearing + height + kind),
not the volatile sequential id, so a fin stays removed across a same-
orientation rebuild (slider tweak) and survives an orientation round-trip.
Export needs no change: `finTris` is now the filtered set, so
`activeAdded()` / stl.js / threemf.js serialize it as-is and the grams
receipt + export triangle count drop with each removal.
Undo/redo snapshots `removedSigs` alongside pose/walls, so each removal
and each restore is one undo step.
Tests: new tests/fins.test.js pins that triRanges are in-bounds, non-
overlapping, cover every fin triangle exactly once, that each fin is a
watertight closed solid in isolation, and that filtering out one fin
excludes exactly its triangles and keeps the rest watertight. Existing
geometry suite unchanged (records only gain additive fields).
The web app is a buildless static SPA (three.js is vendored in web/), so the
deploy image is just nginx serving web/ on :80. This adds:
- Dockerfile: nginx:stable-alpine, copies web/ + LICENSE, OCI labels.
- nginx.conf: port 80 site config that mirrors web/_redirects
(/index.html and /index 301 -> /) via a $request_uri guard that cannot
loop with the `index` directive's internal redirect; cache policy mirrors
dev-server.py (no-store on the app's own JS/CSS/HTML) while long-caching
the vendored three.js tree (immutable per image tag); absolute_redirect
off so the redirect stays relative behind a reverse proxy.
- docker-compose.yml: one `web` service, host 8731:80 so the URL matches
`python3 dev-server.py`, with a wget healthcheck.
- .dockerignore: excludes prototype/, plugin/, tests/, docs/, the dev
servers and model files so the build context is just the web artifact.
- README: "Run with Docker" block pointing at the new files.
Validated: image builds, `nginx -t` passes, GET / -> 200 no-store,
GET /index.html|/index -> 301 relative /, GET /vendor/... -> immutable
long-cache, no redirect loop on /.