nidus-sync/AGENTS.md
Eli Ribble 6c8f83f42d
docs: add AGENTS.md with codebase conventions and cleanup context
Document lint helpers, architecture patterns, build commands,
the rmo/sync host situation, DB access patterns, remaining
lint issues, and the May 2026 cleanup summary.
2026-05-12 14:35:47 +00:00

7.3 KiB

AGENTS.md — Nidus Sync Codebase Guide

This file captures conventions, patterns, and gotchas for anyone working on this codebase. It was produced during a lint cleanup pass (May 2026) to document lessons learned.

Project Overview

Module: github.com/Gleipnir-Technology/nidus-sync Language: Go 1.24 Build: Nix (flake.nix) + standard Go toolchain ORM: Bob (legacy) + Jet (new, partial migration) Frontend: Vue SPA (Vite) replacing Go HTML templates

The app serves two hosts from a single binary:

  • Sync (sync/) — internal dashboard for mosquito control districts
  • RMO (rmo/) — public-facing "Report Mosquitoes Online" site

Both are migrating from Go html/template rendered pages to Vue SPAs served by static.SinglePageApp().

Build & Lint Commands

# Build everything
go build ./...

# Run linter
golangci-lint run

# Build a specific package
go build ./api/
go build ./platform/

Lint Helpers (lint/error.go)

The lint/ package provides helpers for common error-handling patterns. Always use these instead of bare calls to avoid errcheck lint failures:

Helper Use for Example
lint.Fprintf(w, fmt, args...) fmt.Fprintf to writers where errors are non-critical lint.Fprintf(w, "ok")
lint.Fprint(w, args...) fmt.Fprint to writers lint.Fprint(w, "User-agent: *\n")
lint.Write(w, p []byte) w.Write(p) — HTTP response bodies lint.Write(w, body)
lint.LogOnErr(f, msg) Deferred Close() calls defer lint.LogOnErr(file.Close, "close file")
lint.LogOnErrCtx(f, ctx, msg) txn.Commit(ctx) or other ctx funcs lint.LogOnErrCtx(txn.Commit, ctx, "commit")
lint.LogOnErrRollback(f, ctx, msg) Deferred txn.Rollback(ctx) defer lint.LogOnErrRollback(txn.Rollback, ctx, "rollback")

Key rule: LogOnErrRollback silently ignores "sql: transaction has already been committed or rolled back" errors, which occur when a deferred rollback fires after a successful commit. Always use it for deferred rollbacks.

For DB transactions, use this pattern:

txn, err := db.PGInstance.BobDB.BeginTx(ctx, nil)
if err != nil {
    return fmt.Errorf("begin: %w", err)
}
defer lint.LogOnErrRollback(txn.Rollback, ctx, "rollback")

// ... do work ...

if err := txn.Commit(ctx); err != nil {
    return fmt.Errorf("commit: %w", err)
}
return nil

For HTTP handlers that render HTML:

if err := renderShim(w, r, errRender(err)); err != nil {
    http.Error(w, fmt.Sprintf("render shim: %v", err), http.StatusInternalServerError)
}

Architecture Notes

Two hosts, one binary

main.go creates two gorilla/mux routers and supports three modes via CLI flags:

  • -sync — serve the Sync dashboard
  • -report — serve the RMO public site
  • -all — serve both (default)

Each host has its own route registration in sync/routes.go and rmo/routes.go.

RMO package — all handlers are dead

All route registrations in rmo/routes.go are commented out. The file now only serves the Vue SPA via static.SinglePageApp("static/gen/rmo"). During cleanup (May 2026), all handler files were deleted:

rmo/compliance.go  rmo/error.go     rmo/nuisance.go  rmo/report.go
rmo/district.go    rmo/image.go     rmo/quick.go     rmo/root.go
rmo/email.go       rmo/mailer.go    rmo/notification.go  rmo/scss.go
rmo/mock.go        rmo/status.go    rmo/water.go

Only rmo/routes.go remains. Do not add new Go template handlers here — the RMO host is pure Vue SPA now.

Sync package — partially live

Many route registrations in sync/routes.go are active. Files deleted during cleanup were those with zero active registrations:

sync/admin.go       sync/download.go      sync/operations.go   sync/pool.go
sync/cell.go        sync/intelligence.go  sync/parcel.go       sync/radar.go
sync/communication.go  sync/messages.go   sync/planning.go     sync/review.go
sync/dash.go        sync/mock.go          sync/notification.go sync/service-request.go
sync/signin.go      sync/sms.go           sync/text.go         sync/tile.go

api/ vs resource/ — two handler layers

The codebase has two HTTP handler patterns:

  1. api/ — route registration (api/routes.go) + http.HandlerFunc handlers. Handles signin, webhooks (Twilio, VoIP.ms), media uploads, configuration POSTs.

  2. resource/ — typed resource handlers with List, Get, Create, etc. methods. Each resource has a struct embedding *router for URI generation. This is the newer, preferred pattern.

The split is not clean — some api/ files contain substantial business logic. New handlers should use the resource/ pattern.

DB access — Bob vs Jet

Two ORMs coexist:

  • Bob (github.com/Gleipnir-Technology/bob) — legacy, used by most queries. Models in db/models/*.bob.go (103 files).
  • Jet (db/jet/) — new, generated queries in db/query/public/, db/query/publicreport/, db/query/arcgis/. Only 3 schemas partially ported.

The db.PGInstance singleton holds both BobDB and PGXPool. Jet uses PGXPool directly; Bob uses BobDB.

db/prepared.go & db/fieldseeker.go

db/prepared.go contains utility functions (pointOrNull, lineOrNull, queryStoredProcedure, etc.) that are only called from db/fieldseeker.go. That file is entirely commented out (/* ... */). The 9 unused-prepared-funcs lint warnings are expected — do not delete them unless you're also deleting or uncommenting fieldseeker.go.

Lint Cleanup Context (May 2026)

What was fixed

  • errcheck (36→0): All unchecked error returns eliminated using lint/ helpers or explicit checks.
  • unused (50→9): ~60 functions/types deleted across ~30 files. Remaining 9 are in db/prepared.go (see above).

golangci-lint reporting cap

golangci-lint caps unused reports at 50 items. During cleanup, each batch of deletions exposed previously hidden items. If you see 50 unused items, there are almost certainly more hidden behind the cap. Delete the visible ones, re-run lint, and repeat.

What was NOT fixed (remaining lint categories)

  • govet (26): printf format mismatches, copylocks, lostcancel — some are real bugs
  • ineffassign (9): dead assignments that may indicate logic errors
  • staticcheck (29): deprecated io/ioutil, redundant returns, error string conventions, comparison always-true bugs

Deleted by file count

Directory Files deleted Reason
rmo/ 15 All handlers unused — routes commented out
sync/ 17 Unregistered handlers
api/ 2 compliance.go, debug.go — unused handlers
platform/ 2 text/db.go, dashboard.go, publicreport/address.go
Other 1 tomtom/ (prior cleanup)

Commit Conventions

Commits during the cleanup followed a consistent pattern:

lint: fix errcheck in api/api.go debug log writes
lint: remove unused code from sync/ package

Each commit fixes one category of issue in a small set of related files. Build verification (go build ./...) was performed before each commit.

See Also

  • CLEANUP.md — broader cleanup roadmap (Bob→Jet migration, html/ package removal, etc.)
  • HISTORY.md — project history and architectural decisions
  • README.md — administration and build-from-source instructions