Files
grim/DEVELOPING.md
T
Claude 0438d70cae Build 18: move the integrated node off the wallet list into the cog
- Remove the Node status chip from the wallet-list header; the integrated
  node now lives in the cog's settings as a status + Enable/Autorun section,
  with a "Node settings" button into the full stats/mining/tuning panel.
- Stop the desktop two-column rail from auto-docking the node beside the
  settings screen, so the node has a single home in the cog at every width
  (the full panel opens only on demand). Fixes the wide-window double-exposure.
- Prove NIP-17 payments transit the top public relays: parameterize the
  nostr e2e roundtrip and add damus.io + nos.lol proofs (3/3 green).
- Add DEVELOPING.md documenting how we iterate and test (Xwayland :2 recipe,
  Build N cadence, gui-sweep, e2e tests, live infra) — no secrets.

Verified live on :2: chip gone from the list; cog shows the node once
(single column, no left rail); "Node settings" opens the full panel on
demand. 34 lib tests green; nip17 roundtrip green over nrelay + damus + nos.lol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 20:21:13 -04:00

9.0 KiB

Developing & testing Goblin

How this fork is actually iterated on, built, driven, and verified — so you can pick development back up after a /clear without re-deriving the loop. This is the dev/test workflow, not an architecture tour (for that see HANDOFF.md, the ~/.claude/.../memory/ notes, and the code map in goblin-implementation-state).

Secrets stay out of this file. Test-wallet seed phrases and passwords are never committed. They live in the local handoff notes only. This doc refers to wallets by role.


1. Workspace layout

Everything lives under …/Goblin Project/:

Dir What it is
goblin/ The fork you edit. Crate name grim, binary goblin.
goblin-nip05d/ The NIP-05 identity server (axum + sqlite), deployed at goblin.st.
grim/, grin/, egui/, android-activity/, nokhwa/ Reference clones — not build inputs, just there to read upstream source.

Inside goblin/src/gui/views/:

  • goblin/ — the Cash App-style surfaces (home/activity/send/receive/me, onboarding, the Goblin settings pages, widgets).
  • wallets/ — the wallet list + open-wallet host (returning-user list lives in wallets/content.rs).
  • network/ — the integrated GRIN node panel (stats/mining/tuning/recovery).
  • settings/ — the app-level cog settings (settings/content.rs).

First build needs submodules: git submodule update --init --recursive inside goblin/ (else grin_api fails). As butler, the rustup toolchain is in $HOME, so cargo/rustc/rustfmt just work — no env overrides.


2. Build & versioning

cd goblin
cargo build            # ~15s incremental debug; clean release ~2m

Builds are numbered, not semver'd. build.rs computes Build N = git rev-list --count b51a46b..HEAD (the GRIM fork base) and exposes it as the GOBLIN_BUILD env var baked into the binary. Each meaningful change is committed with the message Build N. To see the next number:

git rev-list --count b51a46b..HEAD

A pre-commit hook runs rustfmt --check and aborts if anything is unformatted (it reformats first). Always run rustfmt --edition 2024 <changed files> before committing. Pushing origin master is the owner's job.


3. Driving the real app for screenshots (Xwayland :2)

The desktop session is KDE Wayland, where xdotool input injection does not work (and Parsec may hold the live display). The validated recipe is a throwaway rootful Xwayland server on display :2:

# 1. Stand up an isolated X server (once per session; no window manager inside).
Xwayland :2 -geometry 1600x1000 -decorate -noreset &

# 2. Launch Goblin onto it. MUST unset WAYLAND_DISPLAY or winit picks Wayland.
env -u WAYLAND_DISPLAY DISPLAY=:2 ./target/debug/goblin &

# 3. Find the window, focus it (no WM => keys go nowhere until you focus once).
WID=$(DISPLAY=:2 xdotool search --name Goblin | head -1)
DISPLAY=:2 xdotool windowfocus "$WID"

# 4. Drive it.
DISPLAY=:2 xdotool mousemove <x> <y> click 1
DISPLAY=:2 xdotool type 'hello'
DISPLAY=:2 xdotool key Return

# 5. Capture (ImageMagick). No WM => window sits at 0,0, so image coords == click coords.
DISPLAY=:2 import -window "$WID" /tmp/shot.png

Form factors — resize the window to test responsive layouts:

DISPLAY=:2 xdotool windowsize "$WID" 390 844     # mobile (bottom tabs)
DISPLAY=:2 xdotool windowsize "$WID" 1280 800    # desktop sidebar (>=720px wide)

The layout flips between mobile bottom-tabs and desktop sidebar around 700-720px.

Kill stale instances by exact name only — pgrep -x goblin, never -f (-f would match this checkout's paths and your editor). Data dir is ~/.goblin.


4. The goblin-gui-sweep workflow (run after every UI change)

The saved multi-agent workflow at .claude/workflows/goblin-gui-sweep.js (project root) is the standard post-UI-change check. Invoke it via the Workflow tool by name goblin-gui-sweep. It:

  1. Capture — stands up :2 if dead, builds, kills stale instances, launches, unlocks the test wallet, and drives every surface (desktop + mobile widths, all three themes, settings sub-pages, a send-to-review via a local npub, the ~700px responsive flip), screenshotting to /tmp/goblin-gui-sweep/.
  2. Review — fans out reviewer agents (rendering / accessibility / design / copy) over the screenshot groups.
  3. Verify — adversarially re-checks each finding to drop false positives.
  4. Report — writes /tmp/goblin-gui-sweep/report.md + collage.png.

The driver always finishes its rotation (it restores the Dark theme and locks back), and known-normal states are allowlisted in the reviewer prompts so they aren't re-flagged.

Immediate-mode caveat: egui lags one frame on click, so a nav highlight may be missing in the exact frame you clicked. Verify highlights on the next capture, not the click frame — several "bugs" have been this artifact.


5. Test wallets

Live testing uses real GRIN on mainnet, so there are dedicated wallets:

  • A local dev wallet in ~/.goblin for GUI drives (used by the sweep).
  • Two funded mainnet wallets imported for end-to-end send/receive — referred to by role: Wallet A and Wallet B (@fartmuncher22). Wallet B holds a small amount of grin for full live payment tests.

Their seed phrases and passwords are deliberately kept out of the repo — see the local handoff notes. Never paste them into code, logs, commits, or galleries.


6. Identity / relay / node infra

The live backend runs on us-ea.st (ssh us-east):

  • Relaywss://nrelay.us-ea.st (the Goblin relay, docker). The relay we control; always keep it in the relay set for deliverability.
  • NIP-05 servergoblin-nip05d (axum + sqlite) at /opt/goblin/nip05d, serving https://goblin.st (registration, avatars, .well-known/nostr.json).
  • DNS — PowerDNS; nginx fronts goblin.st and hosts the /review gallery.
  • GRIN node — mainnet node at 127.0.0.1:3413.

Payments are NIP-17 gift wraps (kind 1059). DEFAULT_RELAYS (src/nostr/relays.rs) is nrelay.us-ea.st + relay.damus.io + nos.lol; the recipient's kind-10050 DM-relay list plus the sender's own relays decide where a payment is delivered, so both parties must share at least one reachable relay. The relay set is user-editable in Goblin → Settings → Relays (gui/views/goblin/mod.rs, relays_ui); Save & reconnect rewrites kind-10050 and restarts the service.


7. Tests

cd goblin
cargo test --lib                                   # fast unit tests, offline

# Live network e2e (opt-in, hit real relays/server) — tests/nostr_e2e.rs:
cargo test --test nostr_e2e nip17_slatepack_roundtrip -- --ignored --nocapture   # our relay
cargo test --test nostr_e2e nip17_roundtrip          -- --ignored --nocapture   # + damus.io + nos.lol
cargo test --test nostr_e2e nip05_registration       -- --ignored --nocapture   # goblin.st register/resolve
cargo test --test nostr_e2e avatar                   -- --ignored --nocapture   # avatar upload/serve/limit
cargo test --test replay_check                       -- --ignored --nocapture   # server replay/squat guards

The nip17_roundtrip_* tests prove a gift-wrapped Goblin payment actually transits each relay: Bob advertises a kind-10050 DM relay and subscribes; Alice sends; Bob unwraps, checks the seal author, and extracts the slatepack. The --ignored roundtrips can transiently time out on public-relay latency — re-run before treating a timeout as a regression.


8. Review galleries (goblin.st/review)

Screenshot galleries from a sweep are published for the owner by copying the collage/shots to us-ea.st under the nginx /review/ location (note the trailing slash — bare /review is 301'd to /review/). Galleries are put up time-boxed (e.g. a 1- or 24-hour expiry) and taken down after.


9. Gotchas worth keeping

  • View::title_button_big paints dark-on-yellow ink — invisible on a dark surface. When you need an icon button on a dark background (e.g. the wallet-list gear), draw it manually with ui.painter().text(... Colors::text(false)).
  • xdotool onboarding drives desync when the layout shifts mid-flow (the password-match step re-flows). Fresh-launch clean fields are reliable; the word-confirm steps drift — don't trust a blind coordinate script across a re-flow, re-screenshot and re-find.
  • Camera/QR: open the device by devices[pos].index(), not the nostr list-position — they differ and the wrong one opens the wrong /dev/video*.
  • Tor needs Go at build time. build.rs builds the webtunnel bridge via scripts/webtunnel.sh, which silently no-ops if Go is missing, embedding a 0-byte bridge → Tor never bootstraps. If relay/NIP-05 stay "connecting…": pacman -S go, force a build.rs rerun (touch build.rs), rebuild.
  • git "dubious ownership" after a prior root session: git config --global --add safe.directory "$PWD"; root-owned target/ artifacts → sudo chown -R butler:butler goblin/target or cargo clean.