From 24867524a14f469b2b8f9f0a4535637e8648360c Mon Sep 17 00:00:00 2001 From: Mischa Date: Wed, 8 Jul 2026 23:37:56 +0200 Subject: [PATCH] =?UTF-8?q?docs(solutions):=20compound=20refresh=20?= =?UTF-8?q?=E2=80=94=20fix=20reference=20drift,=20grow=20CONCEPTS.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refresh audit of all 13 docs/solutions learnings against the current codebase. Core guidance verified accurate everywhere; three docs had reference drift: - dual-repo-submodule-workflow: point worktree setup/teardown at the make worktree-new/worktree-rm targets (manual procedure misses .worktree-env isolation) - docker-exec-root-owned-bind-mount-files: tracked-plugin list now includes entry-actions; fix-perms description matches actual target - grav-plugin-config-without-code-wont-enable: 3-category model's custom-in-repo list now includes entry-actions CONCEPTS.md: add Container, Content repo, Outer repo, Pin, Env tree, Remote-only plugin; refresh Active Trip (switching is one setting now). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0195b3cDdMeize2Mm1FgC2aU --- CONCEPTS.md | 22 +++++++++++++- .../dual-repo-submodule-workflow.md | 29 ++++++++----------- ...docker-exec-root-owned-bind-mount-files.md | 4 +-- ...-plugin-config-without-code-wont-enable.md | 2 +- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/CONCEPTS.md b/CONCEPTS.md index 9333058..1e400b8 100644 --- a/CONCEPTS.md +++ b/CONCEPTS.md @@ -12,7 +12,7 @@ A **Trip** owns its **Entries** and **Stories**. Exactly one Trip is the **Activ A single journey the blog is organised around — the top-level content entity. A Trip aggregates its Entries and Stories and carries its own metadata (title, start/end dates, cover image, route GPX files). Each Trip renders as one consolidated **Trip page** showing an inline map, a filtered feed, and inline stats; the journal, map, stats, and story views are not separate pages. ### Active Trip -The one Trip currently featured — set in site config and read by the home page and the post form. Switching the Active Trip is a deliberate, multi-file change; if the post form's target and the featured Trip fall out of sync, new posts land under the wrong Trip. +The one Trip currently featured — set in a single site-config value and read by the home page and the posting pipeline, which derives the write target for new Entries from it at submit time. Switching the Active Trip is that one setting; there is no separate post-form target to keep in sync. ### Published / Draft A Trip's visibility state. A **Published** Trip is listed publicly and reachable by anyone; a **Draft** Trip is hidden from anonymous visitors in the public trip list, while the signed-in owner still sees it (marked "Draft") and can flip it back. The owner toggles this per Trip from the trip list. @@ -28,6 +28,26 @@ The Trip's journal section is labelled "Journal" and lives in the Trip's `dailie ### Story A long-form, designed narrative piece within a Trip — hero image plus scrollytelling/gallery sections — distinct from the short, dated Entry. Stories are curated set pieces; Entries are the running log. +### Container +A Trip's non-routable holder of child pages — one for Entries, one for Stories. A Container's own URL is deliberately inert (it renders no page of its own), while its children stay individually reachable and are aggregated onto the Trip page. Retiring a view must never delete its Container: the folder half is load-bearing data even when the page half is gone. + +## Repos & deployment + +### Content repo +The repository holding everything the site serves — pages, configuration, accounts, the theme. It has its own remote and its own release cadence: pushing it triggers production to pull via webhook, independent of the Outer repo. + +### Outer repo +The dev-environment repository — tests, docs, scripts, container build — that nests the Content repo and records a Pin to an exact Content-repo commit, expressing "this dev-env state expects this content/theme state." + +### Pin +The Outer repo's recorded Content-repo commit (also "pointer bump" for the act of updating it). Routine content churn never moves it; it is bumped once at the end of a cross-repo feature, to a commit already published on the Content repo's main branch. A stale Pin during normal work is expected, not an error. + +### Env tree +A server's per-host configuration overlay. Once it exists, Grav's Admin writes **all** config edits there rather than into the shared configuration — so server-side Admin edits are server-only, invisible to content sync, and can hold live secrets. Diagnosing config on a server means checking both the shared configuration and the Env tree, with the Env tree winning at runtime. + +### Remote-only plugin +One of the project's three plugin-management categories, alongside GPM-managed (declared in the shared install list and restored by the standard install flow) and custom-in-repo (code tracked in the Content repo). A Remote-only plugin is installed explicitly on servers and restored by **no** standard flow — if its code goes missing it stays missing until someone reinstalls it deliberately, even while its configuration persists in the Env tree. + ## Flagged ambiguities - "daily" / "entry" / "journal post" all refer to the same concept (a dated journal post). Canonical term: **Entry**. The section/folder is named "dailies" and the nav label is "Journal" — these name the *collection*, not a different entity. diff --git a/docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md b/docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md index 61fe2f4..6d9182d 100644 --- a/docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md +++ b/docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md @@ -56,33 +56,28 @@ Production is unaffected either way: prod pulls `user/` directly via the content The payoff. Because `docker-compose.yml` mounts `./user` **relative to the compose file**, and a worktree is a full copy of the outer tree (compose file included), each worktree serves *its own* `user/`. Two worktrees = two independent sites, no gitlink collisions. -Set up a feature worktree off `main`: +**Use the make targets — don't do the steps by hand.** From the main checkout: ```bash -# outer worktree on a new feature branch -git worktree add .worktrees/ -b feat/ main -cd .worktrees/ - -# populate user/ at the pinned SHA, then branch it for the cross-repo work -git submodule update --init user -git -C user checkout -b feat/ - -# its own dev server — separate project name + port from the main checkout's :8081 -docker compose -p itte- up -d +make worktree-new NAME= # create + start its own dev server +make worktree-rm NAME= # tear down cleanly ``` +`worktree-new` does, in order: `git worktree add .worktrees/ -b feat/ main`, `git submodule update --init user`, branches `user/` onto `feat/`, writes a git-ignored `.worktree-env` (own compose project name, container name, auto-assigned port `8090+`) so every `make`/compose command run inside that worktree targets its own server, and starts the Grav service. The manual equivalent misses `.worktree-env` — without it, make commands in the worktree hit the main checkout's container on `:8081`. + `.worktrees/` is kept out of git via `.git/info/exclude` (local, shared across worktrees — no committed `.gitignore` change needed). ### Teardown -A submodule inside a linked worktree stores its git dir under `.git/modules/user/worktrees/`, so removing the outer worktree needs a second cleanup step: +A submodule inside a linked worktree stores its git dir under `.git/modules/user/worktrees/`, so teardown needs a submodule-deinit step before the worktree can be removed — skipping it is what leaves orphaned `.worktrees/` dirs. `make worktree-rm NAME=` runs the full sequence: ```bash -docker compose -p itte- down -cd "$(git rev-parse --show-toplevel)" # back to the main checkout -git -C .worktrees/ submodule deinit user # detach the submodule worktree -git worktree remove .worktrees/ # remove the outer worktree -git branch -d feat/ # if merged +# what worktree-rm does internally +make -C .worktrees/ stop # compose down (its own server) +git -C .worktrees/ submodule deinit -f user # detach the submodule worktree +git worktree remove --force .worktrees/ +git worktree prune +git branch -d feat/ # manual, if merged ``` ### Landing a commit on main without disturbing the main checkout diff --git a/docs/solutions/integration-issues/docker-exec-root-owned-bind-mount-files.md b/docs/solutions/integration-issues/docker-exec-root-owned-bind-mount-files.md index 0f14b65..add838b 100644 --- a/docs/solutions/integration-issues/docker-exec-root-owned-bind-mount-files.md +++ b/docs/solutions/integration-issues/docker-exec-root-owned-bind-mount-files.md @@ -143,8 +143,8 @@ The reusable principle, worth internalizing beyond this one repo: - **Don't rely on `APACHE_RUN_USER` or compose-level `UID`/`GID` env vars to fix exec ownership** — they don't apply to `docker exec`. `APACHE_RUN_USER` only affects Apache workers; compose `user:`/env vars only affect services wired to consume them. - **You can't just add `user:` to a service whose entrypoint needs root** (to bind privileged ports, set up cron, etc.). Drop privileges per-exec instead of per-container. - **If a tool run as non-root needs writable scratch dirs that are root-owned in the image, chown them container-internally first.** That doesn't touch the host. -- **Root-owned files accumulate invisibly.** (session history) Plugin code under `user/plugins//` is gitignored by project convention (only `cache-on-save` and `story-blocks` are tracked), so root-owned files pile up in the bind mount without ever appearing in `git status` — they only bite at worktree-removal time. Don't wait for `git status` to reveal them; `find ./user -uid 0 | wc -l` is the real detector. -- **Keep a `make fix-perms` escape hatch** (`find ./user -uid 0 ... chown`) for residual root files — notably first-boot files the base-image entrypoint writes as root (`config/security.yaml`, `data/api-keys.yaml`), which no `-u` on a make target can reach. After this fix it's a rare mop-up, not a routine step. +- **Root-owned files accumulate invisibly.** (session history) Plugin code under `user/plugins//` is gitignored by project convention (only `cache-on-save`, `story-blocks`, and `entry-actions` are tracked), so root-owned files pile up in the bind mount without ever appearing in `git status` — they only bite at worktree-removal time. Don't wait for `git status` to reveal them; `find ./user -uid 0 | wc -l` is the real detector. +- **Keep a `make fix-perms` escape hatch** (container-internal `chown -R 1000:1000 /var/www/html`) for residual root files — notably first-boot files the base-image entrypoint writes as root (`config/security.yaml`, `data/api-keys.yaml`), which no `-u` on a make target can reach. After this fix it's a rare mop-up, not a routine step. - **Verification recipe:** `docker exec -u 1000:1000 touch /mnt/f && stat -c '%u' host/f` should print your uid, not `0`. This lives in the Makefile because make targets are the only sanctioned container interface in this project — the fix belongs there, not in ad-hoc docker commands. diff --git a/docs/solutions/integration-issues/grav-plugin-config-without-code-wont-enable.md b/docs/solutions/integration-issues/grav-plugin-config-without-code-wont-enable.md index 37bca85..3be8b39 100644 --- a/docs/solutions/integration-issues/grav-plugin-config-without-code-wont-enable.md +++ b/docs/solutions/integration-issues/grav-plugin-config-without-code-wont-enable.md @@ -89,7 +89,7 @@ How the code went missing here is **unconfirmed**. It happened around the git-sy - **Check the code layer before the config layer.** When a Grav plugin "won't enable" and config toggles do nothing, FIRST verify the code exists: `ls user/plugins//` on the server. Config-without-code is the failure class; the empty directory is the tell. - **Enumerate both layers in all locations when diagnosing.** Plugins have a code layer (`user/plugins//`) and a config layer, and on prod the config can live in the env tree (`user/env//config/plugins/.yaml`) and persist completely independently of the code. Remember: once `user/env//` exists, Grav Admin writes ALL config there, so always check both `user/config/...` and the env path (env wins). -- **Know which plugins are remote-only.** The 3-category model: GPM-via-`plugins.txt` (admin2 / api / flex-objects), custom-in-repo (cache-on-save / story-blocks), and remote-only (git-sync — never in `plugins.txt`). Remote-only plugins are NOT restored by the standard install/content flows, so reinstall them explicitly via GPM after any operation that could have wiped `user/plugins/`. +- **Know which plugins are remote-only.** The 3-category model: GPM-via-`plugins.txt` (admin2 / api / flex-objects), custom-in-repo (cache-on-save / story-blocks / entry-actions), and remote-only (git-sync — never in `plugins.txt`). Remote-only plugins are NOT restored by the standard install/content flows, so reinstall them explicitly via GPM after any operation that could have wiped `user/plugins/`. - **Diagnose actual state before proposing config fixes.** An `ls` is cheaper than a guess. Establishing that the code was missing would have pointed straight at the reinstall instead of a round of config poking. ## Related