Rename Deployments to Provisioning with compatible redirects and APIs. Add durable Start, Restart, Stop, and Rebuild operations alongside Create and Destroy; rebuild wipes the instance and recreates it in the same slot and port.
Add lifecycle, migration, recovery, and Komodo command tests. Type checks and production build pass; the full suite retains a telemetry assertion failure reproduced on the unchanged branch.
Refs #39
Add an optional exact blueprint mode with keep/create/remove previews, atomic queueing, cancellation of unstarted excess creations, and persistent dependencies for safe port reuse. Track removals alongside creations and preserve fill mode as the default.
Validation: type check, production build, and 103 deployment tests passed. The full suite has one telemetry assertion failure also reproduced on the previous commit.
Fixes#42
Replace saved-key inputs with a ready indicator and direct key updates to Keys. Clear hidden key overrides on preview refresh and update the blueprint documentation.
Fixes#41
Implement the approved UX review across Fleet, Betas, Library, deployments, and Settings. Add reviewed promotion and reconciliation plans, atomic queued-removal cancellation, recoverable beta drafts and launch inputs, accessible controls, and mobile improvements.
Validated with 473 tests, a clean typecheck, production build, and isolated desktop/mobile browser walkthroughs. Include the review and implementation coverage notes.
read/ListTags with an empty body was returning something we couldn't
parse, so every tag lookup silently missed and the CreateTag duplicate
fallback dead-ended with no diagnostic.
Now the tag lookup tries two request styles in order — the per-endpoint
`read/ListTags {}` used by the rest of this file, then the tagged-union
`read {type: "ListTags", params: {}}` used by mbecker20/komodo's
canonical API. If both fail to yield a match, both raw response bodies
get logged so the actual Komodo shape is diagnosable on the next run
without another commit-and-deploy cycle.
Two real-world Komodo responses broke the first pass:
1. read/ListTags didn't surface tag "t5" in a matchable shape, so we
fell through to CreateTag and got HTTP 500 "duplicate key: t5".
2. write/CreateTag returned 200 with an id field named "id" (not "_id"),
which the parser rejected as an unexpected shape even on success.
Fixes:
- extractTagId accepts either "id" or "_id"
- unwrapTagList peels back common list wrappers (bare array, {tags},
{data}, {results}, {items}) instead of requiring a top-level array
- CreateTag duplicate-key error triggers a re-list so the existing tag
can be resolved to its id instead of failing the whole tag pass
- Both ListTags and CreateTag branches log the raw body when parsing
fails, so the next unfamiliar shape is diagnosable at a glance
New deployments get two tags attached (e.g. "t5" and "zombie_pentagon")
so operators can filter the Komodo deployment list by game and by map.
Tags are ensured via read/ListTags + write/CreateTag fallback, then
attached with write/UpdateTagsOnResource against the deployment name.
Tag application runs fire-and-forget after CreateDeployment: any failure
logs a warning and the server creation proceeds. Tags are cosmetic
organization inside Komodo, not a correctness requirement.
The adapter treats "0" as "block anything > 0 bytes" rather than
"unlimited" — SvelteKit's own error message says: "To disable body size
limits, specify Infinity rather than 0." Previous commit set "0", which
kept the 413 in place.
SvelteKit's Node adapter caps request bodies at 512KB by default, which
rejects real-world plugin .dll uploads with HTTP 413. Same trust
boundary as the CSRF disable (LAN-only), so 0 = unlimited is fine.
Restraint-first design language: neutral gray surfaces + one blue accent
(cyan/amber/red reserved for state), 8pt spacing, softer radii, one
ambient + one raised shadow, standardized motion tokens. Alfa Slab One
demoted to BrandMark only; Inter carries all page copy.
New primitives (Button, Card, Badge, Stat, SectionHeader, Skeleton,
EmptyState, Tabs, Breadcrumbs, StatusPip, Field, Input, Select, Textarea)
plus motion utilities (tweenNumber, viewTransition) and a global toast
system. All eight route surfaces rewritten on top: Fleet gains an
overview hero, Server detail restructures into tabs, Deployments gets a
collapsible drift summary + per-server pipeline visual, and the
scripts/configs/plugins/betas flows adopt the new form primitives.
Sticky translucent nav with backdrop blur, view-transition cross-fades
between routes, staggered card entrance on Fleet, breathing status pip
halo, tabular-numeric player counts. Gmail-style g-key nav shortcuts.
Mobile stacks below 720px; prefers-reduced-motion neutralizes durations.
Legacy tokens preserved as aliases so any un-migrated code still renders.
games_sp.log is not wiped on restart, so old J; lines from prior
sessions were poisoning the anchor. Now the scanner walks the tail
backward to the LATEST InitGame and only considers lines after it,
returning an opaque cycle key (the first J; line) plus a state. The
worker stamps game_started_at/ended_at with Date.now() at observation
time instead of inferring from the log's own timestamps. Same-players
game restarts produce a new key and cleanly re-anchor. When the server
transitions to 0 players, game state is cleared server-side and the
UI also gates the timer display on activeSince.
Adds servers.game_cycle_key column with additive migration.
An idle map load emits InitGame in games_sp.log without anyone joining,
which was falsely surfacing the game timer on empty servers. Track the
first `J;<guid>;<slot>;<name>` line inside each InitGame cycle instead
and back-compute the wall-clock start from that line's in-game MM:SS.
Removes the coarse client-side activeSince gate since the scanner is
now the source of truth.
Fleet cards and the per-server detail hero now show three live-ticking
timers: Active (since players > 0), Inactive (since server emptied), and
Game (since InitGame in games_sp.log, back-computed from the log's own
MM:SS prefix so it stays accurate across restarts). Anchors are
persisted on `servers` and fanned out via the SSE bus; a single 1s
ticker feeds every card. Idle map cycles are filtered out so the game
timer doesn't flicker.
execute/DestroyDeployment queues the container removal and returns before
it actually completes. write/DeleteDeployment refuses while a container
still exists for the deployment, so the entity survived every teardown.
Poll komodoInspectHealthy between the two calls until it stops reporting
a running container (up to 30s), then delete the entity.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PLUTAINER_PORT sets the port the container listens on, so the mapping
should be <port>:<port>, not <port>:28960 — otherwise the container is
bound to a port nothing inside is listening on.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>