FotoLog v2 - agent-coded with a focus on auto-curation https://trippin.tfks.net
  • TypeScript 92%
  • CSS 5.6%
  • Just 2.1%
  • Dockerfile 0.1%
Find a file
Erik Stephens 30f4be69e1 docs: replace scaffold README with a developer guide; drop stale TESTING.md and TODO.md
README.md was still the Fresh scaffold boilerplate. Rewritten as an actual entry point: build badge, quick start, the four-env-file table, a `just` command table, testing setup, project layout, the four architecture constraints that are expensive to unwind, and a doc map pointing at AGENTS.md, src/AGENTS.md, wiki/, LEARN.md, and DEPLOY.md — all of which were previously invisible from the repo root.

TESTING.md was a raw ChatGPT transcript from April 2026, complete with `## Prompt:` / `## Response:` headers. Its proposed `scripts/ci-*.sh` were never built; CI is Forgejo Actions running `just validate`. The real testing conventions live in AGENTS.md and test.just.

The README does not claim `just docker::up` works — no docker-compose.yml is committed yet, as DEPLOY.md's own follow-up list notes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JD9ydGg2JNTri6omU3he4Z
2026-08-27 15:26:40 -07:00
.agents/commands/fjx update fjx agent commands 2026-06-26 18:13:50 -07:00
.claude update fjx agent commands 2026-06-26 18:13:50 -07:00
.forgejo/workflows fix(ci): the registry push uses a token that can write packages 2026-08-26 19:56:59 -07:00
.githooks chore: adopt template-deno standards 2026-05-18 12:20:47 -07:00
.vscode make sure vscode uses our deno fmt rules 2026-06-29 10:39:18 -07:00
certs feat(deploy): encrypt the Supabase connection 2026-08-25 04:47:02 -07:00
coverage build(coverage): baseline is a main-only artifact, not a per-branch commit 2026-08-24 09:52:55 -07:00
openspec feat(globe): show public trips to signed-out visitors 2026-08-27 08:39:17 -07:00
src feat(globe): show public trips to signed-out visitors 2026-08-27 08:39:17 -07:00
supabase fix(ci): SMTP_PORT keeps its .env override and gains a floor 2026-08-26 19:10:14 -07:00
wiki docs(wiki): record the two Supabase key migrations and the getUser() dependency 2026-08-27 06:59:17 -07:00
.dockerignore feat(ui)!: Shoelace is removed, and its tokens become --tfks-* 2026-08-26 14:42:40 -07:00
.env.test.example chore(env,email): one env file to edit, absolute links, and mail that renders on any canvas 2026-08-24 07:15:43 -07:00
.gitignore fix(dev): pin the dev origin to localhost, which is the one Mapbox's token allows 2026-08-25 22:20:37 -07:00
.gitleaks.toml build(bakeoff): Day 0 baseline — packaging fixes and evidence pass 2026-08-25 10:22:52 -07:00
.yamllint fix(ci): semgrep stops failing the build on two known patterns 2026-08-26 19:33:27 -07:00
admin.just chore(env,email): one env file to edit, absolute links, and mail that renders on any canvas 2026-08-24 07:15:43 -07:00
AGENTS.md feat(ui)!: Shoelace is removed, and its tokens become --tfks-* 2026-08-26 14:42:40 -07:00
CLAUDE.md chore: adopt template-deno standards 2026-05-18 12:20:47 -07:00
deno.json release v0.1.8 2026-08-27 08:42:34 -07:00
deno.lock fix(deps): nodemailer moves off 6.9.16 and clears eight advisories 2026-08-26 19:28:35 -07:00
docker.just chore: adopt template-deno standards 2026-05-18 12:20:47 -07:00
Dockerfile fix(docker): serve the wrapper entry, and add a health probe 2026-08-26 22:20:47 -07:00
env.example feat(globe): show public trips to signed-out visitors 2026-08-27 08:39:17 -07:00
fjx.json update fjx agent commands 2026-06-26 18:13:50 -07:00
justfile chore(release): the version bump stops reflowing deno.json 2026-08-26 19:23:59 -07:00
LEARN.md feat(bakeoff-c): POI signals store and fetch-submit — zero page loads per edit 2026-08-25 10:42:51 -07:00
migrate.just refactor(supabase): give CI its own trimmed stack and collect the Supabase recipes 2026-08-25 04:47:02 -07:00
README.md docs: replace scaffold README with a developer guide; drop stale TESTING.md and TODO.md 2026-08-27 15:26:40 -07:00
supabase.just fix(ci): SMTP_PORT keeps its .env override and gains a floor 2026-08-26 19:10:14 -07:00
test.just fix(ci): the dependency audit reports instead of gating 2026-08-26 19:33:27 -07:00

trippin

build

Personal travel logging app — trips, points of interest, photos, and a map-first browsing surface. A migration of the old FotoLog.

Fresh 2 (Deno) + Preact islands · Supabase (Postgres, Auth, Storage) · Lit for the Mapbox layer · Tailwind CSS 4 · an in-process background worker.


Quick Start

just setup                       # check tools, install git hooks, cache deps, create .env
supabase start                   # local Supabase stack (Docker)
just test::env                   # generate .env.test = local Supabase creds + your .env
just dev                         # http://localhost:5173

just setup verifies you have deno, docker, just, and supabase on PATH and tells you where to get whatever is missing.

Migrations run automatically at app boot (src/main.ts), so the first just dev brings the schema up. To apply them without starting the app: just migrate up.

Grant yourself admin: just admin::grant you@example.com.

Re-run just test::env after every supabase start (keys rotate) and after every edit to .env. just dev reads .env.test, not .env.


Environment

File Role
env.example template — every supported variable, documented
.env the file you edit; dev-safe values only. Copied wholesale into .env.test
.env.test generated by just test::env — local Supabase credentials + everything from .env. Never hand-edit
.env.prod manually maintained, points at live infra. Nothing reads it unless you ask: just migrate up .env.prod, ENV_FILE=.env.prod just psql

The app runs without the optional keys — Mapbox, OpenRouter (vision + embeddings), Postmark/SMTP, and Turnstile each degrade rather than crash. See env.example for what each one switches on.


Common Commands

Everything goes through just. Run just with no arguments for the full list including submodules.

Command What it does
just dev Vite dev server (ensures the Supabase stack is up first)
just build production build — Vite + the Lit component bundle
just components rebuild src/static/components.js only (after editing src/lib/web-components/)
just icons regenerate the inline icon registry from src/assets/icons/
just check fast gate: format, lint, type-check
just validate what CI runs: check + coverage ratchet + audit + secrets + SAST + fuzz
just test::all full test suite
just test::units unit/integration only (excludes src/e2e/ and src/fuzz/)
just test::e2e end-to-end only
just test::coverage tests with coverage; refreshes .coverage/summary.json
just psql psql shell against the same DB just dev uses
just migrate up [env] apply pending SQL migrations (defaults to .env.test)
just supabase::status local stack URLs, keys, running services
just supabase::verify .env.prod preflight a Supabase project against what the app needs
just admin::grant <email> grant the site-admin role
just release <version> bump version, commit, tag, push

Testing

Tests run against real Supabase — no mocking. Unit and integration tests live next to their source (foo.tsfoo.test.ts); end-to-end tests live in src/e2e/.

supabase start && just test::env   # once per stack restart
just test::all

Two isolation helpers, both from src/lib/testing/mod.ts:

  • createTestUser() — a real, pre-confirmed Supabase Auth user plus a cleanup() you must always call in a finally.
  • withTransaction() — wraps the body in a transaction that is always rolled back. Only valid when the code under test uses the single PoolClient you hand it; code that opens its own pool connections (e.g. processEmailBatch) will not see the uncommitted rows.

The dev stack and the test suite share one database. Three rules keep a test run from spilling into your just dev session: fixtures only ever address @test.invalid / @example.com (the worker refuses to dial reserved domains), just test::_tested purges accounts created during the run, and the middleware treats an auth 5xx as a transient outage rather than a logout. Local GoTrue mail lands in Inbucket at http://localhost:33304.

Full conventions, including how to test Fresh 2 route handlers and middleware: AGENTS.md.


Layout

src/
  main.ts              # entry — env validation, migrations, worker start, Fresh app
  routes/              # file-system routes (pages + /api)
  islands/             # interactive Preact islands
  components/          # server-rendered Preact components
  lib/
    supabase.ts        # server-side Supabase client (SSR cookie auth)
    db/                # Postgres pool + migration runner
    worker/            # queue, registry, task/email batches, loop
    curation/          # photo curation lifecycle — the largest subsystem
    pois/ trips/ teams/ sharing/ auth/ admin/ email/ globe/
    web-components/    # Lit — Mapbox-coupled elements only
    testing/           # test factories and helpers
  migrations/          # numbered SQL, applied at boot
  e2e/                 # end-to-end tests
  static/              # served as-is, incl. the built components.js

Root holds config only (deno.json, justfile + *.just modules, Dockerfile).


Architecture Notes

Four constraints that are easy to violate and expensive to unwind:

  • Queue writes go through src/lib/worker/queue.ts. Never INSERT into tasks or emails directly — the queue is where dedup keys are derived, and that's what makes enqueues safe to retry.
  • Lit is for Mapbox only. trip-map, globe-view, map controls, plus trip-lightbox and tags-input. Every other piece of UI is Preact so it gets SSR, typed props, and one build path. Cross-framework signalling uses document-level CustomEvents.
  • Map UI lives inside the map. Don't absolutely-position controls over a map component from outside; implement MapControl and register it.
  • Storage URLs are signed. Trip media is private and served through short-lived signed URLs generated server-side. Avatars are the one exception — their own public-read bucket.

Where the Docs Live

Doc What it holds
AGENTS.md the operational "how" — dev commands, testing conventions, key constraints. Symlinked as CLAUDE.md
src/AGENTS.md frontend rules — component policy, Button pattern, theme tokens, form/drawer layout, browser-debugging order
wiki/README.md the "why" — design stances, architecture rationale, and debugging post-mortems. Start here for background on a decision
wiki/GLOSSARY.md canonical domain vocabulary. Use these exact terms in code, prompts, and docs
LEARN.md the five load-bearing files, in reading order, for getting oriented fast
DEPLOY.md production: Supabase Cloud provisioning, prod .env, image build, VPS run, verification, rollback
openspec/ spec-driven change proposals and living capability specs — managed by the /opsx:* tooling, not hand-edited

Deployment

Single Docker image, one container, behind Caddy on a VPS; Supabase Cloud for Postgres, Auth, and Storage. The background worker runs in the same process as the web app, which is why exactly one container may run at a time.

just docker::build      # docker build -t trippin .

just docker::up expects a docker-compose.yml that is not committed yet — DEPLOY.md carries the compose and Caddy config to paste onto the host.

Full production procedure — provisioning, TLS-to-Postgres gotchas, the prod environment file, first-boot verification, and rollback — is in DEPLOY.md.

CI is Forgejo Actions (.forgejo/workflows/build.yaml); it runs just validate against a warm Supabase stack on the runner, serialized repo-wide.