- 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>
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 inwallets/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:
- Capture — stands up
:2if 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/. - Review — fans out reviewer agents (rendering / accessibility / design / copy) over the screenshot groups.
- Verify — adversarially re-checks each finding to drop false positives.
- 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
~/.goblinfor 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):
- Relay —
wss://nrelay.us-ea.st(the Goblin relay, docker). The relay we control; always keep it in the relay set for deliverability. - NIP-05 server —
goblin-nip05d(axum + sqlite) at/opt/goblin/nip05d, servinghttps://goblin.st(registration, avatars,.well-known/nostr.json). - DNS — PowerDNS; nginx fronts goblin.st and hosts the
/reviewgallery. - 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_bigpaints 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 withui.painter().text(... Colors::text(false)).xdotoolonboarding 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.rsbuilds the webtunnel bridge viascripts/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 abuild.rsrerun (touchbuild.rs), rebuild. git"dubious ownership" after a prior root session:git config --global --add safe.directory "$PWD"; root-ownedtarget/artifacts →sudo chown -R butler:butler goblin/targetorcargo clean.