From 673948430f8a884db577a99c162f2c1dfbc3f9a0 Mon Sep 17 00:00:00 2001 From: Matthew Mees Date: Sat, 11 Jul 2026 07:34:19 -0500 Subject: [PATCH] Align repository guidance for coding agents --- .gitattributes | 2 +- .github/workflows/ci.yml | 46 +------ .gitignore | 1 - AGENTS.md | 91 ++++++++++++++ CLAUDE.md | 4 + README.md | 7 ++ docs/adr/0001-ui-toolkit-decision.md | 2 +- docs/maintainer/README.md | 18 ++- docs/maintainer/doc-maintenance.md | 17 ++- docs/maintainer/testing.md | 81 ++++-------- docs/moonraker-capabilities.md | 10 +- docs/ui_design/AGENTS.md | 176 +++++++++++++++++++++++++++ docs/ui_design/CLAUDE.md | 174 +------------------------- docs/ui_design/COMPONENTS.md | 19 +-- docs/ui_design/README.md | 20 ++- docs/ui_design/THEMING.md | 3 +- scripts/check.sh | 102 ++++++++++++++++ 17 files changed, 460 insertions(+), 313 deletions(-) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 docs/ui_design/AGENTS.md create mode 100755 scripts/check.sh diff --git a/.gitattributes b/.gitattributes index 7ec28f7d..e7ec17ad 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,7 +6,7 @@ *.bat text eol=crlf *.cmd text eol=crlf -# Freeze the stock Gradle wrapper exactly as committed (CLAUDE.md: do NOT alter gradlew*). +# Freeze the stock Gradle wrapper exactly as committed (AGENTS.md: do NOT alter gradlew*). gradlew -text gradlew.bat -text diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 380f09e0..5385c883 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: cache-read-only: false # lintDebug enforces the shrink-only baseline (R5a step 4: "CI fails on any new violation"); # runner JDK is 17 so the JDK-21 UAST hazard doesn't apply here. - - run: ./gradlew :app:testDebugUnitTest :app:lintDebug :app:assembleDebug --no-daemon + - run: ./scripts/check.sh --gradle-only # R9 step 5 (docs/top-down-audit-roadmap.md §R9): memory-hygiene guardrail. # Scope = tracked public docs that are meant to exist in normal clones. Local-only planning/evidence # plus AI workflow artifacts (.planning/, docs/commands/, docs/superpowers/) are intentionally outside @@ -33,45 +33,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Stale-phrase check (tracked maintainer/public docs) - run: | - mapfile -t sources < <( - git ls-files README.md ':(glob)docs/*.md' ':(glob)docs/maintainer/*.md' \ - ':!:docs/top-down-audit-roadmap.md' \ - ':!:docs/superpowers/**' \ - ':!:docs/commands/**' - ) - if ((${#sources[@]})) && grep -rn "compileSdk 35\|Architecture not yet mapped" "${sources[@]}"; then - echo "FAIL: stale phrases found in tracked maintainer/public docs"; exit 1 - fi - echo "OK: no stale phrases" - - name: Dead internal-link check (tracked maintainer/public docs) - run: | - rm -f /tmp/dead-links - mapfile -t sources < <( - git ls-files README.md ':(glob)docs/*.md' ':(glob)docs/maintainer/*.md' \ - ':!:docs/top-down-audit-roadmap.md' \ - ':!:docs/superpowers/**' \ - ':!:docs/commands/**' - ) - for src in "${sources[@]}"; do - [ -f "$src" ] || continue - dir=$(dirname "$src") - # Extract relative .md link targets: [text](path.md) / [text](path.md#anchor) - # `|| true` guards pipefail: grep exits 1 on no-match, which would kill the - # step under Actions' default `bash -e -o pipefail` before the dead-links check. - { grep -oE '\]\([^)# ]+\.md(#[^)]*)?\)' "$src" 2>/dev/null || true; } \ - | sed -E 's/^\]\(//; s/#[^)]*//; s/\)$//' \ - | sort -u \ - | while read -r target; do - case "$target" in - http://*|https://*|*://*|/*|[A-Za-z]:/*|*'\'*) continue ;; # skip URLs, absolute and Windows paths - esac - # accept either file-relative or repo-root-relative targets - if [ ! -e "$dir/$target" ] && [ ! -e "$target" ]; then - echo "DEAD LINK in $src -> $target" | tee -a /tmp/dead-links - fi - done - done - if [ -s /tmp/dead-links ]; then echo "FAIL: dead internal links"; exit 1; fi - echo "OK: no dead internal links" + - name: Authoritative documentation hygiene + run: ./scripts/check.sh --docs-only diff --git a/.gitignore b/.gitignore index d2c7238a..b33693cc 100644 --- a/.gitignore +++ b/.gitignore @@ -38,7 +38,6 @@ tools/__pycache__/ # Internal planning / AI dev artifacts (local only — not in clones) .planning/ -/CLAUDE.md .claude/skills/ docs/superpowers/ # docs/commands/ is local-only evidence, EXCEPT the tracked artifacts CI requires in every clone: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..94bbd111 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,91 @@ +# jiib Agent Guide + +## Project + +jiib is a native Android control surface for Klipper 3D printers. It connects directly to +Moonraker over the local network and must keep the connect → monitor → control-a-print loop reliable +on old hardware. + +## Trust And Context + +Use current sources in this order: + +1. Source code, build configuration, and tests define current behavior. +2. This file defines repository-wide working rules. +3. `docs/maintainer/` explains current architecture, testing, releases, and documentation ownership. +4. Scoped references such as `docs/ui_design/`, `docs/manual/`, `docs/commands/`, and accepted ADRs + are authoritative only for their stated subject. + +Ignored or local-only paths—including `.planning/`, `.superpowers/`, `.claude/skills/`, +`docs/superpowers/`, and untracked reports—are historical or workflow context. Never treat them as +current implementation guidance. Use them only for archaeology. When documentation conflicts with +the implementation, verify the implementation and update the authoritative document as part of the +change. + +## Non-Negotiable Constraints + +- Android 6.0 / API 23 is the support floor. Do not raise `minSdk` or add dependencies that do. +- The Nexus 7 2013 (Adreno 320, 2 GB RAM) is the performance floor. Profile release builds; debug + Compose performance is not representative. +- Support both portrait and landscape and the dark, light, custom-color, and S/M/L text settings. +- The app is GMS-free and distributed as sideloaded per-ABI APKs. +- Do not change the application ID, release signing model, ABI split strategy, or version scheme + without an explicit project decision. +- Dependency versions are pinned. Do not make opportunistic dependency or build-tool upgrades. +- Do not edit the checked-in Gradle wrapper scripts or wrapper JAR unless the task explicitly changes + the Gradle wrapper. + +## Canonical Commands + +- Run the normal local/CI gate: `./scripts/check.sh` +- Run documentation hygiene only: `./scripts/check.sh --docs-only` +- Run the Gradle gate only: `./scripts/check.sh --gradle-only` +- Run a targeted host test: `./gradlew :app:testDebugUnitTest --tests --no-daemon` + +Under WSL, `scripts/check.sh` automatically uses the Windows-side `E:\Android\gw.bat` helper so adb +and the installed Android SDK remain reachable. CI and native Linux use the checked-in wrapper. +Read `docs/maintainer/testing.md` before device-, printer-, or release-specific verification. + +## Architecture Boundaries + +- `MoonrakerService` owns the live printer session and publishes one atomic `SpineHandle`. +- UI consumes the session spine, store-derived flows, or feature holders. UI must not construct + sockets, sessions, or raw JSON-RPC clients. +- `state/` is headless state and reducers; `net/` is transport/protocol; `command/` is command specs + and dispatch; `service/` owns the session; `di/` exposes process wiring. +- Keep feature state toolkit-agnostic where practical. Compose is the default UI toolkit; classic + Views are reserved for measured high-churn or render-heavy surfaces. +- Put new code in the package that owns the behavior. Avoid generic dumping grounds. + +Read `docs/maintainer/architecture.md` before changing session ownership, reconnect behavior, +printer switching, navigation, state flow, or major package responsibilities. + +## UI Changes + +Before changing screen structure, components, icons, colors, typography, layout, or control intent, +read `docs/ui_design/AGENTS.md` and follow its reading order. Never guess or auto-select a glyph; use +an existing approved asset or ask the owner when no selection exists. Update `docs/manual/` when +user-visible behavior changes. + +## Testing And Completion + +- Add or update tests for behavior changes; add a regression test for a bug when practical. +- Run the narrowest useful test while iterating, then the canonical gate before completion. +- Device-dependent work may require instrumented tests and manual hardware/printer verification. +- Do not grow the lint baseline to hide new violations. +- Command-registry and reference changes must keep their drift tests and tracked generated artifacts + synchronized. + +## Security And Release Safety + +- Never commit API keys, printer credentials, keystores, signing passwords, or credential-bearing + `local.properties` content. +- Do not log credentials or sensitive Moonraker payloads. +- A release build can succeed unsigned. Follow `docs/maintainer/release.md`; never publish an APK + without explicitly verifying its signature and ABI. + +## Documentation Rule + +Update the owning document whenever a change alters how someone builds, tests, releases, configures, +extends, or reasons about the system. Prefer links over copied facts. Delete or correct stale guidance +immediately; do not preserve it inside an authoritative document merely for history. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..cf4011c6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,4 @@ +# Claude Code Instructions + +Read and follow [AGENTS.md](AGENTS.md). It is the single repository-wide source of coding-agent +instructions. This file intentionally adds no duplicate project rules. diff --git a/README.md b/README.md index b33a5985..a9ffa401 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,13 @@ Kotlin · Jetpack Compose + classic Views hybrid · OkHttp (WebSocket) + Retrofi kotlinx.serialization · Coroutines/Flow · Coil. See `docs/adr/0001-ui-toolkit-decision.md` for the UI-toolkit rationale and `docs/ui_design/` for the design system. +## Maintainer guide + +Coding agents and contributors should start with [`AGENTS.md`](AGENTS.md). Current architecture, +testing, release, and documentation-ownership guidance lives in +[`docs/maintainer/`](docs/maintainer/README.md). Run `./scripts/check.sh` for the normal verification +gate. + ## License [GPLv3](LICENSE). diff --git a/docs/adr/0001-ui-toolkit-decision.md b/docs/adr/0001-ui-toolkit-decision.md index ce3b1320..1c3bcff0 100644 --- a/docs/adr/0001-ui-toolkit-decision.md +++ b/docs/adr/0001-ui-toolkit-decision.md @@ -13,7 +13,7 @@ touchscreen in **Jetpack Compose everywhere** or use **classic Views** for the h The constraint is an **Adreno 320 / 2 GB / 1920×1200** tablet — fill-rate-bound, high-resolution, old GPU. A janky printer screen is worse than none, so this is decided by measurement, not preference. -**The CLAUDE.md research prior leaned Compose ("HIGH confidence")** for dev velocity and modern +**The initial project research leaned Compose ("HIGH confidence")** for dev velocity and modern tooling, while explicitly naming a **hybrid-Views fallback** for high-churn surfaces (Files list, temperature graph, Console scrollback) if measurement demanded it. This ADR is that measurement. diff --git a/docs/maintainer/README.md b/docs/maintainer/README.md index dbb05f7a..a3e3283e 100644 --- a/docs/maintainer/README.md +++ b/docs/maintainer/README.md @@ -6,12 +6,16 @@ Use these docs when changing the app, reviewing AI-generated patches, or re-orie from the project. Historical planning notes under `.planning/` are local-only project memory and may contain stale package names, old navigation concepts, or retired implementation details. +Repository-wide working rules and the trust hierarchy live in [AGENTS.md](../../AGENTS.md). Read it +first. These maintainer documents provide the detail that the short root instruction file routes to. + ## Reading Order -1. [Architecture](architecture.md) - current app structure, session spine, navigation, state flow, and UI boundaries. -2. [Testing](testing.md) - CI, local verification commands, device tests, fixtures, and docs-only verification. -3. [Release](release.md) - manual release runbook, versioning, signing, split APKs, and smoke checks. -4. [Doc Maintenance](doc-maintenance.md) - rules for keeping current docs, manuals, design docs, and local evidence in sync. +1. [Root agent guide](../../AGENTS.md) - repository-wide constraints, commands, boundaries, and trust rules. +2. [Architecture](architecture.md) - current app structure, session spine, navigation, state flow, and UI boundaries. +3. [Testing](testing.md) - CI, local verification commands, device tests, fixtures, and docs-only verification. +4. [Release](release.md) - manual release runbook, versioning, signing, split APKs, and smoke checks. +5. [Doc Maintenance](doc-maintenance.md) - rules for keeping current docs, manuals, design docs, and local evidence in sync. ## Existing Docs To Use @@ -22,7 +26,9 @@ contain stale package names, old navigation concepts, or retired implementation ## Local-Only Context -- `docs/commands/` - local-only Moonraker/Klipper/Spoolman command evidence; gitignored and absent from normal public clones, except the tracked `catalog.json` and `printer-matrix.json` fixtures that `CommandCatalogDriftTest` requires in every clone. +- `docs/commands/` - mostly local-only Moonraker/Klipper/Spoolman evidence. Public clones contain the + tracked `catalog.json` and `printer-matrix.json` fixtures plus the generated `COMMANDS.md` reference; + other files in the directory may be absent. - `.planning/` - local-only historical planning/project memory; gitignored and absent from normal public clones. ## What To Read Before Changing @@ -35,7 +41,7 @@ contain stale package names, old navigation concepts, or retired implementation | Testing strategy or CI expectations | [Testing](testing.md) | | Release packaging, signing, or version bumps | [Release](release.md) | | Durable architecture decisions | [Doc Maintenance](doc-maintenance.md) and existing [ADRs](../adr/) | -| Command contracts | [Architecture](architecture.md), `docs/commands/` (tracked `catalog.json`/`printer-matrix.json` plus local-only evidence), [CommandRegistry.kt](../../app/src/main/java/works/mees/jiib/command/CommandRegistry.kt), and [CommandCatalogDriftTest.kt](../../app/src/test/java/works/mees/jiib/command/CommandCatalogDriftTest.kt) | +| Command contracts | [Architecture](architecture.md), [generated command reference](../commands/COMMANDS.md), tracked `catalog.json`/`printer-matrix.json`, [CommandRegistry.kt](../../app/src/main/java/works/mees/jiib/command/CommandRegistry.kt), and the command drift tests | ## Current Source Of Truth diff --git a/docs/maintainer/doc-maintenance.md b/docs/maintainer/doc-maintenance.md index e994cbb1..e8d7caec 100644 --- a/docs/maintainer/doc-maintenance.md +++ b/docs/maintainer/doc-maintenance.md @@ -4,12 +4,16 @@ This file defines which docs own current facts and how to keep them from driftin ## Current And Historical Sources -Current maintainer facts live in `docs/maintainer/`. +Repository-wide agent rules live in `AGENTS.md`; current maintainer explanations live in +`docs/maintainer/`. Source code, build configuration, and tests remain the final authority for current +behavior. Tool-specific instruction files such as `CLAUDE.md` are short pointers and must not copy +project rules. Local-only historical/project-memory material lives in ignored paths such as: - `.planning/` -- `docs/commands/` (except the tracked `catalog.json` and `printer-matrix.json` test fixtures) +- untracked material under `docs/commands/` (the tracked exceptions are `catalog.json`, + `printer-matrix.json`, and the generated `COMMANDS.md` reference) - `docs/superpowers/` plans and evidence `docs/superpowers/` is local-only by policy: specs and plans are written there but never committed. @@ -55,8 +59,8 @@ Update `docs/ui_design/` when visual law, layout grammar, tokens, typography, ic semantics change. Update the tracked `docs/commands/catalog.json` and `docs/commands/printer-matrix.json` fixtures, -local-only `docs/commands/` evidence, and `CommandCatalogDriftTest` when Moonraker/Klipper/Spoolman -command contracts change. +generated `docs/commands/COMMANDS.md`, local-only command evidence, and both command drift tests when +Moonraker/Klipper/Spoolman command contracts change. Add or update an ADR under `docs/adr/` when a durable architecture decision is made and future maintainers need the rationale. @@ -77,8 +81,9 @@ maintainer docs; that makes the docs fail their own check. Negative historical facts may name retired concepts when needed. If they do, the stale-term scan must either allowlist those exact lines or the docs should be reworded to avoid the exact tokens. -Run the stale-phrase and dead-link dry-runs in [Testing](testing.md#docs-only-changes). The source of -truth for CI hygiene remains `.github/workflows/ci.yml`. +Run the documentation check in [Testing](testing.md#docs-only-changes). The executable source of truth +for CI hygiene is `scripts/check.sh`; `.github/workflows/ci.yml` invokes it rather than duplicating its +logic. ## Avoiding Duplicate Truth diff --git a/docs/maintainer/testing.md b/docs/maintainer/testing.md index e1787185..fb8266a5 100644 --- a/docs/maintainer/testing.md +++ b/docs/maintainer/testing.md @@ -7,12 +7,13 @@ This document describes the verification surface for current jiib maintenance wo GitHub Actions runs on pushes to `main` and `gsd/**`, and on pull requests to `main`. The build job runs: ```bash -./gradlew :app:testDebugUnitTest :app:lintDebug :app:assembleDebug --no-daemon +./scripts/check.sh --gradle-only ``` -The hygiene job checks `README.md`, top-level tracked `docs/*.md`, and tracked -`docs/maintainer/*.md` for stale phrases and dead internal Markdown links. It intentionally excludes -local-only ignored trees such as `.planning/`, `docs/commands/`, and `docs/superpowers/`. +The hygiene job runs `./scripts/check.sh --docs-only`. It checks tracked Markdown links, scans the +authoritative instruction and maintainer/design documents for known stale phrases, and verifies that +tracked `CLAUDE.md` files remain short pointers to `AGENTS.md`. Local-only ignored trees such as +`.planning/` and `docs/superpowers/` are absent from the tracked-file input. If `.github/workflows/ci.yml` does not target `main`, fix CI before treating this gate as active. @@ -37,6 +38,15 @@ Examples: CI uses Linux and the checked-in Gradle wrapper directly. +For the normal local gate, prefer the repository entry point: + +```bash +./scripts/check.sh +``` + +It selects the Windows helper automatically under WSL and the checked-in Gradle wrapper under native +Linux/CI. Use the direct helper commands below for targeted tests. + ## Host Unit Tests Host tests live under `app/src/test/java/works/mees/jiib/`. They cover most pure logic, reducers, @@ -73,20 +83,22 @@ Some device checks require a real printer or specific local hardware and are not Test resources live under `app/src/test/resources/`. Captured JSON and binary fixtures are used to keep protocol and parser tests deterministic. `docs/commands/catalog.json` and `docs/commands/printer-matrix.json` -are tracked because `CommandCatalogDriftTest` reads them; the rest of `docs/commands/` is local-only -command evidence and capture material, gitignored and possibly absent from public clones. +are tracked because `CommandCatalogDriftTest` reads them. The generated, tracked +`docs/commands/COMMANDS.md` is guarded by `CommandReferenceDocDriftTest`. Other command evidence and +capture material in that directory is local-only and may be absent from public clones. When adding or changing a command contract, check: - `app/src/main/java/works/mees/jiib/command/CommandRegistry.kt` - `app/src/test/java/works/mees/jiib/command/CommandCatalogDriftTest.kt` - the tracked `docs/commands/catalog.json` and `docs/commands/printer-matrix.json` fixtures +- the generated `docs/commands/COMMANDS.md` and `CommandReferenceDocDriftTest.kt` - additional local-only `docs/commands/` evidence, if available ## Docs-Only Changes -For changes limited to `docs/maintainer/*.md`, local-only `.planning/codebase/*.md` archive banners, -or README-style documentation: +For changes limited to authoritative instructions, maintainer/design docs, local-only planning +archive banners, or README-style documentation: 1. Run internal-link checks for the files touched. 2. Run stale-term scans for old package/navigation names. @@ -98,60 +110,13 @@ tracked maintainer docs just to document the scan; that would make the docs fail Negative historical facts may name retired concepts when needed, but stale-term scans should either allowlist those lines explicitly or the docs should avoid the exact tokens. -Suggested stale-phrase dry run: - -```bash -mapfile -t sources < <( - git ls-files README.md ':(glob)docs/*.md' ':(glob)docs/maintainer/*.md' \ - ':!:docs/top-down-audit-roadmap.md' \ - ':!:docs/superpowers/**' \ - ':!:docs/commands/**' -) -pattern=$(sed -n 's/.*grep -rn "\([^"]*\)" .*/\1/p' .github/workflows/ci.yml | head -1) -if [ -z "$pattern" ]; then - echo "Unable to extract stale-phrase pattern from CI workflow" - exit 1 -fi -if ((${#sources[@]})) && grep -rn "$pattern" "${sources[@]}"; then - exit 1 -fi -echo OK -``` - -Expected: `OK`. - -Suggested dead internal Markdown-link dry run: +Run the same documentation hygiene used by CI: ```bash -rm -f /tmp/dead-links -mapfile -t sources < <( - git ls-files README.md ':(glob)docs/*.md' ':(glob)docs/maintainer/*.md' \ - ':!:docs/top-down-audit-roadmap.md' \ - ':!:docs/superpowers/**' \ - ':!:docs/commands/**' -) -for src in "${sources[@]}"; do - [ -f "$src" ] || continue - dir=$(dirname "$src") - { grep -oE '\]\([^)# ]+\.md(#[^)]*)?\)' "$src" 2>/dev/null || true; } \ - | sed -E 's/^\]\(//; s/#[^)]*//; s/\)$//' \ - | sort -u \ - | while read -r target; do - case "$target" in - http://*|https://*|*://*|/*|[A-Za-z]:/*|*'\'*) continue ;; - esac - if [ ! -e "$dir/$target" ] && [ ! -e "$target" ]; then - echo "DEAD LINK in $src -> $target" | tee -a /tmp/dead-links - fi - done -done -if [ -s /tmp/dead-links ]; then - exit 1 -fi -echo OK +./scripts/check.sh --docs-only ``` -Expected: `OK`. +Expected: `OK: documentation hygiene`. ## Manual Review Still Matters diff --git a/docs/moonraker-capabilities.md b/docs/moonraker-capabilities.md index d1580a2c..ab7746cc 100644 --- a/docs/moonraker-capabilities.md +++ b/docs/moonraker-capabilities.md @@ -30,9 +30,10 @@ The executor reran these read-only endpoints against both printers on 2026-06-02 `v0.10.0-20-g9008485`, API `1.5.0`, 27 components, 98 objects, 60 macros, and 133 `printer.gcode.help` positives. -The full structured evidence now lives in `docs/commands/printer-matrix.json`, with the human -command-by-command view in `docs/commands/printer-availability-matrix.md`. Treat `printer.gcode.help` -as positive evidence only; missing help entries are not absence proof. The refreshed matrix reconciles +The full structured evidence lives in `docs/commands/printer-matrix.json`. A local-only generated +command-by-command availability view may also exist under `docs/commands/`, but it is evidence rather +than public-clone guidance. Treat `printer.gcode.help` as positive evidence only; missing help entries +are not absence proof. The refreshed matrix reconciles `FORCE_MOVE` from gcode-help positives, not from a nonexistent `force_move` status object. Public `LOAD_FILAMENT` and `UNLOAD_FILAMENT` macros are absent on both observed printers, so Extrude UI must continue using live macro gating/fallback behavior instead of assuming the private underscore helpers @@ -299,7 +300,8 @@ check 7 (perf) via gfxinfo. **All 7 checks PASS — functional core proven compl Method: `adb shell dumpsys gfxinfo works.mees.dinghy`, reset → ~15 s **continuous scroll** of the Console scrollback during live line flow → dump. `FrameTimingMetric`/Macrobenchmark is unreliable -on this floor, so **gfxinfo is the system-of-record** per CLAUDE.md. +on this floor, so **gfxinfo is the system of record** under the performance-floor rule in +`AGENTS.md`. **Continuous-scroll-only pass — 991 frames:** diff --git a/docs/ui_design/AGENTS.md b/docs/ui_design/AGENTS.md new file mode 100644 index 00000000..cb09bc6e --- /dev/null +++ b/docs/ui_design/AGENTS.md @@ -0,0 +1,176 @@ +# jiib UI Agent Guide + +This is the scoped source of truth for UI implementation rules. Read the files listed in +[README.md](README.md) for detailed layout, component, theme, and preview specifications. + +## Design philosophy & non-negotiables + +## Design philosophy (bake into every screen) +- **Focus / Field grammar (Gutter removed in the jiib redesign).** Every redesigned screen is + built from two regions — Focus (one primary item, square visual content centered) and Field + (a divisible info/control surface, typically a scrollable list + `FootButtonBar`). Portrait + stacks them full-width; landscape is a single grid — Focus | Field as 50/50 columns. + Either region may be omitted. No persistent status bar — context lives inside a region. + **The Gutter is NO LONGER a first-class region** for new screens: its jobs are rehomed to a + foot-of-list `FootButtonBar` (per-screen actions), a floating `FloatingEStop` overlay (Stop, + printing-only), and the System page (power / device settings). The Kotlin `ScreenScaffold` + gutter slot was DELETED outright (2026-06-12, R1 PrintStatus gutter→foot migration — the last + consumer); no screen renders a gutter region. + See the rewritten `LAYOUT.md` for the full two-region law. See `COMPONENTS.md` for the + component-class catalog (ListRow, FocusFrame, FillMeter, FootButtonBar, FloatingEStop, + SortFilterControlRow and more). +- **Layout non-negotiables (see LAYOUT.md ⚠ section):** (1) everything tabular on one shared + grid — region divides align with foot-button-row edges; (2) aspect ratios are sacred — a + square must render square (use `aspect-ratio`, center square content in its cell); the + portrait-stack / landscape-side-by-side layout difference is the mechanism that protects this; + (3) no hardcoded sizes — every dimension is a %, `fr`, `aspect-ratio`, or container-relative + unit, never absolute px (except hairlines, the touch floor, and the `--fs` text step). **Plus + the unit `U`:** every vertical element in a redesigned screen is an integer number of units U + (DPI-derived from landscape content height, constant through rotation). See `LAYOUT.md §"The + unit U"` and `COMPONENTS.md §4`. +- **Fill the usable space.** This is primarily control-surface software that doubles as a + nicely formatted display during standby / normal printing. Every screen should fill its + usable area with useful, structured information. Empty/negative space is only acceptable + when it is a *deliberate* focus choice, not a layout gap. Prefer a *useful control* over a + passive status badge, and encode status as color on an existing element (e.g. axis labels + green=homed / amber=unhomed) rather than spending a cell on an indicator. Choose items that + are genuinely useful to the screen's current function. +- **Outline-led, touch-first controls.** Interactive elements are bounded by a 2px outline + + soft glow on a transparent fill; primary/pressed states tint faintly with the accent. Touch + targets are big (≥64px tall) — built for gloved, greasy, fat-fingered taps at arm's length. +- **Structured blocks over loose pills.** Prefer structured, aligned blocks of information to + scattered chips. +- **Button intent = color, by SAFETY of the action — the FOUR-CLASS scheme (R5, 2026-06-12; + supersedes the old C1/C5/D-10 assignments).** Buttons (never list rows) are FILLED — fill is what + says "button." **Red/stop** = could be destructive (cancel, e-stop, disable steppers) · + **Amber/warning** = could be destructive but part of the process (load/heat filament, resets, + force-move-class hazards — NOT ordinary jog/home, see go) · **Green/go** = the screen's + EXPECTED action (Print, Load, Save, accept — incl. motion when motion is the screen's purpose, + R19) · **Accent** = neutral items and plain navigation (Back, Home). The old white/neutral-outline + button intent is RETIRED for actions; `Intent.Neutral` survives only as the INACTIVE + toggle-state style (R18). Overridable per case. See THEMING.md for the full law + worked examples. +- **Back = ACCENT, FIRST position (R5/R8, 2026-06-12; supersedes D-10's neutral-Back).** Plain + navigation wears the accent, and Back is always the **first (start-aligned) button** in a + `FootButtonBar`, app-wide — muscle memory holds. A Back/Cancel that DISCARDS pending input is a + cancel-with-loss and stays **stop/red** (the old C7 logic carries over). The long-deferred + app-wide Back sweep is a checklist column in the 2026-06 normalization audit. +- **Icons: never the same glyph twice on one screen.** If you'd repeat one, use a 1–3 letter + text label instead (e.g. "XY"/"Z" homes vs. arrows; chevrons for Z vs. arrows for the XY pad). +- **Icons are real Material Symbols by default** — `IconRef.Ligature` (rendered from the bundled + font) when the glyph is present, or an OFFICIAL Google vector drawable (path data verbatim, never + hand-traced) when the bundled font is too old to carry it. Hand-authored custom drawables are ONLY + for genuinely-custom printer-domain glyphs Material Symbols lacks (nozzle, bed, bed-tilt, spool). + The shape-coded status indicators (square-✕ `StatusStop` / triangle) ARE in the font and are NOT custom. Retires + D-17's "no Material Symbols font" stance — the font is already a shipped dependency used app-wide. + (The "never the same glyph twice on one screen" rule above still holds.) +- **🚫 NEVER create an icon or choose a glyph independently — ASK. (Owner law, 2026-06-07.)** Claude does + NOT invent custom drawables and does NOT pick which Material Symbol/ligature represents a function on + its own. The track record on this is bad — Claude messes it up almost every time (Phase 18.3: invented + a fake side-view spool instead of using the owner's `img/spool.svg`; earlier phases mis-chose glyphs). + The procedure when a screen needs an icon: + 1. **Check first** — is a glyph already selected for this function (in the registry `JiibIcons`, the + hi-fi mockups, an existing screen, or a source asset in `img/`)? Run `ls img/` + grep the repo for + `*.svg` and the registry before doing anything. If one exists, USE it — never substitute. + 2. **If nothing is already selected, STOP and ASK Matthew** which glyph/asset to use. Do not guess, do + not author a custom drawable, do not pick "a reasonable Material Symbol." Wait for his answer. + 3. Only author a hand-made custom drawable when Matthew has explicitly approved doing so for a specific + printer-domain glyph — and even then, prefer converting a source SVG he provides. + This rule overrides any "be proactive / use sensible defaults" instinct for icon/glyph selection. +- **Square the smallest buttons.** When a Field stacks multiple button rows, the shortest row's + height should equal its per-column width (equal spans) so the smallest targets are square. +- **Dense cells drop labels.** At ≥3 columns (portrait) / ≥6 (landscape), a cell shows a single + icon or ≤3-char value scaled to ~75% of its constraining dimension — no text labels + (`FootButtonBar` buttons keep icon+label; they are 1-per-row-third, not dense cells). When a + cell must show a value AND its source, overlay the value on a large background glyph (e.g. the + big axis letter behind the live X/Y/Z value). +- **Every screen keeps an explicit exit.** There is no global nav gesture (the swipe-up App + Drawer was deleted in Phase 28); navigation is explicit — the waterfall root reaches screens, + and each screen's `FootButtonBar` carries Back (first position). A screen without a foot bar + must still have an explicit way out. (Supersedes the old "scrollable Fields suppress the + swipe-up drawer" rule, which guarded an affordance that no longer exists.) +- **Content images fit, they don't crop.** A thumbnail/preview shown as content — including as a + dimmed card background — uses `Fit` so the WHOLE image is visible, centered/letterboxed on the + surface. `Crop` zooms into a center strip in the tall, narrow landscape Focus/Field panes (it + violates sacred aspect ratios, LAYOUT §2). The one exception is a deliberately *circular* fill + (the Print Status ring center), which is meant to be filled. (2026-06-02.) +- **Image-backed info card (shared grammar).** A panel pairing a preview with details uses ONE + grammar: the dimmed thumbnail as the background with left-aligned, vertically-centered icon-led + stat lines overlaid (the "overlay the value on a large background" rule applied to a whole card). + Shared by Print Status' last-job card and the Files Focus — they differ only in WHICH fields show + (Files Focus = future-print fields only: est time · filament · layers · height · size · modified — + never elapsed/finished/status, which are job history). Filenames render in Geist Mono (a filename + is a data value). (2026-06-02.) +- **Panel text fills the box.** A text block in a panel takes the FULL width of its cell, never an + arbitrary inner ruler width. Size the text to fit up to the box; only a single line that genuinely + overflows the full width may marquee-scroll. (Locking scroll lines to a narrower width reads as + "stopping mid-screen.") (2026-06-02.) +- **Theme-able via tokens.** Dark-first baseline; everything routes through semantic tokens + (`--bg/--surface/--text/--outline/--accent/--heat/--go/--stop` …). See THEMING.md. +- **Token carve-out: macro-authored PromptMarkup author-hex (D-03) AND the color-reactive spool spiral + (18.3 D-10).** The Macro Prompt Protocol (Phase 12) renders a Klipper macro author's inline + ``/`` text runs as the author's EXACT literal hex — NOT a role token. This is + a *bounded* exception to the tokens-only law: the hex is content DATA the author chose, not chrome. It + is scoped to `PromptMarkupText` text runs ONLY — all prompt chrome (dialog bg, header, button + outlines/intents, close, toasts, Field) stays token-routed, and the semantic button styles still map + to tokens (`promptStyleColor`). **The same carve-out covers the custom spool glyph's filament spiral + (Phase 18.3, converted from the owner's front-view `img/spool.svg`):** the spool's wound-filament + **spiral** (the coil seen through the body windows) is tinted by the loaded filament's actual color + (the Spoolman-spool-color precedent made literal, D-02/D-05/D-10) — true hex, never clamped. That + reactivity is scoped to the **SPIRAL ONLY**; the spool body disc, the spiral's neutral **keyline**, and + all surrounding chrome stay token-routed (`--text`/`--text2`/`--outline`), and the empty-spool fallback + draws no spiral (pure token chrome — an empty windowed disc). `brandTint`'s WCAG clamp was considered + and **rejected** for the spiral (it would distort the true color; legibility comes from the keyline, + not distortion) — `brandTint` stays for brand chrome only. The Phase-21 conformance audit treats BOTH + as sanctioned, not a violation. + See THEMING.md → "Carve-out: macro-authored PromptMarkup author-hex". (2026-06-04; 12-05 · 2026-06-07; 18.3-03.) +- **Keyboard carve-out: text-entry fields.** The keyboard is used **sparingly, where text entry is + the honest input** — the connection editor (host, name, API key, Advanced URL), Settings, and Save-name + fields (e.g., bed-mesh **Save-name** dialog, pre-filled `YY.MM.DD_HH.MM`). These are text-first entries, + not hot-path controls. The input is allowlist-validated (`[A-Za-z0-9_.-]+` for filenames; relaxed for + hostname/URL). Numeric controls and adjusters still prefer stepper/scrubber; keyboard is not used for + numeric-only inputs. (Owner decision, 2026-06-02 · relaxed 2026-06-xx for connection editor redesign.) +- **Delete is scoped to the active print file, not idle-only.** This **supersedes** the original + "Delete is idle-only" rule. During a print, ONLY the currently-printing file + (`print_stats.filename`) is undeletable; every other file stays deletable, even mid-print. When the + printer is idle, all files are deletable. The scoping check is the pure host-tested predicate + `deleteAllowed(selectedPath, activePrintFilename, printState)` — applied at BOTH the FilesScreen + delete-enabled gate and the FileBrowserHolder dispatch gate (one shared helper so the path-form + match can't regress; the relative, no-leading-`gcodes/` form matches `print_stats.filename`). The + old blanket "block all deletes during any print" behavior was a Phase-7 UAT defect (D-15). + (2026-06-02; 09-06.) + +- **Recovery routing: a socket reconnect now shows the full Syncing Splash.** This **supersedes** the + earlier "socket `ConnectionState` is chrome, never routes" rule (the old D-05). As of Phase 13, the + top-level route derivation (`ui/route/TopRoute.derive`) routes the **full recovery Splash** when the + socket is mid-reconnect (`connection !is Connected`) with a config present and klippy otherwise Ready — + in addition to the klippy-not-Ready case. So a **silent mid-print network drop is visibly non-silent**: + a half-open WiFi drop (detected by the new OkHttp `pingInterval` keepalive) raises the Syncing Splash, + then resyncs. This is SAFE — and does NOT bounce the user off their screen — because the shell nav + state (`dest` + back-stack + in-progress calibration routine) was **hoisted above the Splash/Shell + switch** (`ShellNavState`, owned by `RootController`); the user returns to the screen they were on, not + Home. The recovery Splash also has a **minimum perceptible dwell** (~600ms, a `RootController`-owned UI + latch that only delays HIDING the splash, never the actual recovery) so a fast recovery is still seen. + The Splash's Disconnected/Error "Unreachable" surface (Retry + Edit connection) is preserved, so a + printer that is simply OFF stays reachable, not an eternal dead "Syncing". (Matthew, 2026-06-03; 13-05.) + +## Visual language +- Type: Geist + Geist Mono (tabular numerals for live data). Type ramp: see THEMING.md §"The + type ramp" (R11 — 20sp list/button default). +- Accent: seed-generated signature color (the user's). Heat = the CAUTION/warning color (D-13), no + longer a nozzle/bed identity — temperature identity rides `directional.temperature` (= accent) + + the data pool. Go/Stop: green/red (user-overridable; shape carries safety). Surfaces are + pure-neutral (D-16). +- Shape: soft — 22dp cards, 16dp controls, pill chips; status safety carried by SHAPE + (square-✕ `StatusStop` = stop, triangle-! = caution; go is shapeless). See THEMING.md. +- Motion: alive but cheap — progress fills, ring draws on; NO continuous breathing/looping + animation (Adreno-320 budget). "Glow" as a real blur is not on the API floor; glow tokens render + as static alpha treatments (R6 — a true cached glow is aspirational polish, not law). + **Sanctioned `basicMarquee` exceptions (overflow-only, never decorative):** + - **`FocusFrame` header title** (Focus-header law, 2026-06-13) — the centered title scrolls + horizontally via `Modifier.basicMarquee()` when it overflows. Single-line, overflow-only, + tiny dirty-rect (one row, one direction). Driven by the printing job-filename title. + (The LED `ColorWheel` was retired 2026-06-21 — Outputs LEDs use static H/S/V(+W) slider tracks.) + Any new continuous animation must name a sanctioned exception here before ship. +- North star of record: the as-built shared components and screens plus `THEMING.md`. Local sketches + and the old hi-fi bundle (`reference/hifi.css`, `Print Status Hi-Fi.html`, `images/`) are + HISTORICAL; do not build from them or let them override tracked design documents. diff --git a/docs/ui_design/CLAUDE.md b/docs/ui_design/CLAUDE.md index a4937e4e..202d1906 100644 --- a/docs/ui_design/CLAUDE.md +++ b/docs/ui_design/CLAUDE.md @@ -1,172 +1,4 @@ -# jiib — design philosophy & non-negotiables +# Claude Code UI Instructions -## Design philosophy (bake into every screen) -- **Focus / Field grammar (Gutter removed in the jiib redesign).** Every redesigned screen is - built from two regions — Focus (one primary item, square visual content centered) and Field - (a divisible info/control surface, typically a scrollable list + `FootButtonBar`). Portrait - stacks them full-width; landscape is a single grid — Focus | Field as 50/50 columns. - Either region may be omitted. No persistent status bar — context lives inside a region. - **The Gutter is NO LONGER a first-class region** for new screens: its jobs are rehomed to a - foot-of-list `FootButtonBar` (per-screen actions), a floating `FloatingEStop` overlay (Stop, - printing-only), and the System page (power / device settings). The Kotlin `ScreenScaffold` - gutter slot was DELETED outright (2026-06-12, R1 PrintStatus gutter→foot migration — the last - consumer); no screen renders a gutter region. - See the rewritten `LAYOUT.md` for the full two-region law. See `COMPONENTS.md` for the - component-class catalog (ListRow, FocusFrame, FillMeter, FootButtonBar, FloatingEStop, - SortFilterControlRow and more). -- **Layout non-negotiables (see LAYOUT.md ⚠ section):** (1) everything tabular on one shared - grid — region divides align with foot-button-row edges; (2) aspect ratios are sacred — a - square must render square (use `aspect-ratio`, center square content in its cell); the - portrait-stack / landscape-side-by-side layout difference is the mechanism that protects this; - (3) no hardcoded sizes — every dimension is a %, `fr`, `aspect-ratio`, or container-relative - unit, never absolute px (except hairlines, the touch floor, and the `--fs` text step). **Plus - the unit `U`:** every vertical element in a redesigned screen is an integer number of units U - (DPI-derived from landscape content height, constant through rotation). See `LAYOUT.md §"The - unit U"` and `COMPONENTS.md §4`. -- **Fill the usable space.** This is primarily control-surface software that doubles as a - nicely formatted display during standby / normal printing. Every screen should fill its - usable area with useful, structured information. Empty/negative space is only acceptable - when it is a *deliberate* focus choice, not a layout gap. Prefer a *useful control* over a - passive status badge, and encode status as color on an existing element (e.g. axis labels - green=homed / amber=unhomed) rather than spending a cell on an indicator. Choose items that - are genuinely useful to the screen's current function. -- **Outline-led, touch-first controls.** Interactive elements are bounded by a 2px outline + - soft glow on a transparent fill; primary/pressed states tint faintly with the accent. Touch - targets are big (≥64px tall) — built for gloved, greasy, fat-fingered taps at arm's length. -- **Structured blocks over loose pills.** Prefer structured, aligned blocks of information to - scattered chips. -- **Button intent = color, by SAFETY of the action — the FOUR-CLASS scheme (R5, 2026-06-12; - supersedes the old C1/C5/D-10 assignments).** Buttons (never list rows) are FILLED — fill is what - says "button." **Red/stop** = could be destructive (cancel, e-stop, disable steppers) · - **Amber/warning** = could be destructive but part of the process (load/heat filament, resets, - force-move-class hazards — NOT ordinary jog/home, see go) · **Green/go** = the screen's - EXPECTED action (Print, Load, Save, accept — incl. motion when motion is the screen's purpose, - R19) · **Accent** = neutral items and plain navigation (Back, Home). The old white/neutral-outline - button intent is RETIRED for actions; `Intent.Neutral` survives only as the INACTIVE - toggle-state style (R18). Overridable per case. See THEMING.md for the full law + worked examples. -- **Back = ACCENT, FIRST position (R5/R8, 2026-06-12; supersedes D-10's neutral-Back).** Plain - navigation wears the accent, and Back is always the **first (start-aligned) button** in a - `FootButtonBar`, app-wide — muscle memory holds. A Back/Cancel that DISCARDS pending input is a - cancel-with-loss and stays **stop/red** (the old C7 logic carries over). The long-deferred - app-wide Back sweep is a checklist column in the 2026-06 normalization audit. -- **Icons: never the same glyph twice on one screen.** If you'd repeat one, use a 1–3 letter - text label instead (e.g. "XY"/"Z" homes vs. arrows; chevrons for Z vs. arrows for the XY pad). -- **Icons are real Material Symbols by default** — `IconRef.Ligature` (rendered from the bundled - font) when the glyph is present, or an OFFICIAL Google vector drawable (path data verbatim, never - hand-traced) when the bundled font is too old to carry it. Hand-authored custom drawables are ONLY - for genuinely-custom printer-domain glyphs Material Symbols lacks (nozzle, bed, bed-tilt, spool). - The shape-coded status indicators (square-✕ `StatusStop` / triangle) ARE in the font and are NOT custom. Retires - D-17's "no Material Symbols font" stance — the font is already a shipped dependency used app-wide. - (The "never the same glyph twice on one screen" rule above still holds.) -- **🚫 NEVER create an icon or choose a glyph independently — ASK. (Owner law, 2026-06-07.)** Claude does - NOT invent custom drawables and does NOT pick which Material Symbol/ligature represents a function on - its own. The track record on this is bad — Claude messes it up almost every time (Phase 18.3: invented - a fake side-view spool instead of using the owner's `img/spool.svg`; earlier phases mis-chose glyphs). - The procedure when a screen needs an icon: - 1. **Check first** — is a glyph already selected for this function (in the registry `JiibIcons`, the - hi-fi mockups, an existing screen, or a source asset in `img/`)? Run `ls img/` + grep the repo for - `*.svg` and the registry before doing anything. If one exists, USE it — never substitute. - 2. **If nothing is already selected, STOP and ASK Matthew** which glyph/asset to use. Do not guess, do - not author a custom drawable, do not pick "a reasonable Material Symbol." Wait for his answer. - 3. Only author a hand-made custom drawable when Matthew has explicitly approved doing so for a specific - printer-domain glyph — and even then, prefer converting a source SVG he provides. - This rule overrides any "be proactive / use sensible defaults" instinct for icon/glyph selection. -- **Square the smallest buttons.** When a Field stacks multiple button rows, the shortest row's - height should equal its per-column width (equal spans) so the smallest targets are square. -- **Dense cells drop labels.** At ≥3 columns (portrait) / ≥6 (landscape), a cell shows a single - icon or ≤3-char value scaled to ~75% of its constraining dimension — no text labels - (`FootButtonBar` buttons keep icon+label; they are 1-per-row-third, not dense cells). When a - cell must show a value AND its source, overlay the value on a large background glyph (e.g. the - big axis letter behind the live X/Y/Z value). -- **Every screen keeps an explicit exit.** There is no global nav gesture (the swipe-up App - Drawer was deleted in Phase 28); navigation is explicit — the waterfall root reaches screens, - and each screen's `FootButtonBar` carries Back (first position). A screen without a foot bar - must still have an explicit way out. (Supersedes the old "scrollable Fields suppress the - swipe-up drawer" rule, which guarded an affordance that no longer exists.) -- **Content images fit, they don't crop.** A thumbnail/preview shown as content — including as a - dimmed card background — uses `Fit` so the WHOLE image is visible, centered/letterboxed on the - surface. `Crop` zooms into a center strip in the tall, narrow landscape Focus/Field panes (it - violates sacred aspect ratios, LAYOUT §2). The one exception is a deliberately *circular* fill - (the Print Status ring center), which is meant to be filled. (2026-06-02.) -- **Image-backed info card (shared grammar).** A panel pairing a preview with details uses ONE - grammar: the dimmed thumbnail as the background with left-aligned, vertically-centered icon-led - stat lines overlaid (the "overlay the value on a large background" rule applied to a whole card). - Shared by Print Status' last-job card and the Files Focus — they differ only in WHICH fields show - (Files Focus = future-print fields only: est time · filament · layers · height · size · modified — - never elapsed/finished/status, which are job history). Filenames render in Geist Mono (a filename - is a data value). (2026-06-02.) -- **Panel text fills the box.** A text block in a panel takes the FULL width of its cell, never an - arbitrary inner ruler width. Size the text to fit up to the box; only a single line that genuinely - overflows the full width may marquee-scroll. (Locking scroll lines to a narrower width reads as - "stopping mid-screen.") (2026-06-02.) -- **Theme-able via tokens.** Dark-first baseline; everything routes through semantic tokens - (`--bg/--surface/--text/--outline/--accent/--heat/--go/--stop` …). See THEMING.md. -- **Token carve-out: macro-authored PromptMarkup author-hex (D-03) AND the color-reactive spool spiral - (18.3 D-10).** The Macro Prompt Protocol (Phase 12) renders a Klipper macro author's inline - ``/`` text runs as the author's EXACT literal hex — NOT a role token. This is - a *bounded* exception to the tokens-only law: the hex is content DATA the author chose, not chrome. It - is scoped to `PromptMarkupText` text runs ONLY — all prompt chrome (dialog bg, header, button - outlines/intents, close, toasts, Field) stays token-routed, and the semantic button styles still map - to tokens (`promptStyleColor`). **The same carve-out covers the custom spool glyph's filament spiral - (Phase 18.3, converted from the owner's front-view `img/spool.svg`):** the spool's wound-filament - **spiral** (the coil seen through the body windows) is tinted by the loaded filament's actual color - (the Spoolman-spool-color precedent made literal, D-02/D-05/D-10) — true hex, never clamped. That - reactivity is scoped to the **SPIRAL ONLY**; the spool body disc, the spiral's neutral **keyline**, and - all surrounding chrome stay token-routed (`--text`/`--text2`/`--outline`), and the empty-spool fallback - draws no spiral (pure token chrome — an empty windowed disc). `brandTint`'s WCAG clamp was considered - and **rejected** for the spiral (it would distort the true color; legibility comes from the keyline, - not distortion) — `brandTint` stays for brand chrome only. The Phase-21 conformance audit treats BOTH - as sanctioned, not a violation. - See THEMING.md → "Carve-out: macro-authored PromptMarkup author-hex". (2026-06-04; 12-05 · 2026-06-07; 18.3-03.) -- **Keyboard carve-out: text-entry fields.** The keyboard is used **sparingly, where text entry is - the honest input** — the connection editor (host, name, API key, Advanced URL), Settings, and Save-name - fields (e.g., bed-mesh **Save-name** dialog, pre-filled `YY.MM.DD_HH.MM`). These are text-first entries, - not hot-path controls. The input is allowlist-validated (`[A-Za-z0-9_.-]+` for filenames; relaxed for - hostname/URL). Numeric controls and adjusters still prefer stepper/scrubber; keyboard is not used for - numeric-only inputs. (Owner decision, 2026-06-02 · relaxed 2026-06-xx for connection editor redesign.) -- **Delete is scoped to the active print file, not idle-only.** This **supersedes** the original - "Delete is idle-only" rule. During a print, ONLY the currently-printing file - (`print_stats.filename`) is undeletable; every other file stays deletable, even mid-print. When the - printer is idle, all files are deletable. The scoping check is the pure host-tested predicate - `deleteAllowed(selectedPath, activePrintFilename, printState)` — applied at BOTH the FilesScreen - delete-enabled gate and the FileBrowserHolder dispatch gate (one shared helper so the path-form - match can't regress; the relative, no-leading-`gcodes/` form matches `print_stats.filename`). The - old blanket "block all deletes during any print" behavior was a Phase-7 UAT defect (D-15). - (2026-06-02; 09-06.) - -- **Recovery routing: a socket reconnect now shows the full Syncing Splash.** This **supersedes** the - earlier "socket `ConnectionState` is chrome, never routes" rule (the old D-05). As of Phase 13, the - top-level route derivation (`ui/route/TopRoute.derive`) routes the **full recovery Splash** when the - socket is mid-reconnect (`connection !is Connected`) with a config present and klippy otherwise Ready — - in addition to the klippy-not-Ready case. So a **silent mid-print network drop is visibly non-silent**: - a half-open WiFi drop (detected by the new OkHttp `pingInterval` keepalive) raises the Syncing Splash, - then resyncs. This is SAFE — and does NOT bounce the user off their screen — because the shell nav - state (`dest` + back-stack + in-progress calibration routine) was **hoisted above the Splash/Shell - switch** (`ShellNavState`, owned by `RootController`); the user returns to the screen they were on, not - Home. The recovery Splash also has a **minimum perceptible dwell** (~600ms, a `RootController`-owned UI - latch that only delays HIDING the splash, never the actual recovery) so a fast recovery is still seen. - The Splash's Disconnected/Error "Unreachable" surface (Retry + Edit connection) is preserved, so a - printer that is simply OFF stays reachable, not an eternal dead "Syncing". (Matthew, 2026-06-03; 13-05.) - -## Visual language -- Type: Geist + Geist Mono (tabular numerals for live data). Type ramp: see THEMING.md §"The - type ramp" (R11 — 20sp list/button default). -- Accent: seed-generated signature color (the user's). Heat = the CAUTION/warning color (D-13), no - longer a nozzle/bed identity — temperature identity rides `directional.temperature` (= accent) + - the data pool. Go/Stop: green/red (user-overridable; shape carries safety). Surfaces are - pure-neutral (D-16). -- Shape: soft — 22dp cards, 16dp controls, pill chips; status safety carried by SHAPE - (square-✕ `StatusStop` = stop, triangle-! = caution; go is shapeless). See THEMING.md. -- Motion: alive but cheap — progress fills, ring draws on; NO continuous breathing/looping - animation (Adreno-320 budget). "Glow" as a real blur is not on the API floor; glow tokens render - as static alpha treatments (R6 — a true cached glow is aspirational polish, not law). - **Sanctioned `basicMarquee` exceptions (overflow-only, never decorative):** - - **`FocusFrame` header title** (Focus-header law, 2026-06-13) — the centered title scrolls - horizontally via `Modifier.basicMarquee()` when it overflows. Single-line, overflow-only, - tiny dirty-rect (one row, one direction). Driven by the printing job-filename title. - (The LED `ColorWheel` was retired 2026-06-21 — Outputs LEDs use static H/S/V(+W) slider tracks.) - Any new continuous animation must name a sanctioned exception here before ship. -- North star of record: the as-built Spoolman screen + sketch sources - (`.claude/skills/sketch-findings-jiib/sources/`) + THEMING.md. The old hi-fi bundle - (`reference/hifi.css`, `Print Status Hi-Fi.html`, `images/`) is HISTORICAL — superseded by the - 2026-06-09 jiib redesign; do not build from it. +Read and follow [AGENTS.md](AGENTS.md). It is the scoped source of truth for jiib UI work. This file +intentionally adds no duplicate design rules. diff --git a/docs/ui_design/COMPONENTS.md b/docs/ui_design/COMPONENTS.md index eeed1a4a..fc2f3495 100644 --- a/docs/ui_design/COMPONENTS.md +++ b/docs/ui_design/COMPONENTS.md @@ -105,7 +105,8 @@ Geist Mono at the call site (values, not labels). #### `FocusFrame` -**THE universal Focus container** (Focus Frame law, `.planning/notes/2026-06-12-focus-frame-law-design.md`). +**THE universal Focus container** (Focus Frame law; see `LAYOUT.md` and the tracked +`designsystem/components/FocusFrame.kt` implementation). Every screen's Focus region is a `FocusFrame` — **except Webcam**, which stays full-bleed native media (the one exemption). Renamed from the old `DetailCard`. Filled background (`t.surface` — visually distinct from the translucent list/Field area), corner radius `t.rCard` (22dp). `FocusFrame` is @@ -142,7 +143,7 @@ header is always present — idle and printing alike. It contains: - **Centered title** — rendered with `Modifier.basicMarquee()`. The title is ALWAYS single-line; it scrolls horizontally on overflow — **NO shrink, NO ellipsis, NO wrap.** This is a **sanctioned exception to the no-continuous-animation motion law** (overflow-only, single-line, - tiny dirty-rect — see `CLAUDE.md §Motion`). Drives the printing job-filename title. + tiny dirty-rect — see `AGENTS.md §Motion`). Drives the printing job-filename title. **Header/content divider (2026-06-15 owner UAT):** @@ -625,7 +626,7 @@ canonical `FootButtonBar` usage. All icons on all screens come from `JiibIcons.kt` or are requested via the owner. **Never auto-pick a Material Symbol or create a custom drawable independently.** See -`docs/ui_design/CLAUDE.md §"Icons: never the same glyph twice…"` and the never-auto-pick law +`docs/ui_design/AGENTS.md §"Icons: never the same glyph twice…"` and the never-auto-pick law that follows it. This applies to the SortFilterControlRow Sort direction-overlay glyph (`SortAsc`/`SortDesc`; the leading type-tile is retired), FocusFrame glyphs, and every other use. @@ -665,9 +666,9 @@ icon tiles, count-driven labels"). Key invariants (leading TYPE tile RETIRED 202 ### Stepper and AdjusterPanel -A step-based adjuster (preferred over drag on the perf floor). See -`.claude/skills/sketch-findings-jiib/references/adjustment-controls.md` for the locked -design. Phase 26 executed the adjustment-screen rebuild (Fine-Tune, Temperature, Outputs) on it. +A step-based adjuster (preferred over drag on the performance floor). The locked design is specified +below and implemented by the tracked shared adjustment components. Fine-Tune, Temperature, and +Outputs use it. **AdjusterPanel — two-zone layout (compliance pass, 2026-06-13):** @@ -713,7 +714,7 @@ future selected/toggle state that needs a custom fill rather than just a border **Pressed state — soft intent tint (LAW, owner UAT 2026-06-17):** -Makes concrete the design-CLAUDE.md intent "primary/pressed states tint faintly with the +Makes concrete the design `AGENTS.md` intent "primary/pressed states tint faintly with the accent" — generalized to the button's own **intent** color (per the R5 four-class scheme). Every `OutlinedControl` (so every button app-wide) signals touch-down by **filling its background with a SOFT tint of its intent color**, keeping the full intent color on the border — @@ -775,7 +776,7 @@ The canonical drag-adjust control, owner-referenced Android-SeekBar style ported - **Load-bearing impl rule (the `fa97efb` lesson):** build the element ONCE; update fill width, thumb position, and value **in place** during drag. NEVER rebuild/recompose the dragged element per move — it detaches the node / stales the drag closure and the gesture dies. -- Source sketch: `.claude/skills/sketch-findings-jiib/sources/004-scrubber-style/`. +- Canonical implementation: `designsystem/components/Scrubber.kt`. **Status (R9 — migration COMPLETE 2026-06-12):** the canonical implementation is `designsystem/components/Scrubber.kt`. The legacy fill-bar composables (`ScrubberPage`, then @@ -875,5 +876,5 @@ Do **not** add speculative classes before they are needed by a real screen. |---|---| | `LAYOUT.md` | Spatial grammar (Focus/Field), unit U formula, fill convention in layout context, NON-NEGOTIABLES | | `THEMING.md` | Every token name, intent colors, button-intent-by-safety rule, C-series conformance criteria | -| `CLAUDE.md` | Design philosophy, icon law (never-auto-pick), token carve-outs | +| `AGENTS.md` | Design philosophy, icon law (never-auto-pick), token carve-outs | | `PREVIEW_AND_TOKENS.md` | Preview-matrix convention, `@Preview` matrix shape, `fsSp` usage rules | diff --git a/docs/ui_design/README.md b/docs/ui_design/README.md index c5ccf3a6..2971572f 100644 --- a/docs/ui_design/README.md +++ b/docs/ui_design/README.md @@ -1,10 +1,9 @@ # jiib — UI design system (front door) -> **Rewritten 2026-06-12** as part of the visual-normalization sweep (rulings R1–R14, -> `.planning/notes/2026-06-12-spec-recon-inventory.md`). The previous README was the original -> 2026-05-31 design-handoff bundle; it described the pre-redesign three-region grammar and is -> preserved in git history only. Where any doc in this directory contradicts a newer one, the -> doc with the later "supersedes" header wins; file a correction. +This directory is the tracked source of truth for jiib's UI design system. Follow the reading order +below. If two current design documents conflict, verify the as-built components and tests, then fix +the conflicting document; do not choose guidance merely because it has a newer date. Historical +design material is identified explicitly and never overrides the five current documents below. jiib is a touchscreen control surface for **Klipper / Moonraker** 3D printers: phones through tablets, portrait and landscape, dark + light + user-custom themes, S/M/L text size. It is @@ -13,7 +12,7 @@ print. The **Nexus 7 2013 (Adreno 320)** is the performance floor. ## Reading order (the law, in order) -1. **`CLAUDE.md`** — design philosophy + non-negotiables (icon law, carve-outs, fill-the-space). +1. **`AGENTS.md`** — design philosophy + non-negotiables (icon law, carve-outs, fill-the-space). 2. **`LAYOUT.md`** — the two-region **Focus / Field** grammar, the unit **U**, the NON-NEGOTIABLES (shared grid, sacred aspect ratios, ratio-only sizing), UAT-1..5. 3. **`COMPONENTS.md`** — the component-class catalog (ListRow, FocusFrame, FootButtonBar, @@ -49,10 +48,9 @@ Neutral/outline is retired as a button intent. Back is always the **first** butt ## Visual north star of record -The **as-built Spoolman screen**, the **jiib sketch sources** -(`.claude/skills/sketch-findings-jiib/sources/`, incl. `themes/default.css` and the -`004-scrubber-style` control), and the **`THEMING.md` token law**. Type is Geist + Geist Mono -(tabular numerals for live data). +The **as-built shared components and screens** and the **`THEMING.md` token law**. Type is Geist + +Geist Mono (tabular numerals for live data). Local sketches may explain how a decision emerged, but +they are historical context and do not override tracked design documents or the implementation. ## Historical reference (superseded — do not build from these) @@ -65,6 +63,6 @@ for archaeology only; nothing in them overrides the five docs above. ## Assets - **Fonts:** Geist + Geist Mono (bundled). Material Symbols Outlined font for icons, rendered - by ligature via the `JiibIcons` registry — never auto-pick a glyph (owner law, `CLAUDE.md`). + by ligature via the `JiibIcons` registry — never auto-pick a glyph (owner law, `AGENTS.md`). - **Custom printer-domain glyphs** (nozzle, bed, bed-tilt, spool) are owner-approved vector drawables converted from `img/*.svg` source art. diff --git a/docs/ui_design/THEMING.md b/docs/ui_design/THEMING.md index 81a57ab9..2c8bc1a6 100644 --- a/docs/ui_design/THEMING.md +++ b/docs/ui_design/THEMING.md @@ -166,8 +166,7 @@ pending result** is a cancel-with-loss and stays **red** (`stop`) — e.g. `Meas > These criteria were surfaced by the **D-10 guided on-device review** of the core daily-driver > screens (15.2-05) and are now LAW. They sharpen the "Button intent = color" and Status rules into -> **testable conformance checks** so the 15.2 app-wide sweep (and every future phase) audits against -> them. Each maps to a column / finding in `.planning/phases/15.2-…/15.2-AUDIT.md`. +> **testable conformance checks** for future app-wide reviews. - **C1 — Expected action wears GO, including motion when motion is the screen's purpose (REWRITTEN per R5 + R19, 2026-06-12).** A button that performs a screen's EXPECTED action uses diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 00000000..8a08973d --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) +cd "$repo_root" + +mode=all +case "${1:-}" in + "") ;; + --docs-only) mode=docs ;; + --gradle-only) mode=gradle ;; + -h|--help) + printf '%s\n' \ + 'Usage: ./scripts/check.sh [--docs-only|--gradle-only]' \ + ' no option Run documentation hygiene and the Gradle CI gate.' \ + ' --docs-only Check tracked Markdown links, stale phrases, and instruction pointers.' \ + ' --gradle-only Run unit tests, lint, and the debug build.' + exit 0 + ;; + *) + printf 'Unknown option: %s\n' "$1" >&2 + exit 2 + ;; +esac + +check_docs() { + local stale_pattern='compileSdk 35|Architecture not yet mapped' + local dead_links + local src dir target + local -a sources instruction_files + + mapfile -t sources < <(git ls-files '*.md') + if ((${#sources[@]} == 0)); then + printf '%s\n' 'FAIL: no tracked Markdown files found' >&2 + return 1 + fi + + if grep -En "$stale_pattern" \ + AGENTS.md README.md docs/maintainer/*.md docs/ui_design/*.md; then + printf '%s\n' 'FAIL: stale phrase found in authoritative documentation' >&2 + return 1 + fi + + dead_links=$(mktemp) + trap 'rm -f "$dead_links"' RETURN + for src in "${sources[@]}"; do + [[ -f "$src" ]] || continue + dir=$(dirname "$src") + while IFS= read -r target; do + case "$target" in + http://*|https://*|*://*|/*|[A-Za-z]:/*|*\\*) continue ;; + esac + if [[ ! -e "$dir/$target" && ! -e "$target" ]]; then + printf 'DEAD LINK in %s -> %s\n' "$src" "$target" | tee -a "$dead_links" + fi + done < <( + { grep -oE '\]\([^)# ]+\.md(#[^)]*)?\)' "$src" || true; } \ + | sed -E 's/^\]\(//; s/#[^)]*//; s/\)$//' \ + | sort -u + ) + done + if [[ -s "$dead_links" ]]; then + printf '%s\n' 'FAIL: dead internal Markdown links found' >&2 + return 1 + fi + + mapfile -t instruction_files < <(git ls-files 'CLAUDE.md' ':(glob)**/CLAUDE.md') + for src in "${instruction_files[@]}"; do + if (( $(wc -l < "$src") > 10 )) || ! grep -q 'AGENTS\.md' "$src"; then + printf 'FAIL: %s must remain a short pointer to its AGENTS.md\n' "$src" >&2 + return 1 + fi + done + + printf '%s\n' 'OK: documentation hygiene' +} + +check_gradle() { + local -a tasks=( + :app:testDebugUnitTest + :app:lintDebug + :app:assembleDebug + --no-daemon + ) + + if [[ -r /proc/version ]] \ + && grep -qi microsoft /proc/version \ + && [[ -x /mnt/c/Windows/System32/cmd.exe ]] \ + && [[ -f /mnt/e/Android/gw.bat ]]; then + /mnt/c/Windows/System32/cmd.exe /c \ + 'E:\Android\gw.bat :app:testDebugUnitTest :app:lintDebug :app:assembleDebug --no-daemon' + else + ./gradlew "${tasks[@]}" + fi +} + +if [[ "$mode" == all || "$mode" == docs ]]; then + check_docs +fi +if [[ "$mode" == all || "$mode" == gradle ]]; then + check_gradle +fi