mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 22:27:01 +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>
77 lines
2.5 KiB
Python
77 lines
2.5 KiB
Python
"""Turn-efficiency accounting for a single transcript.
|
|
|
|
Split out of ``parse.py``: the tracker is self-contained — it knows only about
|
|
turn totals and the classification of the commands in a turn — and lifting it
|
|
out keeps the parser inside the repo's 500-line-per-file limit.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
from python_pkg.session_autopsy.records import TurnEfficiency
|
|
from python_pkg.session_autopsy.turns import LINT_TEST, POLL, VCS_CHECK, classify_turn
|
|
|
|
|
|
@dataclass
|
|
class OpenTurn:
|
|
"""The API turn currently being accumulated, keyed by requestId."""
|
|
|
|
request_id: str | None = None
|
|
tools: int = 0
|
|
commands: list[str] = field(default_factory=list)
|
|
|
|
|
|
class TurnTracker:
|
|
"""Running turn-efficiency totals plus the turn currently open.
|
|
|
|
The two belong together: the open turn only exists to be folded into the
|
|
totals, and keeping them as one object is also what keeps the parser's
|
|
accumulator inside its instance-attribute budget.
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
"""Start with empty totals and no open turn."""
|
|
self.totals = TurnEfficiency()
|
|
self._open = OpenTurn()
|
|
|
|
def begin(self, request_id: object) -> None:
|
|
"""Open a new turn if this line belongs to a different API response.
|
|
|
|
Args:
|
|
request_id: The line's ``requestId`` (or message-id fallback).
|
|
"""
|
|
if request_id != self._open.request_id:
|
|
self.flush()
|
|
self._open.request_id = request_id if isinstance(request_id, str) else None
|
|
|
|
def add_tool(self) -> None:
|
|
"""Count one tool_use block in the open turn."""
|
|
self._open.tools += 1
|
|
|
|
def add_command(self, command: str) -> None:
|
|
"""Record one Bash command issued in the open turn.
|
|
|
|
Args:
|
|
command: The raw Bash tool input.
|
|
"""
|
|
self._open.commands.append(command)
|
|
|
|
def flush(self) -> None:
|
|
"""Fold the open turn into the totals and start a fresh one."""
|
|
open_turn = self._open
|
|
self._open = OpenTurn()
|
|
if open_turn.tools <= 0:
|
|
return
|
|
self.totals.api_turns += 1
|
|
self.totals.tool_calls += open_turn.tools
|
|
if open_turn.tools > 1:
|
|
self.totals.batched_turns += 1
|
|
label = classify_turn(open_turn.commands)
|
|
if label == POLL:
|
|
self.totals.poll_turns += 1
|
|
elif label == LINT_TEST:
|
|
self.totals.lint_test_turns += 1
|
|
elif label == VCS_CHECK:
|
|
self.totals.vcs_check_turns += 1
|