mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 20:54:00 +01:00
The solver only ran behind ./run.sh, had one media query in the whole
stylesheet, and kept the inventory in a 46vh scroll box that showed maybe a
third of the 43 dice. Setting it up on the PC did nothing for the phone.
Deployment
dice.kuhy.duckdns.org, behind the existing gitea-caddy edge. DuckDNS already
wildcards *.kuhy.duckdns.org (checked against 1.1.1.1 and 8.8.8.8), so no
dashboard step. deploy_build.sh builds to dist.staging and rsyncs into dist/
with --delete-after --delay-updates; plain --delete removes stale assets
before writing the new ones, which 404s a client mid-load. A post-commit hook
starts a MemorySwapMax=0 systemd user unit, so a rebuild can neither slow a
commit nor thrash zram.
Three traps found by driving the live URL in a browser rather than curling it:
a site address of "127.0.0.1:8089" reads as a HOSTNAME, so Caddy enabled
auto-HTTPS and started an HTTP->HTTPS redirect listener that bound *:80 next
to the edge -- SO_REUSEPORT would then split inbound HTTP and intermittently
break ACME for every domain here. The same address also made a Host matcher,
so the edge's forwarded Host matched nothing and served an empty 200 while the
local health check passed. And watching dist/'s own inode could never detect a
detached bind mount: rsync into an existing directory never changes it. Each
now has a guard that asserts positively rather than failing open.
Dice list
All 43 on screen at every width, no inner scrollbar. Column count and size
tier are chosen together from a container query on the panel -- not the
viewport, which mispredicts by 2x at the point the columns split. Two
invariants govern it: the list fits above the fold, and the name column stays
wide enough to tell Painter's die B, G and R apart. The left column holds the
grid alone, so it is sized to fill the height it owns: one --die-row value
drives the type, buttons and bar strip, giving a 52px row and 44px buttons on
a 1296px-tall screen and 20px rows on a phone.
Three safe-design rules are broken inside .die-list deliberately: sub-16px
body text, square icon buttons, sub-8px intra-cell gaps. 43 items with a name,
a chart and a counter in one viewport is arithmetically incompatible with 16px.
Import / export
A file, the clipboard, or a link carrying the inventory in the URL fragment.
One validator behind all four entry points including localStorage; it keeps
what it can and reports what it dropped, so a die added in a later game patch
costs you that entry, not the inventory. A link never silently overwrites what
is saved here -- it offers Keep or Discard, and only a link that actually
carries dice is applied at all, because one encoding a count of zero parsed
cleanly, blanked the screen, disabled Clear and left Keep writing the emptiness
over the visitor's own loadout.
session_autopsy
parse.py (551) and report.py (504) were over the repo's 500-line limit, so
test_file_length has been failing on main since 5ffd035. Extracted TurnTracker
and the two candidate-cell helpers. Report output is byte-identical on real
data apart from its timestamp, checked against HEAD in a worktree.
Verified: 288 TS tests and 911 Python tests, 100% coverage both, lint and
pre-commit clean, 8 viewports measured on the live HTTPS site, solver confirmed
through the real module worker.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
70 lines
6.6 KiB
JSON
70 lines
6.6 KiB
JSON
{
|
|
"intent": "Let a KCD2 dice-solver inventory move between devices — download/load a .json file, copy or paste the JSON, and share a link that carries the inventory in its URL fragment — and deploy the result at https://dice.kuhy.duckdns.org/.",
|
|
"scope": [
|
|
"kcd2_dice_solver/src/lib/inventoryIo.ts — one validator plus the clipboard/save-file/url-hash ports",
|
|
"kcd2_dice_solver/src/components/InventoryIo.tsx — the collapsible import/export panel",
|
|
"kcd2_dice_solver/src/App.tsx — shared-link arrival, deferred persistence, dropped-payload notices",
|
|
"kcd2_dice_solver/deploy_build.sh — the single build path used by both setup and the post-commit hook",
|
|
"linux_configuration/scripts/single_use/features/setup_kcd2_dice_solver.sh — serves dist/ behind the shared gitea-caddy edge",
|
|
"Non-goal: no change to the solver core, the dice/badge data, or the scoring model"
|
|
],
|
|
"changes": [
|
|
"Moved inventory persistence out of App.tsx into src/lib/inventoryIo.ts, so localStorage, a pasted file, the clipboard and a shared link all funnel through one validator that sanitises rather than rejects (unknown ids, bad counts and duplicates are dropped and reported, not thrown away silently).",
|
|
"Added import/export UI: download .json, copy JSON, copy share link, load a file, and paste-to-import, with clipboard and download injected as ports because navigator.clipboard and URL.createObjectURL are absent under jsdom.",
|
|
"Shared links encode base64url of the JSON rather than a positional scheme, because DICE is sorted alphabetically and a die added in a later patch would silently redefine every previously-shared link.",
|
|
"A link-loaded inventory is NOT written to storage on arrival; persistence is deferred until the visitor presses Keep or edits anything, so a shared link cannot silently overwrite a saved loadout.",
|
|
"FIX (found in review, was a real defect): a damaged fragment such as #i=@@@ still parses to a non-null result holding an EMPTY inventory, so `fromLink?.inventory ?? loadInventory(store)` never fell through — the visitor's own saved dice vanished from the page with no explanation. Extracted isEmptyInventory/isUsable into inventoryIo.ts as the single definition of 'worth applying', used now by both the paste box and the link path; App.tsx applies a link only when usable and otherwise falls back to storage.",
|
|
"Surfaced ParseResult.dropped on the link path, which was previously computed and discarded: a partially-usable link now shows 'Ignored: Unknown die ...' and an unusable one shows 'That link carried nothing usable, so this is what is saved here.'",
|
|
"Added a .notice.bad style (warning accent, same shape as .notice) for the damaged/incomplete case.",
|
|
"deploy_build.sh builds into dist.staging/ and rsyncs into dist/, and asserts the dist/ inode is unchanged: the serving container bind-mounts dist/ at /srv, so any rm -rf + mv would silently detach the mount and serve stale files forever with no error."
|
|
],
|
|
"verification": [
|
|
{
|
|
"command": "pnpm exec vitest run --coverage",
|
|
"result": "pass",
|
|
"evidence": "15 files, 280 tests passed. Coverage 100%: statements 856/856, branches 373/373, functions 179/179, lines 804/804. Includes 2 new regression tests that fail against the pre-fix code."
|
|
},
|
|
{
|
|
"command": "regression check — new tests fail before the fix",
|
|
"result": "pass",
|
|
"evidence": "Before the fix, 'keeps the saved inventory, and says so, when the link is damaged' failed with the rendered DOM showing '0 dice' plus the 'Loaded from a shared link' banner, proving the saved lucky:1 inventory was discarded. After the fix both new tests pass."
|
|
},
|
|
{
|
|
"command": "pnpm exec tsc --noEmit; pnpm exec eslint src",
|
|
"result": "pass",
|
|
"evidence": "TSC=0 ESLINT=0 (exit codes asserted directly; an earlier ${PIPESTATUS[0]} check was a bash-ism under zsh and asserted nothing)."
|
|
},
|
|
{
|
|
"command": "./deploy_build.sh",
|
|
"result": "pass",
|
|
"evidence": "Published 4 files to dist (inode 87041124, unchanged before and after rsync, so the container bind mount is still attached)."
|
|
},
|
|
{
|
|
"command": "setup_kcd2_dice_solver.sh status",
|
|
"result": "pass",
|
|
"evidence": "All 13 checks OK: container running, listening only on 8089, Caddy snippet present, DNS resolves, external https OK, post-commit rebuild hook and unit installed."
|
|
},
|
|
{
|
|
"command": "curl https://dice.kuhy.duckdns.org/",
|
|
"result": "pass",
|
|
"evidence": "HTTP 200 public and 200 on 127.0.0.1:8089; served bundle is assets/index-CjacUXQ5.js, byte-identical to the hash just built, confirming the live site serves this change and not a stale build."
|
|
},
|
|
{
|
|
"command": "grep -hoP '^\\s*id:\\s*\"[^\"]*\"' src/data/*.ts | grep -P '[^\\x00-\\x7F]'",
|
|
"result": "pass",
|
|
"evidence": "No matches across all 77 dice/badge ids, so btoa() in toBase64Url cannot throw on current data."
|
|
}
|
|
],
|
|
"risks": [
|
|
"NOT verified in a real browser: headless chromium is blocked by this environment's permission layer, so the fragment round-trip was verified only in jsdom (with the real solver and real modules, not stubs) plus HTTP/bundle-hash checks against the live site. Browser-specific APIs on this path — btoa/atob, URLSearchParams, history.replaceState — are standard, but a human should open the share link once to confirm.",
|
|
"toBase64Url uses btoa, which throws on any character above U+00FF. All 77 current ids are ASCII and the validator only ever admits known ids, so this is unreachable today; a future die added with a non-ASCII id would white-screen the page at render, since shareUrl is computed during render.",
|
|
"A shared link is applied over the local inventory on arrival (by design — following a link is deliberate). It is not persisted until Keep or an edit, so a reload recovers the saved loadout.",
|
|
"The inner Caddyfile needs auto_https off and an explicit http:// scheme: a bare 127.0.0.1:8089 address reads as a hostname, so Caddy would serve TLS on 8089 and start an HTTP->HTTPS redirect listener on :80 that, under host networking, would contend with the shared edge and could break ACME for every domain on this host. This is encoded in setup_kcd2_dice_solver.sh, not only in the generated file."
|
|
],
|
|
"rollback": [
|
|
"git revert the commit, then run kcd2_dice_solver/deploy_build.sh to republish the previous bundle into the existing dist/ inode.",
|
|
"Validate: setup_kcd2_dice_solver.sh status reports all checks OK, and curl -o /dev/null -w '%{http_code}' https://dice.kuhy.duckdns.org/ returns 200.",
|
|
"Saved inventories survive a rollback: STORAGE_KEY is unchanged and the older loadInventory tolerates the same JSON shape."
|
|
]
|
|
}
|