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:

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

  1. Use skills first - Always invoke relevant skills before starting work
  2. Use jj, NOT git - This repo uses Jujutsu VCS (jj log, jj commit, etc.)
  3. Use bug agent commands - When working with issues, use agent API (bug agent new, bug agent read, etc.)
  4. Code must compile - Rescript and Go must compile with zero errors or warnings
  5. Client details - See client/AGENTS.md for 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