AGENTS.md
rtw.mono - Adventure Motorcycle Trip Planning App
A Go backend and Rescript frontend for planning adventure motorcycle trips.
Required Skills (MUST INVOKE)
Before writing code, you MUST invoke the appropriate skills:
| Task Type | Required Skill |
|---|---|
| Creating implementation plans | superpowers:writing-plans |
| Executing implementation plans | superpowers:executing-plans |
| Client/Rescript work | superpowers:working-in-rescript |
| Bug tracking | superpowers:working-with-bug |
| Version control | working-with-jj |
Project Structure
rtw.mono/
├── server/ # Go backend
│ ├── cmd/server/ # Entry point
│ ├── pkg/ # Library packages
│ └── Makefile # Build commands
└── client/ # Rescript frontend
├── src/ # Source code
├── test/ # Unit tests
├── Makefile # Dev commands
└── AGENTS.md # Detailed Rescript guidelines
Go Workspace
This project uses a Go workspace that co-develops the application alongside the ygo and waldo libraries:
- Application:
rtw.mono/server/- The main Go backend - Libraries:
~/projects/ygo- Shared Go library for working with Yjs CRDTs (git.kilimanjaro.io/ygo)~/projects/waldo- Shared Go library and application for forward/reverse geocoding (git.kilimanjaro.io/waldo)
These repositories are developed concurrenctly. Changes to the ygo and waldo library are immediately available to the server without versioning.
Breaking Changes Allowed: The ygo and waldo libraries are pre-production and used exclusively by this application. API changes, refactors, and breaking changes are permitted without maintaining backward compatibility.
Quick Commands
Server (Go)
cd server/
make build # Build server binary to result/bin/server
make lint # Run golangci-lint
Client (Rescript)
cd client/
pnpm exec rescript build # Compile Rescript (MUST pass with no errors)
make dev # Start dev server (compiles + vite + watch)
make test # Run vitest tests
Critical Rules
- Use skills first - Always invoke relevant skills before starting work
- Use jj, NOT git - This repo uses Jujutsu VCS (
jj log,jj commit, etc.) - Use
bug agentcommands - When working with issues, use agent API (bug agent new,bug agent read, etc.) - Code must compile - Rescript and Go must compile with zero errors or warnings
- Client details - See
client/AGENTS.mdfor Rescript-specific guidelines
Known Issues (Agent Environment)
Brotli Header Build Errors
Build failures in server/cmd/server and server/pkg/middleware related to missing brotli/encode.h or brotli/decode.h headers are expected in the agent environment. These packages depend on the brotli C library which is not installed in the container. This does not affect:
- Running tests for other packages (geo, routing, waypoint, etc.)
- Building the server in a properly configured development environment
- The actual functionality of the middleware (brotli compression)
Bug Tracking
Use the bug CLI for issue tracking:
# Human commands (interactive)
bug new --title "Issue title"
bug read <id>
bug list
# Agent commands (automation - use these when acting as agent)
bug agent new --title "Issue" --message "Description"
bug agent read <id>
bug agent comment <id> --message "Update"
Version Control (Jujutsu)
Key commands:
- jj log -r @ - View current change
- jj commit -m "message" - Commit changes
- jj new - Create new change
- jj status - Check working copy state
Do NOT use git commands.
Patterns and Guidelines
Logging (Go)
The Go backend must use log/slog exclusively. Do not use the standard log package anywhere outside pkg/log/ (which implements a custom canonical-log handler on top of slog).
Use the global functions:
slog.Info("starting server", "addr", server.Addr)
slog.Debug("waypoints extracted", "doc_id", docID, "count", len(waypoints))
slog.Warn("clientCountCh full", "doc_id", docID)
slog.Error("failed to create doc", "doc_id", docID, "error", err)
Rules: - Messages are lowercase without trailing punctuation. - Attributes use snake_case keys (doc_id, remote_addr, ref_count). - The error key is always “error”. - Never use log.Fatalf in library code. In main(), use slog.Error(…) + os.Exit(1). - Never use format strings (%s, %v) in slog calls — pass values as structured attributes.
Custom Event Handler Pattern
When attaching event listeners to DOM elements in Rescript/Preact:
Problem: React refs are not available on first render, causing timing issues with addEventListener.
Solution: Attach listeners to document instead of component refs:
// Module-level signal (persists across renders)
let onSearchSelect = Preact.Signal.make(None)
// Effect attaches/detaches based on state
Preact.Signal.effectWithCleanup(() => {
switch onSearchSelect->Preact.Signal.get {
| Some(_) => {
// State active - don't listen
None
}
| None => {
// State inactive - attach listener
let documentElement = Obj.magic(WebAPI.Global.document)
let cleanup = PreactDOM.addEventListener(
documentElement,
"customevent",
handler
)
Some(() => cleanup())
}
}
})
Benefits: - Document always exists - no null checks needed - Single signal dependency - effect only re-runs when state changes - Natural cleanup - listener removed when condition changes - No ref timing issues
Signal State Placement
Inside Component (make function):
- Local ephemeral state
- Resets on component re-render
- Use for: UI-only state that doesn’t affect rendering
Module Level (outside make):
- Persists across component re-renders
- Triggers re-renders when updated
- Use for: State that controls conditional rendering
Example:
// Module level - persists, triggers re-renders
let showSearch = Preact.Signal.make(false)
@jsx.component
let make = () => {
// Component level - resets on re-render
let inputValue = Preact.Signal.useSignal("")
// Effect reads module-level signal
Preact.Signal.effect(() => {
if showSearch->Preact.Signal.get {
// Component re-renders when showSearch changes
}
})
<div>{...}</div>
}
Anti-pattern: Creating signals inside component that control rendering causes: - State reset on every render - Infinite re-render loops - Confusing behavior