Skip to content

refactor(minesweeper): model idiomatic JaWS state - #386

Merged
linkdata merged 4 commits into
mainfrom
refactor/idiomatic-minesweeper
Aug 25, 2026
Merged

refactor(minesweeper): model idiomatic JaWS state#386
linkdata merged 4 commits into
mainfrom
refactor/idiomatic-minesweeper

Conversation

@linkdata

@linkdata linkdata commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • render Minesweeper directly from synchronized game and cell state, with no DTOs, projections, render snapshots, or retained presentation copies
  • use precise cell, board, game-over, and scalar dependency tags, with exact dirty-fanout and cross-request convergence coverage
  • demonstrate the justified standard-widget overload: cellButton embeds ui.Button, inherits its unmodified render path, and overrides only JawsUpdate
  • render dynamic initial attributes inline through an ordinary cell.InitialAttrs() template parameter, leaving the initial TailHTML empty
  • strengthen the JaWS contracts, skill guidance, example documentation, accessibility, tests, and committed benchmarks

Final widget shape

The template makes every render dependency explicit:

{{$.NewUI .Button .BoardTag .GameOverTag `class="cell"` .InitialAttrs}}

cell.Button() constructs a fresh *cellButton that embeds the standard ui.Button over the authoritative *cell. It does not implement JawsRender; Go method promotion supplies ui.Button.JawsRender, including getter, precise-tag, click, and context-menu registration. The outer definition overrides only JawsUpdate, because a dirty cell must update data-state, aria-label, disabled, and inner HTML as one local read of authoritative state.

InitialAttrs() returns escaped template.HTMLAttr for the ordinary render parameter. BoardTag() and GameOverTag() are ordinary template parameters as well. The cell does not implement JawsInitialHTMLAttr, and neither the render path nor the updater retains presentation state.

Initial attributes and inner HTML are separate synchronized reads, as permitted by the JaWS render contract. Dirty selection applies to dependencies already registered on an Element; each later matching dirty event re-reads the authoritative cell and brings every registered copy current.

Framework guidance

The repository history and all standard widgets support keeping JawsInitialHTMLAttr initial-only. Its result is raw opening-tag syntax: it identifies neither attribute ownership nor removals, so treating it as an update getter would create an underspecified live-DOM contract. A future general dynamic-attribute API would need explicit ownership/diff semantics, preferably structured set/remove operations.

The updated JaWS skill now says, in stronger terms:

  • use a standard getter, binder, semantic object, or render parameter when it expresses the control;
  • overload a standard widget only for behavior those mechanisms cannot express;
  • embed or retain the standard widget, keep the outer definition as the Element's single UI value, and document the exact phase being specialized;
  • never reinterpret the initial-attribute hook as an update getter.

Review resolution

Implemented across the review rounds:

  • preserve LAN and container access by listening on :8080
  • keep class="cell" template-owned and update only data-state
  • separate the status/statistics live regions; make the scrollable board a named, focusable region
  • release game.mu before all JaWS Element operations
  • reject duplicate dirty tags in tests
  • remove the reset pre-scan and avoid locals shadowing the cell type
  • format each accessible cell label in one Sprintf call
  • keep jw.Logger for the long-running server, with the rationale documented; direct UI tests leave it nil so contract violations fail fast
  • remove the artificial blocked-initial-read test and assert the actual invariant: initial rendering queues no redundant DOM operations

Benchmarks

The committed benchmark measures both the initial page and one-shot tail response. Payload bytes are stable across runs:

Metric main (b337341) Prior head (ced56ea) This head This head vs. main
Initial page 9,214 B 15,338 B 15,338 B +66.46%
Tail payload 69,800 B 32,120 B 0 B -100.00%
Total initial response 79,014 B 47,458 B 15,338 B -80.59%

Compared with ced56ea, ten 1-second samples on darwin/arm64 (Apple M5 Max) give:

time/op       300.7 µs -> 293.4 µs   -2.42%
page-B/op      15,338 -> 15,338      unchanged
tail-B/op      32,120 ->      0      -100.00%
B/op         447.1 KiB -> 279.6 KiB  -37.46%
allocs/op       6,599 ->  6,875      +4.18%

The measured goal is the initial network payload. The fresh overload's allocation cost is reported rather than hidden.

go test ./examples/minesweeper -run '^$' -bench '^BenchmarkInitialPageAndTail$' -benchmem -benchtime=1s -count=10
benchstat before.txt after.txt

BenchmarkSingleCellDirtyFanout remains stable at about 168 ns/op, 180 B/op, and 12 allocs/op across six 1-second samples.

Follow-up

Issue #387 proposes batching TailHTML operations through one client dispatcher call with a compact JSON payload. It records the existing TailHTML/WebSocket decoding drift and the requirement to preserve the current ordering relative to jawsAttachChildren(document).

Verification

  • go generate ./...
  • go vet ./...
  • gofmt -l .
  • gofumpt -l examples/minesweeper/*.go contracts.go
  • git diff --check
  • staticcheck ./...
  • golangci-lint v2.13.1 run
  • gosec ./...
  • go build ./...
  • JAWS_REQUIRE_NODE=1 go test -race ./...
  • JAWS_REQUIRE_NODE=1 go test ./...
  • go test -race -cover ./examples/minesweeper (99.0%)
  • both committed benchmarks, repeated at least six times
  • rendered go doc review for Updater and InitialHTMLAttrHandler
  • JaWS skill quick_validate.py

@linkdata
linkdata merged commit 2b3623b into main Aug 25, 2026
7 checks passed
@linkdata
linkdata deleted the refactor/idiomatic-minesweeper branch August 25, 2026 08:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant