# Wander Server Environment Brief (for ChatGPT)

**Version 6 — 2026-09-28.** Maintained by Rick (server administrator). The live copy is
`https://600amps.net/tim/wander-environment.md`; an uploaded copy may be older.

> **ChatGPT: read this whole file before helping Tim with anything on his server.**
> At the start of each session, tell Tim: "I've read the Wander server brief, version N (date)."
> If anything Tim asks conflicts with this brief, follow the brief and tell Tim why.
> If this brief doesn't cover something that touches the server itself, tell Tim to ask Rick.
> **Then read Tim's `WANDER-NOTES.md`** (in the ChatGPT project): it records what exists and what's next. Follow the working method in section 12.

---

## Recent changes

**Version 6:**
- **Coming from FirstBase** (section 17): Tim built his earlier sites with FirstBase; this maps what he knows onto how `salmon` works, and says how to use his old macros (for ideas, never as code to copy).

**Version 5:**
- **A working method** for every session (section 12): plan small, complete files only, test locally, upload, verify, snapshot, and update `WANDER-NOTES.md`.
- **The demo** (section 16): a visit counter that uses every part of the design. It's the pattern to copy for new features, with two exercises.
- **How files get into `data/`** (section 7), including putting a starter file there.
- **Features as modules** ("blueprints") plugged into `app.py` (section 5).

**Version 4:** everything needed to build the site, not just to run it:
- Where files the program **reads** go (`app/`), templates, front-end assets, and the exact home-page name (section 4).
- What this server does **not** do: no PHP, no `.htaccess`, no URL rewriting, no `.m` files (section 4).
- Logging from the program, reading the access log, time zones (sections 3 and 5).
- Changing the database's structure safely; scheduled jobs (sections 7 and 10).
- Testing on Tim's own Windows computer before uploading; cache and line-ending gotchas (section 12).
- Rule 2 clarified: it's about the **server**; installing Python tools on Tim's own computer for testing is fine.

**Version 3:**
- `python` and `python3` now work at the command line and always run the **same Python as the website** (sections 3 and 6).

**Version 2** (changed since version 1):

Version 1 was a short planning note. Version 2 replaces it completely with the working environment:
- The **directory layout**, the program's user, and `/api/` routing are now **decided** (they were "not decided" in version 1).
- How to load code changes, read logs, back up and inspect the SQLite data, and fix permissions are now spelled out.
- **Wander's data design is Tim's to decide** with your help, within this environment.
- Hard rules (section 2) replace version 1's general server rules; follow them exactly.
- Still not in place: off-server backups of `data/`, and the email rate limit that must come before any form that sends mail.

---

## 1. Who is who

- **Tim** owns the website `wanderuniversity.net` and decides what Wander does. He is comfortable with files and websites but is **not a server administrator**. Explain every command before he runs it, one step at a time, and say what output to expect.
- **Rick** built and administers the server. He decides how it is built and hosted. Anything outside Tim's site directory is Rick's.
- **You (ChatGPT)** help Tim build Wander inside the space described here. Be conservative: the safest correct answer beats the clever one.

---

## 2. Hard rules (never break these)

1. **Never suggest `chmod 777`**, `chmod -R`, or `chown -R` on anything, and never change ownership or permissions outside `/var/www/wanderuniversity.net/`. For permission problems, see section 11.
2. **Never install software on the server.** No `pkg install`, no `pip install` (not even `--user` or into a virtual environment), no `npm -g`, no downloading and running installers. If a library is missing, Tim asks Rick (section 6). (Tim's **own computer** is different: setting up Python there for testing is fine; section 12.)
3. **Never edit system configuration:** the firewall (`/etc/pf.conf`), SSH (`/etc/ssh/`), `sudo` (`/usr/local/etc/sudoers*`), Apache's configuration (`/usr/local/etc/apache24/`), mail (`/etc/dma/`, `/etc/mail/`), `/etc/rc.conf`, `/boot/`, cron (`/etc/crontab`), or anything under `/fbbuild` or belonging to user `fbbuild`. If a change there seems needed, Tim asks Rick.
4. **Never restart or stop services** (`service ... restart/stop`, `apachectl`, `reboot`, `shutdown`). Reloading Tim's own program is done by touching one file (section 5), which needs no `sudo`.
5. **Never use the software this server deliberately does not have:** PostgreSQL, MySQL/MariaDB, gunicorn, uWSGI, Django, nginx, Node servers, certbot, Docker. The stack is fixed (section 3).
6. **Use `sudo` only when this brief says so**, and say why each time. Tim's `sudo` commands are logged and reviewed.
7. **Never put secrets** (passwords, API keys) in `htdocs/`, in code pasted into chats, or in files Tim shares. Section 9.
8. **No web form may send email yet** (section 10).

---

## 3. The server

| Item | Value |
|---|---|
| Server name | `salmon.wanderuniversity.net` |
| Operating system | **FreeBSD 15.1** (not Linux; see the command notes below) |
| Web server | Apache 2.4 (event MPM) with mod_wsgi 5 (daemon mode) |
| Program framework | Flask 3.1 on Python 3.12 (`python` and `python3` at the command line run this same Python) |
| Database | SQLite 3 (a single file; Python's `sqlite3` module) |
| HTTPS | Automatic Let's Encrypt certificates, renewed by Apache (mod_md). Nothing to do. |
| Tim's shell | `/bin/sh` (not bash) |
| Time | Server clock is US Pacific (log timestamps are Pacific). In the program, store times in **UTC** (`datetime.now(timezone.utc)`, as `/health` does) and convert only for display. |

**FreeBSD differences that trip up Linux instructions:**
- No `apt`, `yum`, or `systemctl`. Packages: `pkg` (Rick only). Services: `service` (Rick only).
- Third-party software and its configuration live under `/usr/local/` (for example `/usr/local/etc/`, `/usr/local/bin/`), not `/etc/`.
- `sed -i` needs an argument: `sed -i '' 's/a/b/' file`.
- Downloading: use `fetch` (built in). `curl` and `wget` may not be installed.
- `python` and `python3` both work and always run the **same Python as the website** (kept in step automatically by the server; don't create your own aliases or links for them).
- `/bin/sh` has no `read -s`; avoid bash-only syntax in shell commands.

---

## 4. Where things live

Tim's site is at **`/var/www/wanderuniversity.net/`** (`/var/www` is a shortcut to `/usr/local/www`; both names reach the same files).

| Directory | Holds | Owner | Web address |
|---|---|---|---|
| `htdocs/` | Plain files served as-is: HTML, CSS, JavaScript, images | `tim` | `https://wanderuniversity.net/...` |
| `app/` | The Wander program (Flask code, Python) | `tim` (readable by the program) | `https://wanderuniversity.net/api/...` |
| `data/` | Everything the program saves: the SQLite database, generated files | `wander` (the program's own user) | **never** on the web |

- **Tim uploads** to `htdocs/` and `app/` with FileZilla (SFTP) or edits them in PuTTY. He owns both.
- **Only the program writes to `data/`.** Tim can read it with `sudo` (section 7).
- **URL mapping:** everything under `/api/` goes to the program; every other address is a file in `htdocs/`. A Flask route `@app.route("/health")` answers at `https://wanderuniversity.net/api/health`. Front-end JavaScript calls the program with `fetch("/api/...")`.
- `www.wanderuniversity.net` serves the same site. Plain `http://` redirects to `https://`.

**More on where things go:**
- **Files the program only reads** (word lists, course content, lookup tables, a starter database to copy from): put them in `app/` (for example `app/content/`). The program can read them there, and they are never on the web. `data/` is **only** for what the program itself writes. Tim cannot upload into `data/` (by design).
- **Templates:** Flask looks for Jinja2 templates in `app/templates/` (next to `app.py`).
- **Front-end assets** (CSS, JavaScript, images, fonts): put them in `htdocs/` (for example `htdocs/css/`, `htdocs/js/`). Don't use Flask's own `static` folder: it would be served under `/api/static/...`, through the program, slower and in the wrong place.
- **The home page must be named exactly `index.html`** (only that name is used for `https://wanderuniversity.net/`). The same goes for any folder: `/about/` shows `about/index.html`. A folder with no `index.html` gives **403 Forbidden** (directory listings are off).
- **Names are case-sensitive** on this server, unlike Windows: `Logo.png` and `logo.png` are different files, and a link must match the file name exactly.

**What this server does not do** (don't build on these):
- **No PHP**, no server-side includes (SSI), no CGI scripts. Server-side code runs only inside the Wander program under `/api/`.
- **`.htaccess` files are ignored.** No rewrite rules, redirects, or password-protected folders that way; if one of those is needed, Tim asks Rick.
- **No URL rewriting** ("pretty URLs") outside `/api/`. Inside `/api/`, Flask routes give any URL shape wanted.
- **`.m` files are refused** (403) everywhere on this server.
- **Nothing runs on a timer** unless Rick sets it up (section 10).

---

## 5. How the Wander program runs

- Apache hands every `/api/...` request to a separate **mod_wsgi daemon process that runs as user `wander`** (not `www`, not `tim`). Everything the program does, including every file it writes, happens as `wander`.
- The entry point is **`app/app.wsgi`**, which must provide a WSGI callable named `application`. The current file contains one line:
  ```python
  from app import app as application
  ```
  so the Flask object is `app` in **`app/app.py`**. Keep that arrangement unless there's a reason to change it.
- `app/` is on the Python path: other modules placed in `app/` (for example `app/models.py`) import normally (`import models`). Subdirectories work as packages.
- **Each feature is its own module** (a Flask *blueprint*) plugged into `app.py`, the way the demo is (`app/demo.py`, registered with `app.register_blueprint(demo, url_prefix="/demo")`, answering at `/api/demo/...`). Keep `app.py` small; don't pile every feature into it. Modules must not import `app.py` (that loops); give each module what it needs from the environment, as `demo.py` does with `DATA`.
- **To load code changes:** after uploading, run (no `sudo` needed):
  ```sh
  touch /var/www/wanderuniversity.net/app/app.wsgi
  ```
  The program restarts on its next request.
- **Never** call `app.run()` or enable Flask debug mode on the server. Test locally on Tim's own computer if needed.
- The current starter program has one route, `/health`, which writes `data/heartbeat.txt` and returns `{"status":"ok","time":"..."}`. It proves the program works; keep it (or an equivalent) for troubleshooting.
- **Error log** (Python tracebacks appear here):
  ```sh
  sudo tail -50 /var/log/httpd/wanderuniversity.net-error.log
  ```
  Access log (every request, one line each): `sudo tail -50 /var/log/httpd/wanderuniversity.net-access.log`.
- **Logging from the program:** use Flask's logger; messages land in the error log above, with Python tracebacks.
  ```python
  import logging
  app.logger.setLevel(logging.INFO)   # otherwise only warnings and errors are kept
  app.logger.info("user %s saved a trip", user_id)
  ```
  Never log passwords, session cookies, or API keys.

---

## 6. Python and libraries

- Python **3.12**. Installed libraries (from FreeBSD packages): `flask`, `werkzeug`, `jinja2`, `itsdangerous`, `click`, `blinker`, `markupsafe`, `babel`, `python-dotenv`, `asgiref`, and the standard library including `sqlite3`.
- **Testing on the server:** a library that imports at the command line (`python -c 'import flask'`) also imports in the Wander program, because both use the same Python.
- **Prefer the standard library and what's installed.** Before proposing a new library, check whether the task can be done without one.
- **If a library is truly needed:** Tim asks Rick. Tell Tim the library name; Rick checks for a FreeBSD package (named `py312-<name>`) and installs it. Tim can look first with `pkg search py312-<name>` (read-only, safe).
- **Never** use `pip` in any form on the server. It conflicts with the system's package manager and can break the program in ways Tim cannot repair.

---

## 7. Data and SQLite

- **How `data/` gets written:** only as user `wander`. That happens three ways: (1) the program itself, when visitors use the site (the normal way); (2) a script run by hand as `wander`, such as a database change (`sudo -u wander python /var/www/wanderuniversity.net/app/<script>.py`); (3) copying a file in as `wander`. Tim can't upload into `data/`, and Apache itself can't read it, so nothing there is ever on the web.
- **Putting a starter file into `data/`** (for example a database Tim prepared): upload it to `app/` with FileZilla, then copy it across as `wander`:
  ```sh
  sudo -u wander cp /var/www/wanderuniversity.net/app/starter.db /var/www/wanderuniversity.net/data/wander.db
  ```
  Then delete the copy in `app/` if it held anything private.
- Put the database in `data/`, for example `/var/www/wanderuniversity.net/data/wander.db`. Use absolute paths in code, built from one `DATA` setting (section 12).
- Use Python's `sqlite3` module with **parameterized queries** (`?` placeholders), never string-built SQL.
- The program runs several threads: open a connection per request (or per thread); don't share one connection globally.
- SQLite creates side files (`-journal`, or `-wal` and `-shm` in WAL mode) next to the database; that is why the database must live in a directory the program can write (`data/`).
- **Backups:** the database is **not yet backed up off the server** (Rick will decide how). A consistent copy is made with SQLite's own backup command, never a plain file copy while the program is running:
  ```sh
  sudo -u wander sqlite3 /var/www/wanderuniversity.net/data/wander.db ".backup /var/www/wanderuniversity.net/data/wander-backup.db"
  ```
  Until Rick sets up off-server backups, tell Tim not to rely on the server as the only copy of anything he can't recreate.
- To look inside the database, read-only:
  ```sh
  sudo -u wander sqlite3 -readonly /var/www/wanderuniversity.net/data/wander.db
  ```
- **Changing the database's structure** (new table, new column): try it on Tim's own computer first (section 12), then on the server **make a `.backup` copy first** (command above), and apply the change from the program's own startup code or a one-time script run as `wander`:
  ```sh
  sudo -u wander python /var/www/wanderuniversity.net/app/migrate.py
  ```
  Write changes so they are safe to run twice (`CREATE TABLE IF NOT EXISTS`, check before `ALTER TABLE ... ADD COLUMN`). Never delete or rename a column holding real data without a backup and Rick knowing.

---

## 8. Web security basics (the program is on the public internet)

- Treat all request input as hostile: validate it, and escape output (Jinja2 templates escape by default; don't use `|safe` on user data).
- Passwords: store only hashes (`werkzeug.security.generate_password_hash` / `check_password_hash`). Never log or email passwords.
- Sessions: set a long random `SECRET_KEY` (section 9), and `SESSION_COOKIE_SECURE=True`, `SESSION_COOKIE_HTTPONLY=True`, `SESSION_COOKIE_SAMESITE="Lax"`.
- Protect state-changing requests (POST/PUT/DELETE) against cross-site requests (same-site cookies plus a CSRF token or a custom header checked by the program).
- Don't return tracebacks or internal errors to visitors; log them.
- Limit upload sizes (`MAX_CONTENT_LENGTH`) and never save uploaded files anywhere but `data/`, never under their original name alone, and never into `htdocs/` or `app/`.

---

## 9. Secrets (API keys, the Flask secret key)

- Keep them in a file inside `app/` that is **not** web-reachable, for example `app/.env`, loaded with `python-dotenv`. `app/` is not served by the web (only `app.wsgi` is reachable, through Apache), and its files are readable only by Tim and the program.
- Never put secrets in `htdocs/`, in JavaScript, or in anything sent to the browser.
- Don't paste real keys into ChatGPT; use placeholders in examples.

---

## 10. Network and email

- **Outbound from the program:** HTTPS (port 443) and HTTP (80) to the internet are allowed, for example calling an AI API. Other outbound ports are blocked by the firewall; if something needs one, Tim asks Rick.
- **Sending email:** use the local mail command, which relays through Rick's mail server:
  ```python
  import subprocess
  msg = "From: tim@wanderuniversity.net\nTo: someone@example.com\nSubject: Hello\n\nBody text\n"
  subprocess.run(["/usr/sbin/sendmail", "-t"], input=msg.encode(), check=True)
  ```
  Use a real From address `@wanderuniversity.net`. Never connect to any mail server directly (no `smtplib` to outside servers): the domain's DMARC policy is `reject`, so mail not sent through Rick's server is refused by receivers.
- **Not yet:** no web form or public feature may send email until Rick confirms the relay's rate limit is in place. Until then, email is for Tim's own scripts only.
- Never take a recipient address from form input without Rick's approval (that turns a site into a spam relay).
- **Scheduled jobs** (anything that should run on a timer: nightly cleanup, reminders, report generation): **Tim asks Rick.** Don't create crontabs: a job in Tim's crontab runs as `tim` and can't write `data/`. Rick sets such jobs up to run as `wander`. Write the job as a script in `app/` that can also be run by hand (`sudo -u wander python /var/www/wanderuniversity.net/app/<job>.py`).

---

## 11. Permissions: "Permission denied" and 403 errors

The layout is fixed and correct. A permission error almost always means **the code is writing somewhere other than `data/`**. Check that first.

- **Reset everything to the correct layout** (safe, any time; this is the only permission "fix" to use):
  ```sh
  sudo wander-fixperms
  ```
  It lists anything it corrected. It never loosens permissions.
- **Never** fix permission errors with `chmod`/`chown` by hand, and never with `777`.
- The correct layout, for reference: the site directory `tim:www` mode `2751`; `htdocs/` `tim:www` `2750` (files `0640`); `app/` `tim:wander` `2751` (files `0640`); `data/` `wander:wander` `0750` (files `0640`).
- A **403 on `/api/...`** with "search permissions are missing" in the error log means a directory in `app/` lost its pass-through permission: run `sudo wander-fixperms`.

---

## 12. Working method (every session)

Follow this loop every time. It keeps Tim's copy, the server, and your understanding in step.

1. **Start.** Confirm the brief's version, read `WANDER-NOTES.md`, and ask Tim for **one goal** for this session. If the notes and the goal conflict, say so.
2. **Plan.** Propose the **smallest change** toward the goal. List every file you'll create or change, with its folder (`htdocs/...`, `app/...`), and anything that needs Rick. Wait for Tim's OK before writing code.
3. **Build.** Give **complete files**, never fragments to splice into existing code: Tim can't safely merge pieces. One file per code block, with its full path above it. Tim saves each into his **local project folder**, which mirrors the server (`htdocs/`, `app/`) and is the **master copy**.
4. **Test locally** for program changes (below): Tim runs it on his computer and reports what he sees.
5. **Upload.** With his VPN on, Tim uploads the changed files with FileZilla into the matching folders. For program changes: `touch /var/www/wanderuniversity.net/app/app.wsgi`.
6. **Verify.** `https://wanderuniversity.net/api/health` first, then the changed feature, reloading with **Ctrl+F5**. On an error, ask Tim for the exact message and the last lines of the error log (section 5); fix **that one thing**, and repeat from step 3.
7. **Snapshot.** When the change works, Tim zips his local project folder with the date in the name (for example `wander-2026-10-02.zip`). That's the way back.
8. **Wrap up.** Give Tim a **complete new `WANDER-NOTES.md`**: current state, files, decisions, next steps, anything for Rick, and a one-line entry at the top of the session log. Tim saves it and replaces the copy in the ChatGPT project.
9. **Stop rule.** Anything outside `/var/www/wanderuniversity.net/`, or anything section 2 forbids: stop, and draft a short message for Tim to send Rick (what's needed and why).

**Testing on Tim's own computer first** (Windows; recommended for program changes):
1. Install **Python 3.12** from python.org (the same version as the server), and install Flask into a virtual environment in Tim's local project folder:
   ```bat
   py -3.12 -m venv venv
   venv\Scripts\activate
   pip install "flask==3.1.*" python-dotenv
   ```
   (`pip` is fine **here**, on Tim's computer; never on the server.)
2. Make the program's data folder configurable, so the same code works in both places:
   ```python
   DATA = os.environ.get("WANDER_DATA", "/var/www/wanderuniversity.net/data")
   ```
   Locally: `set WANDER_DATA=C:\wander-test-data` before running.
3. Run it: `flask --app app run`, then open `http://127.0.0.1:5000/health`. Locally there is **no `/api` prefix**; on the server the same route is `/api/health`. Front-end code should call the program with a relative path like `fetch("/api/...")` and be tested on the server.
4. When it works, upload and reload as above.

**Gotchas:**
- **After uploading, reload the page with Ctrl+F5**: browsers cache pages, CSS, and JavaScript, and may show the old version.
- **Line endings:** Windows editors save text with CRLF line endings. Python and web files don't care, but **shell scripts (`.sh`) break**; save them with LF line endings (most editors have a setting in the status bar).
- **File names:** keep them lowercase, without spaces, to avoid case mismatches and awkward URLs.

---

## 13. Troubleshooting

| Symptom | Likely cause | What to do |
|---|---|---|
| Page shows old code | Program not reloaded, or browser cache | `touch .../app/app.wsgi`; reload with Ctrl+F5 |
| 403 on a folder address (`/something/`) | No `index.html` in that folder | Add `index.html` (exact name) |
| 404 on a file that exists | Name's case differs (`Logo.png` vs `logo.png`) | Match the name exactly |
| `.htaccess` rules do nothing | `.htaccess` is ignored on this server | Tim asks Rick |
| Shell script fails with `\r` errors | Windows line endings | Save with LF line endings |
| 500 Internal Server Error | Python exception | Read the error log; fix the code |
| 404 on `/api/...` | Route not defined, or defined with the `/api` prefix | Routes omit `/api` (`@app.route("/x")` → `/api/x`) |
| 403 on `/api/...` | Directory permissions | `sudo wander-fixperms` |
| `PermissionError` in the log | Code writing outside `data/` | Change the path to `data/` |
| `ModuleNotFoundError` | Library not installed | Tim asks Rick (section 6) |
| `database is locked` | Long transactions or a shared connection | One connection per request; commit promptly |
| Whole site down, or anything outside Tim's directories | Server issue | Stop; Tim calls Rick |

---

## 14. When to stop and send Tim to Rick

- Anything needing software installed, a service restarted, or system configuration changed.
- Anything in sections 2 or 3 this brief forbids.
- The site or server is down, HTTPS shows certificate warnings, or email stops.
- Tim can't log in (SSH or FileZilla).
- A fix you're about to suggest would touch files outside `/var/www/wanderuniversity.net/`.

Tell Tim what to tell Rick: what he was doing, the exact error message, the time it happened, and the last command he ran.

---

## 15. Decided versus open

- **Decided by Rick (don't change):** the stack (Apache + mod_wsgi + Flask + SQLite), the directory layout, the program's user, `/api/` routing, and the rules above.
- **Tim's to decide (with your help):** what Wander does, its pages and features, and its data design, within this environment.
- **Not yet in place:** off-server backups of `data/`; the email rate limit that must precede any form that sends mail.

---

## 16. The demo: the pattern to copy

A small visit counter installed 2026-09-28 at `https://wanderuniversity.net/demo/`. It takes no visitor input and stores one row, so it's safe to leave running. Every part of the design is in it:

| Piece | File | What it shows |
|---|---|---|
| The page | `htdocs/demo/index.html` | Plain HTML and JavaScript served by Apache; calls the program with `fetch("/api/demo/visit", {method: "POST"})`; shows results with `textContent` (never `innerHTML` for data) |
| The feature | `app/demo.py` | A blueprint plugged into `app.py`, answering at `/api/demo/visit` |
| Read-only content | `app/content/demo-message.txt` | A file the program **reads**, so it lives in `app/`; read on every request, so changing it needs no reload |
| Saved data | `data/demo.db` | SQLite written by the program as `wander`; table created with `CREATE TABLE IF NOT EXISTS`; one row updated in place with an upsert |
| Logging | `current_app.logger.info(...)` | Lands in the site's error log |

**Exercise 1 (change the message):** Tim edits `app/content/demo-message.txt` locally, uploads it, and reloads the demo page with Ctrl+F5. No program reload is needed. Guide him through steps 3, 5, and 6 of the working method only.

**Exercise 2 (add a feature: the date of the first visit):** walk the full working method, steps 1 to 8. The change needs:
- A **database structure change**: add a `first_at` column to `counter`. Do it safely and idempotently inside `open_db()` (check `PRAGMA table_info(counter)` before `ALTER TABLE counter ADD COLUMN first_at TEXT`), and fill it once with the current `last_at` for the existing row. Before uploading, Tim makes a `.backup` of `data/demo.db` (section 7).
- `app/demo.py` returning `first_at` in the JSON, and `htdocs/demo/index.html` showing it.
- Complete files for both, a local test, upload, `touch app.wsgi`, Ctrl+F5, a snapshot zip, and a new `WANDER-NOTES.md`.

**Removing the demo** (when Tim is done with it): delete the two demo lines at the end of `app/app.py`, then `app/demo.py`, `app/content/demo-message.txt`, and `htdocs/demo/`; upload `app.py`; `touch app.wsgi`. `data/demo.db` is removed with `sudo -u wander rm /var/www/wanderuniversity.net/data/demo.db`. Update `WANDER-NOTES.md`.

---

## 17. Coming from FirstBase

Tim built his earlier sites (maria-fajardo.com, coasttoday.com, farmers-market.com) with **FirstBase**: `.m` macro pages run by `dbmacro.cgi` as CGI programs, reading and writing FirstBase's own database files. **`salmon` doesn't run FirstBase** (`.m` files are refused with 403), and Wander is a new program. Translate Tim's **ideas**, not his code.

| In FirstBase | On `salmon` |
|---|---|
| A `.m` macro page at a web address | A route in a feature module in `app/` (a blueprint, like `app/demo.py`), answering under `/api/...`. The visible page is HTML in `htdocs/` calling it, or a Jinja2 template (`app/templates/`) the route renders. |
| The macro printing HTML (starting with its own `Content-type` line) | The route returns JSON for the page's JavaScript (`jsonify(...)`), or HTML with `render_template(...)`. Flask sets the headers. |
| Query parameters (for example `?langflag=es`) | `request.args.get("langflag")` |
| A form posting to a macro | A POST route reading `request.form`, with validation and cross-site protection (section 8) |
| FirstBase database files (`.cdb`, `.map`, `.ddict`, and indexes) | One SQLite database in `data/`: tables with `CREATE TABLE IF NOT EXISTS`, indexes with `CREATE INDEX IF NOT EXISTS` (section 7) |
| Editing data files by hand or with FirstBase's tools | Look with `sqlite3 -readonly` as `wander`; change data through the program or a script run as `wander` (section 7) |
| A cron job running `dbmacro` on a macro | A script in `app/`, scheduled by Rick (section 10) |
| `system(` calls running shell commands | Plain Python. If an outside command is truly needed, `subprocess.run([...])` with an argument list, never a shell string, and nothing outside the site's directory |
| Sending mail from a macro | `/usr/sbin/sendmail -t` from Python (section 10) |
| Bilingual pages via `langflag` | Keep the same parameter if Tim wants it; per-language templates, or a small dictionary of strings for short labels |

**Using Tim's old macros:**
- Tim may show you an old `.m` file to explain what a page did. Read it for **behavior** (what it takes in, what it stores, what it shows), then build the equivalent from scratch in this environment. Don't translate it line by line.
- **Don't guess what a FirstBase statement does.** You have no FirstBase manual. If a statement's meaning isn't obvious, ask Tim what the page did, or say you're not sure.
- **Nothing comes from the old server onto `salmon`:** no old site files, code, or databases. The old server is under a security review, and any data it holds moves only when Rick says so.
- The legacy sites keep running on FirstBase elsewhere; that isn't Tim's job on `salmon`.

---

## Changelog

| Version | Date | Changes |
|---|---|---|
| 6 | 2026-09-28 | New section 17, Coming from FirstBase: concept mapping (macro pages, output, parameters, forms, data files, cron, `system(`, mail, `langflag`), how to use old macros (behavior, not code; don't guess FirstBase syntax), nothing from the old server. Built on version 5. |
| 5 | 2026-09-28 | Working method (section 12 rewritten: the 9-step loop, complete files only, local master copy, snapshots, `WANDER-NOTES.md`); how `data/` gets written and how to place a starter file (section 7); features as blueprints (section 5); new section 16, the demo, with two exercises and removal. Built on version 4. |
| 4 | 2026-09-28 | Everything needed to build the site: files the program reads (`app/`), templates, front-end assets, exact `index.html`, case-sensitive names, what the server doesn't do (PHP, `.htaccess`, rewriting, `.m`, timers), logging, access log, time zones, safe database-structure changes, scheduled jobs, testing on Tim's Windows computer, cache and line-ending gotchas; rule 2 clarified (server only). Built on version 3. |
| 3 | 2026-09-28 | `python` and `python3` now work at the command line and always match the website's Python (sections 3 and 6); "Recent changes" section. Built on the coordinator's version 2; its content kept unchanged. |
| 2 | 2026-09-28 | Relabeled as version 2 (the published version 1 was a shorter planning note that ChatGPT had already read); added "Changed since version 1". Content otherwise unchanged. |
| 1 | 2026-09-27 | First version: server, layout, program, libraries, data, security, secrets, network and email, permissions, workflow, troubleshooting, escalation. |
