From 34b535e9ea0c8bded300b25cb12158563dc33298 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 05:14:17 +0000 Subject: [PATCH] docs(affine): capture the AffineScript interface constraints durably MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three AffineScript compiler constraints that shaped src/ui/tea/*.affine only lived in a STATE.a2ml session-log entry. Lift them into discoverable source docs so they aren't rediscovered the hard way, and guard the code against well-meaning "simplification": - New src/ui/tea/README.adoc — what the TEA interface is (pure core + cmd_* effect layer), how to typecheck (cwd-relative, `just affine-check`), the symbol-contract guard (`just affine-contract`, CI), and the three language constraints with rationale. - gsa_gui.affine — an in-context "constraints (read before editing)" header block: `handle` is reserved (use `server_handle`); record-spread does not typecheck on nominal structs, so every `update` arm restating all 13 fields is DELIBERATE, not oversight — do not DRY it with a spread; module resolution is cwd-relative. - gsa_ffi.affine — a one-line pointer to the naming convention + README. Docs/comments only. No behavioural change; the .affine sources are unchanged in logic (AffineScript has no compiler in this environment, so a blind refactor of working code was deliberately avoided). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TaWWedv6VQqeZaPvc94keN --- src/ui/tea/README.adoc | 88 +++++++++++++++++++++++++++++++++++++++ src/ui/tea/gsa_ffi.affine | 4 ++ src/ui/tea/gsa_gui.affine | 23 ++++++++++ 3 files changed, 115 insertions(+) create mode 100644 src/ui/tea/README.adoc diff --git a/src/ui/tea/README.adoc b/src/ui/tea/README.adoc new file mode 100644 index 0000000..73ec3a9 --- /dev/null +++ b/src/ui/tea/README.adoc @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += GSA AffineScript TEA Interface +:toc: + +The AffineScript surface of Game Server Admin: a formally-structured, +resource-safe GUI interface that can drive *any* game GSA supports. It is the +estate's AffineScript half of the seam `AffineScript (interface) → Zig (FFI) → +Idris2 (ABI)`, and it is a parallel interface to the Ephapax/Gossamer GUI — +both talk to the same `libgsa` C ABI. + +== Files + +[cols="1,3"] +|=== +| File | Role + +| `gsa_gui.affine` +| The **pure TEA core** — `init` / `update` / `view` / `subs`. No I/O. The + `Model` is driven by the A2ML game-profile registry (18 shipped profiles + + runtime adds), with generic protocol-fingerprint fallback for unknown games. +| `gsa_ffi.affine` +| The **effect layer** — typed `extern` declarations for the `libgsa` C ABI + (all 38 `gossamer_gsa_*` exports) plus `cmd_*` wrappers carrying the `/ IO` + effect row. A `ResultCode` enum mirrors `src/interface/abi/Types.idr` + (codes 0–17). +|=== + +The core performs no effects: the host runs a `cmd_*` and feeds the outcome +back in as a `Msg`. + +== Typechecking + +AffineScript module resolution is **cwd-relative**, so typecheck from this +directory: + +[source,bash] +---- +just affine-check # typecheck gsa_gui.affine + gsa_ffi.affine +---- + +The AffineScript compiler is not packaged in every environment (it is not +available in CI or the web sandbox), so a local check is the only way to +typecheck the `.affine` sources. What *is* enforced everywhere is the FFI +symbol contract: + +[source,bash] +---- +just affine-contract # scripts/affine-ffi-contract-check.sh +---- + +This asserts that every `extern` declared in `gsa_ffi.affine` is a real Zig +export and that the operational core symbol set is fully declared. It runs +locally and as the `affine-ffi-contract` job in +`.github/workflows/abi-contract.yml`. Because CI cannot typecheck the sources, +**run `just affine-check` locally before pushing changes to these files.** + +== Language constraints (why the code looks the way it does) + +Three properties of the current AffineScript compiler shape both files. None is +a defect in this code — each is a constraint the code is written *around*. They +are repeated in the `gsa_gui.affine` header so an editor sees them in context. + +`handle` is a reserved word:: + FFI handle parameters are named `server_handle`, never `handle`. Do not + rename them back. + +Record update by spread does not typecheck on nominal structs:: + There is no `#{ m with field: ... }` form for nominal structs. Every arm of + `update` therefore reconstructs the full `Model` literal — all 13 fields, + changing only the one or two that move. This verbosity is **deliberate**, not + an oversight; do not try to DRY it with a spread (it will not compile). If a + future compiler gains record spread on nominal structs, the `update` arms can + be collapsed then — until then, restating the record is the supported idiom. + +Module resolution is cwd-relative:: + Always typecheck from `src/ui/tea` (see above). A run from the repo root will + fail to resolve `use gsa_ffi::…`. + +== Relationship to the rest of GSA + +* Result codes here are a mirror of the single source of truth, + `src/interface/abi/Types.idr` — see + `docs/wikis/05-The-Proven-ABI.md` for how that contract is machine-checked. +* The C ABI symbols come from the Zig FFI — see + `docs/developer/FFI-MODULE-REFERENCE.adoc`. +* For the panel/wizard semantics the `view` renders, see + `docs/wikis/07-GUI-and-Nexus-Setup.md`. diff --git a/src/ui/tea/gsa_ffi.affine b/src/ui/tea/gsa_ffi.affine index b6f127a..7e9c3ad 100644 --- a/src/ui/tea/gsa_ffi.affine +++ b/src/ui/tea/gsa_ffi.affine @@ -14,6 +14,10 @@ // `scripts/affine-ffi-contract-check.sh` keeps this file honest: every // symbol declared here must exist as a Zig export, and the operational // core set must all be declared here. Run it after touching either side. +// +// Naming note: `handle` is a reserved word in AffineScript, so the handle +// params below are named `server_handle`. See src/ui/tea/README.adoc for the +// full set of language constraints that shape this interface. module gsa_ffi; diff --git a/src/ui/tea/gsa_gui.affine b/src/ui/tea/gsa_gui.affine index 77e276f..245405c 100644 --- a/src/ui/tea/gsa_gui.affine +++ b/src/ui/tea/gsa_gui.affine @@ -24,6 +24,29 @@ use gsa_ffi::{cmd_bootstrap, cmd_probe, cmd_server_action, cmd_fetch_logs, // Stdlib canon extern (pure; see affinescript stdlib/effects.affine). extern fn int_to_string(n: Int) -> String; +// ── AffineScript constraints (read before editing) ────────────────────────── +// +// Three properties of the current AffineScript compiler shape this file. They +// are documented here (and in src/ui/tea/README.adoc) so they are not +// rediscovered the hard way. None is a bug in this code — each is a language +// constraint the code is written *around* on purpose: +// +// 1. `handle` is a reserved word. FFI externs and params therefore use +// `server_handle`, never `handle`. Do not "simplify" the name back. +// +// 2. Record update by spread (`#{ m with status_line: ... }`) does NOT +// typecheck on nominal structs. Consequently every arm of `update` +// below reconstructs the full `Model` literal, restating all 13 fields +// and changing only the one or two that move. This verbosity is +// DELIBERATE — it is the cost of nominal-struct + affine semantics, not +// an oversight. Do not try to DRY it with a spread; it will not compile. +// (If a future compiler gains spread, collapse the arms then.) +// +// 3. Module resolution is cwd-relative. Typecheck from `src/ui/tea` +// (`just affine-check`). The extern↔Zig symbol contract is enforced by +// `scripts/affine-ffi-contract-check.sh` (`just affine-contract`, also +// the `affine-ffi-contract` job in `.github/workflows/abi-contract.yml`). +// // ── Types ───────────────────────────────────────────────────────────────── enum Panel {