Compare commits
2
Commits
ed6e43ae51
...
9ec2349cd6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ec2349cd6 | ||
|
|
839a4d0e69 |
@@ -1,179 +1,74 @@
|
|||||||
# CLAUDE.md
|
# CLAUDE.md
|
||||||
|
|
||||||
## 0. Project specifics
|
Rules, gotchas, and entry points — the things that must change what you do *before* you open a file. Everything descriptive lives next to the code:
|
||||||
|
|
||||||
**Only ever write changes in this folder (travel-blog-intotheeast/) or its subfolders.**
|
| Need | Read |
|
||||||
|
|
||||||
### Folder explanation
|
|
||||||
|
|
||||||
- **./**: Grav CMS dev environment for intotheeast travel blog
|
|
||||||
- **scripts/**: Server install and maintenance scripts
|
|
||||||
- **user/**: Site content, config, pages, and theme — its own git repo (`intotheeast-com-content.git`), tracked by the outer repo as a **git submodule** (pinned commit). See "Dual-repo submodule structure" below and `docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md`
|
|
||||||
- **docs/**: All plans, specs, and project documentation (moved here from `user/docs/` on 2026-06-19)
|
|
||||||
- **docs/solutions/**: documented solutions to past problems (bugs, patterns, workflow gotchas), organized by category with YAML frontmatter (`module`, `tags`, `problem_type`). Relevant when implementing or debugging in a documented area
|
|
||||||
- **CONCEPTS.md** (repo root): shared domain vocabulary (Trip, Entry, Story, Active Trip). Relevant when orienting to the codebase or discussing domain concepts
|
|
||||||
|
|
||||||
### Current stack
|
|
||||||
|
|
||||||
- **Grav:** 2.0.7 stable (baked into the custom Docker image via `Dockerfile`; server upgrades in place via `bin/gpm self-upgrade`)
|
|
||||||
- **Admin:** Admin2 v2.0.12 (plugin slug: `admin2`, NOT `admin`)
|
|
||||||
- **GPM channel:** `stable` — set in `user/config/system.yaml` → `gpm.releases` (authoritative). `GRAV_CHANNEL=production` in `docker-compose.yml` is cosmetic/consistency only
|
|
||||||
- **Plugin management:** `admin2`, `api`, and `flex-objects` are now **GPM-managed via `plugins.txt`** (installed by `make install-plugins`), no longer hand-extracted from the core bundle. `git-sync` stays **remote-only** — never in `plugins.txt`
|
|
||||||
- **Docker image:** `getgrav/grav` with `GRAV_CHANNEL=production`
|
|
||||||
- **PHP session:** `session.save_path = /tmp` set in `php/php-local.ini`
|
|
||||||
|
|
||||||
### Dev server
|
|
||||||
|
|
||||||
The Docker dev server runs at **http://localhost:8081** (mapped from container port 80 in `docker-compose.yml`). A second service, `travel-memories`, runs at **http://localhost:8082**. Both ports and the container name are overridable via `GRAV_PORT` / `TM_PORT` / `GRAV_CONTAINER` — a worktree's `.worktree-env` sets these so isolated servers never collide (see "Dual-repo submodule structure").
|
|
||||||
|
|
||||||
### Local dev commands
|
|
||||||
|
|
||||||
`make setup` for a first run (build → start → install-plugins → fix-perms); `make start` / `make stop` thereafter. Other targets are self-describing in the `Makefile`.
|
|
||||||
|
|
||||||
**`make build-assets` is mandatory after editing anything in `user/themes/intotheeast/js/src/`** — esbuild writes the *committed* bundles `js/main.js`, `js/map.js`, `js/feed-actions.js`, `js/trip-publish.js`, `js/post/`, and `css-compiled/`. **Never hand-edit those.** By contrast `css/style.css` and `css/tokens.css` are hand-authored sources.
|
|
||||||
|
|
||||||
### Custom plugins
|
|
||||||
|
|
||||||
Three plugins are site-owned and tracked in the `user/` repo (everything else under `user/plugins/` is GPM-managed and git-ignored): **`cache-on-save`** (clears page-tree cache on `new-entry` submits + injects the write target from `site.active_trip`), **`story-blocks`** (story shortcodes), **`entry-actions`** (owner-only entry delete via the API).
|
|
||||||
|
|
||||||
### Local plugin patches
|
|
||||||
|
|
||||||
Third-party plugins live in the **git-ignored** `user/plugins/`, so local fixes to them do not travel with the content repo and are **overwritten by `make install-plugins`** or a fresh image build. Keep the fix as a tracked patch in `deploy/patches/` instead:
|
|
||||||
|
|
||||||
- `make apply-plugin-patches` — idempotent `git apply` (skips already-applied patches). `make install-plugins` runs it automatically as its last step
|
|
||||||
- `make remote-apply-plugin-patches-test` / `-prod` — piped over SSH into `patch -p1 --forward`; also runs automatically after a remote plugin install
|
|
||||||
- Details and the current patch list: `deploy/patches/README.md`
|
|
||||||
|
|
||||||
### Trip entity architecture
|
|
||||||
|
|
||||||
The site is structured around Trip entities. Key facts:
|
|
||||||
- Active trip is set in `user/config/site.yaml` → `active_trip` (currently `/trips/denmark-2026`). The value is a **route**, not a bare slug
|
|
||||||
- Trip pages live at `user/pages/01.trips/<slug>/`
|
|
||||||
- Each trip has two content subfolders: `01.dailies/` (journal entries) and `04.stories/` (stories). The former `02.map/` and `03.stats/` standalone views were **removed** (2026-07-04, see `docs/working/plans/2026-07-04-standalone-page-cleanup.md`) — map and stats now render inline on the trip page
|
|
||||||
- `01.dailies/` and `04.stories/` are `routable:false` **data containers** — visiting `/trips/<slug>/dailies` or `/stories` directly 404s/redirects; their children (entries/stories) render at their own detail URLs and are aggregated by the trip page
|
|
||||||
- Site nav in `base.html.twig` has Home + Past Trips only — does not link to trip sub-sections
|
|
||||||
- New journal entries are written to the active trip's `dailies` — the write target is derived from `site.active_trip` at submit time by the `cache-on-save` plugin (post-form.md no longer hardcodes `pageconfig.parent`)
|
|
||||||
- The trip page (`trip.html.twig`) uses a **client-side filter bar** (All content / Journal / Stories). The standalone `/dailies`, `/map`, `/stats`, `/stories` view pages no longer exist — do NOT try to re-create them or link to them. This filter bar + stats chrome is shared with the home active-trip view via the `trip-feed-col` partial (see "Two shared partials" below)
|
|
||||||
- Stats are shown inline on the trip page via a toggle (the standalone `/stats` view was removed)
|
|
||||||
- GPX route files live as media on the trip page itself, parsed client-side via toGeoJSON (bundled into `js/map.js`) and drawn on the trip/home map
|
|
||||||
|
|
||||||
### Two shared partials — the rules
|
|
||||||
|
|
||||||
Trip and home render the same map and feed chrome through **two** shared partials, both included with `{% include … with {…} only %}`. Full parameter contracts: [`docs/reference/architecture.md`](docs/reference/architecture.md) → "Shared partial contracts". The rules that must not be broken:
|
|
||||||
|
|
||||||
- **`partials/entry-map.html.twig` is the only map path.** The engine is `MapUtils.initEntryMap(opts)` in `js/src/maplibre-utils.js`. Do not add a second map implementation — an older three-variant setup (`feed-map.html.twig`, full-page `map.html.twig`) was consolidated away on 2026-07-04.
|
|
||||||
- **It must keep assigning `window.tripMap` / `window.homeMap`** — the Playwright map specs assert these globals.
|
|
||||||
- **`partials/trip-feed-col.html.twig` is the feed column beside the map** (date-range header, filter bar, stats/cycling panels, feed loop). Its sibling `partials/home-predeparture.html.twig` is the home-only "Coming soon" state, selected by `home.html.twig` when `all_items` is empty. **Keep `trip-feed-col` single-purpose — do NOT fold the pre-departure branch back into it.**
|
|
||||||
- **Stats glue:** `trip-feed-col` calls `window.initTripStats({…})`, one shared function in `js/src/main.js` that depends on `window.MapUtils` from `map.js`. Both load in the `bottom` asset group.
|
|
||||||
|
|
||||||
### GPX file management
|
|
||||||
|
|
||||||
GPX files are page media on the trip page (`user/pages/01.trips/<slug>/`), auto-detected via `trip_page.media.all` filtered to `.gpx` and passed to the `entry-map` partial — no manual linking. `.gpx` is registered in `user/config/media.yaml`.
|
|
||||||
|
|
||||||
Manage them at `/gpx-manager` (admin login required; filenames auto-slugified on upload), or drop files into the trip folder and `make content-push`. Wiring details — API routes, session-cookie auth, the `Blob`/`FormData` upload gotcha — are in [`docs/guides/gpx-manager.md`](docs/guides/gpx-manager.md).
|
|
||||||
|
|
||||||
### Switching to a new trip
|
|
||||||
|
|
||||||
The active trip lives in **one** place: `user/config/site.yaml` → `active_trip`. **Never re-add a `pageconfig.parent` to `post-form.md`** — `cache-on-save` derives the write target from `site.active_trip` at submit time, and a static parent would override it and reintroduce the old silent-desync bug. Procedure and the new-trip page tree: [`docs/guides/trip-switching.md`](docs/guides/trip-switching.md).
|
|
||||||
|
|
||||||
### Environment
|
|
||||||
|
|
||||||
**Never read `.env`, `.env.prod`, or `.env.test`** — they contain sensitive credentials. You may pass them to commands (e.g. `docker compose`, `make`) but never read their contents directly. Ask the user if you need environment-specific information.
|
|
||||||
|
|
||||||
### Remote operations
|
|
||||||
|
|
||||||
Always use `make` commands for anything on the production server (`make remote-install-plugins`, `make remote-clean`, etc.) — never SSH directly since credentials live in `.env`. If a remote operation isn't covered by an existing `make` command, either ask the user to run it manually or suggest adding a new `make` command if it seems reusable.
|
|
||||||
|
|
||||||
For a full upgrade/deploy through local → test → prod (ordered steps, smoke checklist, rollback), follow the runbook at [`docs/guides/deploy-cycle.md`](docs/guides/deploy-cycle.md).
|
|
||||||
|
|
||||||
### Content sync
|
|
||||||
|
|
||||||
- `make content-push` — commit and push `user/` to Gitea (triggers production pull via webhook)
|
|
||||||
- `make content-pull` — pull latest from Gitea to local
|
|
||||||
- `plugins.txt` is manually maintained — installing a plugin via Admin does NOT update it
|
|
||||||
- `make demo-load` — load **every** fixture trip under `user/docs/demo/trips/` into the pages tree (currently `italy-2026-demo` and `no-photos-demo`). Add a new fixture by dropping a trip folder there; no Makefile edit needed
|
|
||||||
- `make demo-reset` — remove the demo trips' pages folders and clear cache (full reset; re-run `demo-load` to restore)
|
|
||||||
- `make pixelfed-import` — import posts from Pixelfed via `scripts/pixelfed-import.py`
|
|
||||||
|
|
||||||
### User repo gitignore
|
|
||||||
|
|
||||||
Only these folders are tracked in the `user/` Git repo: `pages/`, `config/`, `accounts/`, `themes/`. The `plugins/` and `data/` folders are excluded — **except** the three site-owned plugins, which are un-ignored explicitly (see "Custom plugins" below). Also ignored: the test accounts, `italy-2026-demo` pages, secrets (`config/plugins/git-sync.yaml`, `config/security.yaml`, `api-private.php`), and the whole `env/` override tree.
|
|
||||||
|
|
||||||
### Dual-repo submodule structure
|
|
||||||
|
|
||||||
`user/` is a **git submodule** of the outer repo (`.gitmodules` at the root; git dir absorbed into `.git/modules/user`). Full workflow: `docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md`. The essentials:
|
|
||||||
|
|
||||||
- **Two repos, two cadences.** Outer repo = dev environment (tests/docs/scripts/Docker). `user/` = content + theme, with its own remote and `make content-push` cadence. The outer repo pins an exact `user/` commit via the `user` gitlink.
|
|
||||||
- **Pointer-bump convention.** Routine content changes → **do not** bump the pin (leave it stale; harmless). At the **end of a cross-repo feature** → bump the pin once to the finished `user/` commit. Pin a commit reachable from `user/`'s published `main` (prefer the merge-to-main commit, not a squash-away branch tip), and **push `user/` before the outer repo** (superproject references a child SHA that must already exist upstream). The pin is dev-side coordination only — production pulls `user/` via the content webhook independently.
|
|
||||||
- **`M user` / `m user` is normal.** `M` = pin differs from `user/` HEAD (bump pending/intentional). `m` = submodule working tree dirty (e.g. local-testing `config/site.yaml`). Neither is an error — do not "fix" them by committing the gitlink or the `site.yaml`.
|
|
||||||
- **Worktrees for parallel work — use the make targets, don't do it by hand.** `make worktree-new NAME=<feature>` (from the main checkout) creates the outer worktree off `main`, initialises its own `user/` submodule, branches both, and starts an **isolated** dev server (own container name + auto-assigned port `8090+`, persisted in a git-ignored `.worktree-env` so every `make`/compose command in that worktree targets its own server). `make worktree-rm NAME=<feature>` tears it down cleanly (compose down → `submodule deinit` → `worktree remove` → `prune`) — skipping the deinit is what leaves orphaned `.worktrees/` dirs. Worktrees live under `.worktrees/` (excluded via `.git/info/exclude`). A fresh worktree's `user/` is empty until the submodule init runs, and `M user`/`m user` is normal (see above) — do not "fix" either. To add a commit to `main` while the main checkout is on another branch, use a throwaway `main` worktree rather than `git checkout main`.
|
|
||||||
|
|
||||||
## 1. Environment modes
|
|
||||||
|
|
||||||
### Rule: do not switch modes during development
|
|
||||||
|
|
||||||
**Never toggle between development and production mode mid-session.** If a caching or config issue appears, fix it at the application level (plugin, template logic) rather than temporarily flipping a mode flag to work around it. Mode switches introduce inconsistent state and make bugs harder to reproduce.
|
|
||||||
|
|
||||||
### Development mode (current)
|
|
||||||
|
|
||||||
Active settings in `user/config/system.yaml`:
|
|
||||||
|
|
||||||
| Setting | Dev value | Why |
|
|
||||||
|---|---|---|
|
|
||||||
| `twig.cache` | `false` | Theme file edits take effect immediately; no stale compile errors |
|
|
||||||
|
|
||||||
With these settings, Grav rebuilds templates on every request. This is intentionally slower but means you never need to flush cache after editing a `.html.twig` file.
|
|
||||||
|
|
||||||
### Production mode (per-environment override)
|
|
||||||
|
|
||||||
Prod needs different Twig settings than dev, but they are **never** committed to `user/config/system.yaml` — `twig.cache: false` and `debug`/`auto_reload: true` there are the *intended dev values*, and committing prod values breaks local development for everyone. Prod values live in the version-controlled `deploy/env/prod/system.yaml` and deploy to the server's `user/env/<hostname>/` tree via `make remote-apply-env-<env>`.
|
|
||||||
|
|
||||||
Mechanics, the settings table, and the re-apply-after-install rule: [`docs/guides/deploy-cycle.md`](docs/guides/deploy-cycle.md) → "The env override tree". Two things to carry in your head:
|
|
||||||
|
|
||||||
> **⚠️ Once `user/env/<hostname>/` exists, Grav's Admin saves ALL config there** — system *and* plugin. So (a) config edited via Admin **on the server is server-only** and silently never reaches Gitea or local (good for secrets, invisible to the repo); (b) when reading or writing server config, check **both** `user/config/…` and `user/env/<host>/config/…` — **env wins**, so tooling must look there first.
|
|
||||||
|
|
||||||
**Pre-launch smoke test:** with the prod override applied, submit one post via `/post` and confirm it appears in the trip feed immediately — this proves `cache-on-save` works with caching on.
|
|
||||||
|
|
||||||
## 2. Local development setup
|
|
||||||
|
|
||||||
Full setup guide: [`docs/guides/local-setup.md`](docs/guides/local-setup.md)
|
|
||||||
|
|
||||||
### Superpowers skill paths
|
|
||||||
|
|
||||||
Specs: `docs/working/specs/YYYY-MM-DD-<topic>-design.md`
|
|
||||||
Plans: `docs/working/plans/YYYY-MM-DD-<topic>.md`
|
|
||||||
|
|
||||||
The brainstorming and writing-plans skills default to `docs/superpowers/`; these lines override that default.
|
|
||||||
|
|
||||||
### Plan status convention
|
|
||||||
|
|
||||||
Every plan in `docs/working/plans/` must have a `**Status:**` line immediately after the title heading:
|
|
||||||
|
|
||||||
| Status | Meaning |
|
|
||||||
|---|---|
|
|---|---|
|
||||||
| `📋 Not started` | Plan written; work not yet begun |
|
| How the site hangs together — stack, plugin roles, templates, partial contracts, data flows | [`docs/reference/architecture.md`](docs/reference/architecture.md) |
|
||||||
| `🔄 In progress — <note>` | Actively being worked on |
|
| Domain vocabulary — Trip, Entry, Story, Active Trip | [`CONCEPTS.md`](CONCEPTS.md) |
|
||||||
| `⏸️ Deferred — <reason>` | Intentionally postponed |
|
| Doing something operational — posting, GPX, switching trips, local setup, deploying | [`docs/guides/`](docs/guides/) |
|
||||||
| `✅ Complete (YYYY-MM-DD)` | Done |
|
| Test suite layout and conventions | [`docs/reference/testing.md`](docs/reference/testing.md) |
|
||||||
| `❌ Abandoned — <reason>` | Won't implement |
|
| A bug or workflow trap already hit and written up | [`docs/solutions/`](docs/solutions/) — check when working in a documented area |
|
||||||
|
| Folder map, prerequisites, the full `make` command list | [`README.md`](README.md) |
|
||||||
|
|
||||||
**When asked what's open:** surface `Not started` and `In progress` plans. Show `Deferred` plans but label them clearly. Omit `Complete` and `Abandoned` unless explicitly asked.
|
The site is Grav (flat-file PHP CMS, no database) in Docker, with content and theme in the `user/` submodule.
|
||||||
|
|
||||||
**When finishing a plan:** update the `**Status:**` field in the plan file to `✅ Complete (YYYY-MM-DD)` before closing the session. This applies whether execution was done by Claude directly, via the superpowers:executing-plans skill, or via superpowers:subagent-driven-development.
|
## Hard rules
|
||||||
|
|
||||||
## 3. Testing
|
- **Only ever write inside `travel-blog-intotheeast/`** or its subfolders.
|
||||||
|
- **Never read `.env`, `.env.prod`, `.env.test`** — they hold credentials. Pass them to commands (`make`, `docker compose`) but never read them; ask the user if you need a value.
|
||||||
|
- **Never SSH to a server directly** — use the `make remote-*` targets, since credentials live in `.env`. If no target covers what you need, ask the user to run it or propose a new target.
|
||||||
|
- **Never hand-edit build output**, and know which files those are — sources and outputs share folders under `user/themes/intotheeast/` (all paths below are relative to it). `make build-assets` is mandatory after editing any source, and it writes:
|
||||||
|
- **Generated (never edit):** `js/main.js`, `js/map.js`, `js/feed-actions.js`, `js/trip-publish.js`, `js/post/`, `css-compiled/`, `fonts/`, and `templates/partials/weather-icons.html.twig`.
|
||||||
|
- **Hand-authored sources:** everything in `js/src/`, plus `js/maplibre-utils.js` and `js/nav.js` (which sit *next to* the bundles in `js/`), `css/style.css`, `css/tokens.css`, and `scripts/gen-weather-icons.js`.
|
||||||
|
- **Never toggle dev↔prod mode mid-session.** If a caching or config issue appears, fix it at the application level (plugin, template logic) rather than flipping a mode flag — mode switches leave inconsistent state and make bugs harder to reproduce.
|
||||||
|
|
||||||
**The dev server must be running** (`make start`) — every suite drives the live site over HTTP.
|
## Dev environment
|
||||||
|
|
||||||
| Command | Scope |
|
- Dev server: **http://localhost:8081** (`make setup` on a first run, `make start` / `make stop` after). A second service, `travel-memories`, runs on :8082. A worktree gets its own container and port `8090+` from its `.worktree-env` — pass `GRAV_BASE_URL` when pointing tests at one.
|
||||||
|---|---|
|
- `user/config/system.yaml` is committed with **dev** values (`twig.cache: false`), so templates recompile per request and no cache flush is needed after editing a `.html.twig`. Prod values live in `deploy/env/prod/system.yaml` and **never** in `user/config/`.
|
||||||
| `make test` | Everything: `test-config` → `test-post` → `test-ui` |
|
- ⚠️ **Once `user/env/<hostname>/` exists on a server, Grav's Admin saves ALL config there** — system *and* plugin. So (a) config edited via Admin on the server is server-only and silently never reaches Gitea or local; (b) when reading or writing server config, check **both** `user/config/…` and `user/env/<host>/config/…` — **env wins**, so look there first. Mechanics: [`docs/guides/deploy-cycle.md`](docs/guides/deploy-cycle.md).
|
||||||
| `make test-config` | Form/config sanity via `scripts/test-form-config.sh` |
|
- The Admin plugin slug is **`admin2`**, not `admin`.
|
||||||
| `make test-post` | End-to-end post submission via `scripts/test-post.sh` |
|
- `plugins.txt` is maintained by hand — installing a plugin via Admin does **not** update it. `git-sync` is **remote-only** and must never appear in it.
|
||||||
| `make test-ui` | Playwright suite (`npx playwright test`) |
|
- Everything under `user/plugins/` is git-ignored and gets overwritten by `make install-plugins` — **except** the three site-owned plugins (`cache-on-save`, `story-blocks`, `entry-actions`). So a fix to a third-party plugin must be a tracked patch in `deploy/patches/`, never an in-place edit: [`deploy/patches/README.md`](deploy/patches/README.md).
|
||||||
|
|
||||||
- **Test account is automatic.** `test-post` and `test-ui` depend on `test-account`, which creates a `testrunner` admin (password `Testpass1234`) inside the container if absent. It is git-ignored — never commit it, and keep the password free of shell/Make/URL-special characters since several consumers interpolate it.
|
## Content and trips
|
||||||
- **Playwright layout:** config at `playwright.config.js`, specs under `tests/ui/` (`a11y`, `auth`, `dailies`, `gpx`, `home`, `maps`, `nav`, `post`, `stories`, `trip`), shared helpers in `tests/ui/helpers.js`, global setup/teardown in `tests/`.
|
|
||||||
- **Auth is a dependency project.** `auth.setup.js` runs first and writes `tests/.auth/user.json`; the `chromium` project reuses it as `storageState`. Don't add per-test logins.
|
- The active trip lives in **one** place: `user/config/site.yaml` → `active_trip`, and its value is a **route** (`/trips/denmark-2026`), not a bare slug.
|
||||||
- **Base URL:** defaults to `http://localhost:8081`; override with `GRAV_BASE_URL` (required when testing a worktree's isolated server on `8090+`).
|
- `cache-on-save` derives the post write target from `active_trip` at submit time. **Never re-add a `pageconfig.parent` to `post-form.md`** — a static parent would override it and reintroduce the old silent-desync bug. Switching trips: [`docs/guides/trip-switching.md`](docs/guides/trip-switching.md).
|
||||||
- Single spec / focused run: `npx playwright test tests/ui/maps` (add `--headed` to watch). `retries: 0` and screenshots-on-failure only, so a failure is a real failure.
|
- The standalone `/dailies`, `/map`, `/stats` and `/stories` trip views were **deleted** (2026-07-04) — map, stats, and filtering all render inline on the trip page. Do not re-create them or link to them. `01.dailies/` and `04.stories/` are `routable:false` data containers whose children are aggregated by the trip page.
|
||||||
|
- GPX routes are page media on the trip page, auto-detected — no manual linking. Manage them at `/gpx-manager` (admin login): [`docs/guides/gpx-manager.md`](docs/guides/gpx-manager.md).
|
||||||
|
- `make content-push` commits and pushes `user/` to Gitea, which triggers the production pull; `make content-pull` is the reverse.
|
||||||
|
|
||||||
|
## Two shared partials — the rules
|
||||||
|
|
||||||
|
Trip and home render the same map and feed chrome through two shared partials, both included `with {…} only`. Parameter contracts: [`docs/reference/architecture.md`](docs/reference/architecture.md) → "Shared partial contracts". What must not break:
|
||||||
|
|
||||||
|
- **`partials/entry-map.html.twig` is the only map path** — the engine is `MapUtils.initEntryMap(opts)` in `js/maplibre-utils.js` (a hand-authored file, imported by `js/src/map.js`). Do not add a second map implementation; an older three-variant setup was deliberately consolidated away.
|
||||||
|
- It must keep assigning **`window.tripMap` / `window.homeMap`** — the Playwright map specs assert those globals.
|
||||||
|
- **Keep `trip-feed-col.html.twig` single-purpose.** Its sibling `partials/home-predeparture.html.twig` is the home-only "Coming soon" state — do **not** fold the pre-departure branch back into it.
|
||||||
|
|
||||||
|
## Dual-repo submodule structure
|
||||||
|
|
||||||
|
`user/` is a git submodule with its own Gitea remote and its own cadence; the outer repo pins an exact commit. Full workflow, worktree mechanics, teardown: [`docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md`](docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md).
|
||||||
|
|
||||||
|
- **`M user` / `m user` is normal, not an error.** `M` = the pin differs from `user/` HEAD; `m` = the submodule working tree is dirty (e.g. a local-testing `site.yaml`). Do not "fix" either by committing the gitlink or that `site.yaml`.
|
||||||
|
- **Don't bump the pin for routine content changes.** Bump it once at the end of a cross-repo feature, to a commit reachable from `user/`'s published `main`, and **push `user/` before the outer repo**.
|
||||||
|
- **Use `make worktree-new NAME=<x>` / `make worktree-rm NAME=<x>`** — never a hand-rolled `git worktree add`. The targets initialise the submodule and an isolated dev server; skipping the deinit on teardown is what leaves orphaned `.worktrees/` dirs.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
`make test` runs everything (`test-config` → `test-post` → `test-ui`). **The dev server must be running** — every suite drives the live site over HTTP. Layout, helpers, and per-suite commands: [`docs/reference/testing.md`](docs/reference/testing.md).
|
||||||
|
|
||||||
|
- **Auth is a dependency project.** `auth.setup.js` writes `tests/.auth/user.json`, which the `chromium` project reuses as `storageState`. Never add per-test logins.
|
||||||
|
- The `testrunner` admin account is created automatically and is git-ignored — never commit it, and keep its password free of shell/Make/URL-special characters, since several consumers interpolate it.
|
||||||
|
- `retries: 0`, so a failing test is a real failure, not flake.
|
||||||
|
|
||||||
|
## Working docs
|
||||||
|
|
||||||
|
Specs go in `docs/working/specs/YYYY-MM-DD-<topic>-design.md`, plans in `docs/working/plans/YYYY-MM-DD-<topic>.md`. These paths override the `docs/superpowers/` default used by the brainstorming and writing-plans skills.
|
||||||
|
|
||||||
|
Every plan needs a `**Status:**` line immediately after its title heading: `📋 Not started` · `🔄 In progress — <note>` · `⏸️ Deferred — <reason>` · `✅ Complete (YYYY-MM-DD)` · `❌ Abandoned — <reason>`.
|
||||||
|
|
||||||
|
- **When asked what's open:** surface `Not started` and `In progress`; show `Deferred` but label it clearly; omit `Complete` and `Abandoned` unless explicitly asked.
|
||||||
|
- **When finishing a plan:** set its status to `✅ Complete (YYYY-MM-DD)` before closing the session — whether you executed it directly or via the executing-plans / subagent-driven-development skills.
|
||||||
|
|||||||
@@ -10,10 +10,29 @@ Two git repos:
|
|||||||
|
|
||||||
| Repo | Contents | Location |
|
| Repo | Contents | Location |
|
||||||
|------|----------|----------|
|
|------|----------|----------|
|
||||||
| `intotheeast.com` (this repo) | Docker setup, Makefile, scripts, plugins.txt | `./` |
|
| `intotheeast.com` (this repo) | Docker setup, Makefile, scripts, tests, docs, plugins.txt | `./` |
|
||||||
| `intotheeast.com-content` | Site config, pages, theme | `user/` (standalone git repo) |
|
| `intotheeast.com-content` | Site config, pages, theme | `user/` (git submodule) |
|
||||||
|
|
||||||
The `user/` directory is a standalone git repo — its changes are pushed/pulled independently to Gitea. The Grav Sync plugin on the server automatically pulls from Gitea when content is pushed.
|
`user/` is tracked by this repo as a **git submodule** — it has its own Gitea remote and its own push/pull cadence (`make content-push` / `make content-pull`), and this repo pins an exact `user/` commit. The Git Sync plugin on the server pulls from Gitea automatically when content is pushed. A persistent `M user` / `m user` in `git status` is normal, not a problem; see [`docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md`](docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md).
|
||||||
|
|
||||||
|
### Folder map
|
||||||
|
|
||||||
|
| Path | Contents |
|
||||||
|
|------|----------|
|
||||||
|
| `user/` | Site content, config, pages, theme (the content submodule) |
|
||||||
|
| `user/themes/intotheeast/js/src/` | JS sources — esbuild inputs; run `make build-assets` after editing. Note `js/maplibre-utils.js` and `js/nav.js` are *also* sources, despite sitting beside the generated bundles |
|
||||||
|
| `deploy/env/` | Per-environment Grav config overrides (e.g. prod Twig settings) |
|
||||||
|
| `deploy/patches/` | Tracked patches for third-party plugins, which are otherwise git-ignored |
|
||||||
|
| `scripts/` | Server install and maintenance scripts |
|
||||||
|
| `tests/` | Playwright suite — see [`docs/reference/testing.md`](docs/reference/testing.md) |
|
||||||
|
| `php/` | Local PHP ini overrides |
|
||||||
|
| `docs/` | All project documentation — start at [`docs/README.md`](docs/README.md) |
|
||||||
|
| `docs/guides/` | Operational how-tos (posting, GPX, trip switching, setup, deploy cycle) |
|
||||||
|
| `docs/reference/` | Stable facts: architecture, design system, testing |
|
||||||
|
| `docs/solutions/` | Write-ups of bugs and workflow traps already hit, with YAML frontmatter (`module`, `tags`, `problem_type`) |
|
||||||
|
| `docs/working/` | Specs, plans, backlog, QA — work in flight |
|
||||||
|
| `CONCEPTS.md` | Shared domain vocabulary (Trip, Entry, Story, Active Trip) |
|
||||||
|
| `CLAUDE.md` | Rules and gotchas loaded into every Claude Code session |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -82,13 +101,42 @@ make content-push # push local user/ commits → Gitea
|
|||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
|
| `make setup` | First run: build → start → install plugins → fix perms |
|
||||||
| `make start` | Start the local Docker container |
|
| `make start` | Start the local Docker container |
|
||||||
| `make stop` | Stop the local Docker container |
|
| `make stop` | Stop the local Docker container |
|
||||||
| `make setup` | Start container and install all plugins from plugins.txt |
|
| `make install-plugins` | (Re)install plugins from plugins.txt, then apply local plugin patches |
|
||||||
| `make install-plugins` | (Re)install plugins from plugins.txt in the local container |
|
| `make apply-plugin-patches` | Idempotently re-apply the patches in `deploy/patches/` |
|
||||||
| `make content-push` | Push local `user/` commits to Gitea |
|
| `make fix-perms` | Reset file ownership inside the container |
|
||||||
|
| `make build-assets` | Run esbuild over `user/themes/intotheeast/js/src/` — **required** after editing any JS source |
|
||||||
|
| `make content-push` | Push local `user/` commits to Gitea (triggers the production pull) |
|
||||||
| `make content-pull` | Pull latest `user/` content from Gitea |
|
| `make content-pull` | Pull latest `user/` content from Gitea |
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `make test` | Everything: `test-config` → `test-post` → `test-ui` |
|
||||||
|
| `make test-config` | Form/config sanity checks |
|
||||||
|
| `make test-post` | End-to-end post submission |
|
||||||
|
| `make test-ui` | Playwright suite |
|
||||||
|
|
||||||
|
Details and conventions: [`docs/reference/testing.md`](docs/reference/testing.md).
|
||||||
|
|
||||||
|
### Demo content and imports
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `make demo-load` | Copy every fixture trip under `user/docs/demo/trips/` into the pages tree (add a fixture by dropping a folder there — no Makefile edit needed) |
|
||||||
|
| `make demo-reset` | Remove those demo trips from the pages tree and clear cache |
|
||||||
|
| `make pixelfed-import` | Import posts from Pixelfed via `scripts/pixelfed-import.py` |
|
||||||
|
|
||||||
|
### Parallel work
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `make worktree-new NAME=<feature>` | Create a worktree with its own `user/` checkout and an isolated dev server on port `8090+` |
|
||||||
|
| `make worktree-rm NAME=<feature>` | Tear one down cleanly (compose down → submodule deinit → worktree remove → prune) |
|
||||||
|
|
||||||
### Remote credentials
|
### Remote credentials
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|
|||||||
+3
-2
@@ -8,14 +8,15 @@
|
|||||||
- [Switching to a new trip](guides/trip-switching.md)
|
- [Switching to a new trip](guides/trip-switching.md)
|
||||||
- [Rebuilding local dev from scratch](guides/local-setup.md)
|
- [Rebuilding local dev from scratch](guides/local-setup.md)
|
||||||
|
|
||||||
**Checking project status?** → [`working/`](working/)
|
**Checking project status?** → [`working/`](working/) — [what's in there + the plan status convention](working/README.md)
|
||||||
- [Backlog](working/backlog.md)
|
- [Backlog](working/backlog.md)
|
||||||
- [Production todo](working/production-todo.md)
|
- [Bugs and fixes](working/bugs-and-fixes.md)
|
||||||
- [QA results](working/qa/results.md)
|
- [QA results](working/qa/results.md)
|
||||||
|
|
||||||
**Design or architecture decisions?** → [`reference/`](reference/)
|
**Design or architecture decisions?** → [`reference/`](reference/)
|
||||||
- [Design system](reference/design-system.md)
|
- [Design system](reference/design-system.md)
|
||||||
- [Architecture overview](reference/architecture.md)
|
- [Architecture overview](reference/architecture.md)
|
||||||
|
- [Testing](reference/testing.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,23 @@ Three categories, by how each plugin is installed and maintained:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Asset pipeline
|
||||||
|
|
||||||
|
`make build-assets` runs the theme's `npm run build` (esbuild) in a throwaway `node:20-alpine` container, as the host uid so outputs land in the tracked theme tree owned by you rather than root.
|
||||||
|
|
||||||
|
| Source | → Output |
|
||||||
|
|---|---|
|
||||||
|
| `js/src/main.js` | `js/main.js` + `css-compiled/main.css` + `fonts/` (font files via the `woff2` loader) |
|
||||||
|
| `js/src/map.js` | `js/map.js` + `css-compiled/map.css` — bundles `maplibre-gl`, `@mapbox/togeojson`, and `js/maplibre-utils.js` |
|
||||||
|
| `js/src/feed-actions.js` | `js/feed-actions.js` |
|
||||||
|
| `js/src/trip-publish.js` | `js/trip-publish.js` |
|
||||||
|
| `js/src/post-form.js` | `js/post/` (ESM + code splitting) + `css-compiled/post-form.css` |
|
||||||
|
| `scripts/gen-weather-icons.js` | `templates/partials/weather-icons.html.twig` (Lucide SVGs inlined into a Twig map) |
|
||||||
|
|
||||||
|
**The trap:** `js/` holds both bundles *and* hand-authored sources. `js/maplibre-utils.js` (the `MapUtils` map engine, a plain IIFE imported by `js/src/map.js`) and `js/nav.js` are sources despite sitting beside the minified bundles. `css/style.css` and `css/tokens.css` are hand-authored too — only `css-compiled/` is generated.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Template hierarchy
|
## Template hierarchy
|
||||||
|
|
||||||
All page templates extend `base.html.twig`:
|
All page templates extend `base.html.twig`:
|
||||||
@@ -83,6 +100,8 @@ templates/
|
|||||||
|
|
||||||
The standalone `dailies.html.twig`, `map.html.twig`, `stats.html.twig` and `stories.html.twig` view templates were **removed** in the 2026-07-04 standalone-page cleanup — the trip page (`trip.html.twig`) consolidated the feed, inline map, and inline stats.
|
The standalone `dailies.html.twig`, `map.html.twig`, `stats.html.twig` and `stories.html.twig` view templates were **removed** in the 2026-07-04 standalone-page cleanup — the trip page (`trip.html.twig`) consolidated the feed, inline map, and inline stats.
|
||||||
|
|
||||||
|
Site nav (in `base.html.twig`) is deliberately minimal — **Home + Past Trips only**. It does not link to trip sub-sections, because those standalone views no longer exist.
|
||||||
|
|
||||||
Partials live in `templates/partials/` (plus macros in `templates/macros/`). Key partials: `base.html.twig` (site shell extended by all page templates), `entry-map.html.twig` (shared map column + `initEntryMap` call, used by trip + home), `trip-feed-col.html.twig` (feed column chrome, shared by trip + home), `home-predeparture.html.twig`, `entry-journal.html.twig` / `entry-story.html.twig` (feed cards), `trip-publish-toggle.html.twig`, and `weather-icons.html.twig`.
|
Partials live in `templates/partials/` (plus macros in `templates/macros/`). Key partials: `base.html.twig` (site shell extended by all page templates), `entry-map.html.twig` (shared map column + `initEntryMap` call, used by trip + home), `trip-feed-col.html.twig` (feed column chrome, shared by trip + home), `home-predeparture.html.twig`, `entry-journal.html.twig` / `entry-story.html.twig` (feed cards), `trip-publish-toggle.html.twig`, and `weather-icons.html.twig`.
|
||||||
|
|
||||||
### Shared partial contracts
|
### Shared partial contracts
|
||||||
@@ -205,3 +224,7 @@ Rendered as route polyline on map
|
|||||||
| `user/plugins/api/api.yaml` | `session_enabled: true` for GPX manager auth |
|
| `user/plugins/api/api.yaml` | `session_enabled: true` for GPX manager auth |
|
||||||
| `user/themes/intotheeast/css/tokens.css` | Design tokens (colors, fonts, spacing) |
|
| `user/themes/intotheeast/css/tokens.css` | Design tokens (colors, fonts, spacing) |
|
||||||
| `CLAUDE.md` | Project rules and always-loaded context for Claude |
|
| `CLAUDE.md` | Project rules and always-loaded context for Claude |
|
||||||
|
|
||||||
|
### What the `user/` repo tracks
|
||||||
|
|
||||||
|
Only `pages/`, `config/`, `accounts/`, and `themes/` are versioned in the content repo. `plugins/` and `data/` are ignored — **except** the three custom plugins, un-ignored explicitly in `user/.gitignore`. Also ignored: the test accounts, the demo-trip pages, secrets (`config/plugins/git-sync.yaml`, `config/security.yaml`, `api-private.php`), and the whole `env/` override tree. Read `user/.gitignore` for the authoritative list.
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
# Testing
|
||||||
|
|
||||||
|
Every suite drives the **live site over HTTP**, so the dev server must be running (`make start`) before any of them.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Scope |
|
||||||
|
|---|---|
|
||||||
|
| `make test` | Everything: `test-config` → `test-post` → `test-ui` |
|
||||||
|
| `make test-config` | Form/config sanity via `scripts/test-form-config.sh` |
|
||||||
|
| `make test-post` | End-to-end post submission via `scripts/test-post.sh` |
|
||||||
|
| `make test-ui` | Playwright suite (`npx playwright test`) |
|
||||||
|
| `make test-account` | Creates the `testrunner` admin if absent (a dependency of `test-post` and `test-ui`) |
|
||||||
|
|
||||||
|
Focused runs bypass `make`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx playwright test tests/ui/maps # one suite
|
||||||
|
npx playwright test tests/ui/maps --headed # watch it
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
playwright.config.js ← config (testDir: ./tests/ui)
|
||||||
|
tests/
|
||||||
|
├─ global-setup.js ← runs once before all projects
|
||||||
|
├─ global-teardown.js ← runs once after
|
||||||
|
├─ fixtures/
|
||||||
|
└─ ui/
|
||||||
|
├─ helpers.js ← shared helpers; import from here rather than re-rolling
|
||||||
|
├─ auth/ ← includes auth.setup.js (see below)
|
||||||
|
├─ a11y/ dailies/ gpx/ home/
|
||||||
|
├─ maps/ nav/ post/ stories/ trip/
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Config facts
|
||||||
|
|
||||||
|
| Setting | Value | Why it matters |
|
||||||
|
|---|---|---|
|
||||||
|
| `baseURL` | `process.env.GRAV_BASE_URL \|\| 'http://localhost:8081'` | Set `GRAV_BASE_URL` to test a worktree's isolated server on `8090+` |
|
||||||
|
| `retries` | `0` | A failing test is a real failure, not flake — do not paper over it with retries |
|
||||||
|
| `timeout` | `30_000` | Per test |
|
||||||
|
| `screenshot` | `only-on-failure` | Video off; artifacts stay small |
|
||||||
|
| `reporter` | `line` | |
|
||||||
|
|
||||||
|
### Auth is a dependency project
|
||||||
|
|
||||||
|
Two Playwright projects, in order:
|
||||||
|
|
||||||
|
1. **`setup`** — matches `auth.setup.js`, logs in once, writes `tests/.auth/user.json`.
|
||||||
|
2. **`chromium`** — `dependencies: ['setup']`, consumes that file as `storageState`.
|
||||||
|
|
||||||
|
So every test in `chromium` starts already authenticated. **Never add a per-test login** — it duplicates the setup project and slows the suite.
|
||||||
|
|
||||||
|
### The test account
|
||||||
|
|
||||||
|
`make test-account` creates a `testrunner` admin (via `bin/plugin login new-user`, admin type `both`) inside the container if `user/accounts/testrunner.yaml` is missing. It is git-ignored.
|
||||||
|
|
||||||
|
- Never commit it.
|
||||||
|
- Keep the password free of shell/Make/URL-special characters — it is interpolated by the Makefile, `scripts/test-post.sh`, and the Playwright setup, and a special character breaks at least one of them.
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# docs/working/ — work in flight
|
||||||
|
|
||||||
|
Everything here is a live working document: specs being built from, plans being executed, notes from sessions in progress. Once something is finished it stays (as a record) rather than being deleted — the `**Status:**` line is how you tell the difference.
|
||||||
|
|
||||||
|
Stable facts belong in [`../reference/`](../reference/); how-to procedures in [`../guides/`](../guides/); write-ups of bugs already solved in [`../solutions/`](../solutions/).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What's in here
|
||||||
|
|
||||||
|
| Path | Contents |
|
||||||
|
|---|---|
|
||||||
|
| `specs/` | Design docs — the *what* and *why*, written before a plan. Named `YYYY-MM-DD-<topic>-design.md` |
|
||||||
|
| `plans/` | Implementation plans — the ordered *how*, with a status line. Named `YYYY-MM-DD-<topic>.md` |
|
||||||
|
| `milestones/` | Milestone scope documents (`milestone-1.md` … ) |
|
||||||
|
| `qa/` | Test plans, QA results, readiness audits |
|
||||||
|
| `handovers/` | Session handover notes — context for picking up unfinished work |
|
||||||
|
| `learnings/` | Retrospective notes worth keeping but not yet promoted to `../solutions/` |
|
||||||
|
| `backlog.md` | Unscheduled ideas and wishes |
|
||||||
|
| `bugs-and-fixes.md` | Running log of bugs found and what fixed them |
|
||||||
|
| `summary.md` | Project summary / current state |
|
||||||
|
| `pm-analysis.md`, `git-sync-notes.md`, dated one-offs | Standalone notes, kept for reference |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plan status convention
|
||||||
|
|
||||||
|
Every plan in `plans/` carries a `**Status:**` line immediately after its title heading. This is the single place a plan's state is recorded — there is no separate tracker.
|
||||||
|
|
||||||
|
| Status | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| `📋 Not started` | Plan written and reviewed; no work begun yet |
|
||||||
|
| `🔄 In progress — <note>` | Actively being worked on. The note says where it stopped, so anyone (or any session) can resume |
|
||||||
|
| `⏸️ Deferred — <reason>` | Intentionally postponed. Still valid, just not now — the reason matters more than the status |
|
||||||
|
| `✅ Complete (YYYY-MM-DD)` | Done and shipped. The date is when it landed, not when the plan was written |
|
||||||
|
| `❌ Abandoned — <reason>` | Won't be implemented. Kept so the decision (and its reasoning) is not re-litigated later |
|
||||||
|
|
||||||
|
Notes on using it:
|
||||||
|
|
||||||
|
- **A trailing note after `✅ Complete` is normal and encouraged** for anything non-trivial — what actually shipped, what was deferred, which commit or environment it landed in. Several plans here carry a paragraph.
|
||||||
|
- **`Deferred` is not `Abandoned`.** Deferred means "still want this"; abandoned means "decided against it". Keeping them distinct is the whole point of having both.
|
||||||
|
- **Update the status when the work lands**, not later. A plan whose status lags reality is worse than no plan, because it is trusted.
|
||||||
|
|
||||||
|
### Asking Claude what's open
|
||||||
|
|
||||||
|
Claude reads these statuses directly (the convention is also in [`../../CLAUDE.md`](../../CLAUDE.md), so it applies without being asked). When asked what's open it will surface `Not started` and `In progress`, show `Deferred` items with the label made explicit, and leave out `Complete` and `Abandoned` unless you ask for them. It sets the status to `✅ Complete (YYYY-MM-DD)` on finishing a plan.
|
||||||
|
|
||||||
|
A quick manual sweep of the same thing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
grep -rH '^\*\*Status:\*\*' docs/working/plans/ | grep -v 'Complete\|Abandoned'
|
||||||
|
```
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
## ⚠️ Config lives in the ENVIRONMENT tree, not `user/config/` (IMPORTANT)
|
## ⚠️ Config lives in the ENVIRONMENT tree, not `user/config/` (IMPORTANT)
|
||||||
|
|
||||||
Prod has a per-environment override directory `user/env/<hostname>/config/`
|
Prod has a per-environment override directory `user/env/<hostname>/config/`
|
||||||
(created for Twig prod-mode — see CLAUDE.md §1). **A crucial Grav side effect:
|
(created for Twig prod-mode — see [`../guides/deploy-cycle.md`](../guides/deploy-cycle.md) →
|
||||||
|
"The env override tree"). **A crucial Grav side effect:
|
||||||
once that env dir exists, the Admin panel saves ALL config changes — system and
|
once that env dir exists, the Admin panel saves ALL config changes — system and
|
||||||
plugin — into the active environment's config tree**, not `user/config/`.
|
plugin — into the active environment's config tree**, not `user/config/`.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user