main advanced 13 commits while this documentation audit ran — the
location-override work was merged into the outer repo — and it independently
fixed two of the audit's findings. Without this merge the branch would have
REVERTED them, which is the worst outcome for a cleanup pass because it arrives
disguised as an improvement.
Both conflicts resolved in main's favour; main's wording was better informed:
- CLAUDE.md, the single-map-path rule: main's carve-out (829325c) states the
exception as its own top-level bullet, names MAP_STYLE as the one shared
thing, and spells out both prohibitions ("do not fold it into initEntryMap",
"do not add a third path"). Taken verbatim over the version drafted here.
- 2026-07-23-post-form-location-override.md: main (a517331) had already set the
status to Complete, with far richer detail — the multi-agent review findings,
the green-run numbers, the DEL4 regression still open, and the merge SHAs.
Taken in full; the audit's claim that the status "lagged" was dropped, since
it was true only of this branch's older branch point.
Submodule pin: main bumped user/ to dd19995 and this merge preserves that. The
audit's own no-gitlink-commit discipline applies to bumping the pin as a side
effect of routine work, not to discarding a bump main already made.
main touched none of the other nine corrected documents, so the remaining 18
findings stand unchanged.
Audit notes corrected to match reality rather than left overstated:
- superseded-decisions.md R13 now dates the carve-out to 2026-07-24 (829325c)
rather than implying this pass introduced it.
- The reconciliation spec gains an "audit baseline moved twice" section: the
submodule pin lagged real HEAD, and then the base branch advanced mid-audit.
- The compounded learning's section 6 is rewritten from "audit the current
state" to "re-check the baseline before publishing, not only before starting",
with the two habits that actually follow: merge the base branch in before
opening the PR and read conflicts as findings, and when the incoming version
is better, take it wholesale. An audit has no special authority over the work
it audits.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Captures the durable learnings from the 2026-07-25 whole-repo documentation
reconciliation as a knowledge-track learning, plus the vocabulary it introduced.
New learning — conventions/reconciling-drifted-docs-tense-tiering-and-a-supersession-ledger.md:
- Tier docs by TENSE, not only by content type. claude-md-content-tiering.md
established "descriptions drift, rules don't" and tiered by content type; that
axis could not say what to do with 41 completed plans, which are neither rules
nor current descriptions. Present-tense docs are defects when stale; past-tense
records are supposed to be stale and get annotated, never rewritten.
- Ledger AND inline notes, because each covers the other's failure: a ledger
alone is a pointer you may not follow, inline notes alone give no changelog
view. Prefer annotation patterns the repo already uses.
- Record what was NOT reversed, or a ledger of only reversals makes every old
doc look suspect and settled decisions get re-litigated.
- Separate "docs are wrong" from "code is wrong" — route code-side findings to a
recommendations doc so a docs diff stays reviewable.
- Verify against the artifact that decides behaviour: the Makefile for commands
(including macro-generated targets a grep misses), the build script for
outputs, imports for source-vs-output, branch history for whether a plan
shipped. An audit that never withdraws a finding has not been checking itself
— one finding here was withdrawn after reading package.json.
- Audit the state that actually runs: a fresh worktree checks out the submodule
PIN, which lagged real HEAD and would have hidden a whole merged feature.
Three structural lessons in "Why This Matters":
- An index describing another document's role is a factual claim that can rot,
and it is worse than the stale document itself — it defeats the reader's
judgement before it engages. This was the tree's single most misleading line.
- Wrong beats absent again, now for commands: README's server runbook documented
every remote-* target without the -test/-prod suffix guard-env requires.
deploy-cycle.md had it right — the defect was a second copy drifting.
- Promoting a doc to "the authoritative list of X" creates a completeness
obligation it did not have as prose, and nothing enforces it.
- A removal is not finished when the code is gone, but when every consumer and
every description of it is gone — travel-memories left a compose service
behind, hidden by a cached Docker image. Local state can mask a breakage
indefinitely, so "it works here" is not evidence.
Overlap with conventions/claude-md-content-tiering.md scored MODERATE (2 of 5
dimensions: same root-cause thesis, overlapping files; different tiering axis
and different prevention), so a new doc was written rather than folding into it.
Flagged in the Related section as a consolidation candidate if a third
documentation learning appears.
CONCEPTS.md — new Documentation cluster (Historical record, Superseded decision,
Plan status) and one flagged ambiguity recording that a present-tense historical
record is not a claim about the current system. These three are now referenced by
CLAUDE.md and both doc READMEs, so they needed defining.
Discoverability check: no edit needed — CLAUDE.md's entry-point table already
surfaces docs/solutions/ with its frontmatter fields and CONCEPTS.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four rounds of CLAUDE.md reduction (255 -> 305 -> 179 -> 74 lines) turned
up one consistent finding: every stale fact was a *description* of code or
config, never a rule. Two had been written by Claude days earlier.
Documents the operational test ("does this line change what Claude does on
a task where it wouldn't otherwise open the relevant file?"), the tiering
table, why gotchas are the one category that cannot move to a read-on-demand
docs/exceptions/, invariants-over-enumerations, and how to tell when a
reduction pass has hit the floor.
Also surfaces the docs/solutions frontmatter fields in CLAUDE.md's
entry-point table so the store is greppable by module, not just browsable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Close the remaining root-owned bind-mount vector: build-assets (a docker
run, missed by the docker-exec fix in 209b804) now runs as the host
uid/gid with HOME=/tmp for npm's cache. Verified: build completes clean,
zero root-owned files under user/themes, bundles byte-identical.
Solution doc updated from "still open" to fixed; CLAUDE.md stack section
now matches the Dockerfile's Grav 2.0.7.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195b3cDdMeize2Mm1FgC2aU
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0195b3cDdMeize2Mm1FgC2aU
The install-plugins fix (209b804) only covered docker exec. build-assets
runs `docker run node:20-alpine` without --user, so it still writes
root-owned node_modules + esbuild bundles into user/themes/ — which is what
blocked `git worktree remove` at teardown. Broaden the doc and prevention
rule to cover docker run, with the --user fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
Land the owner trip publish/unpublish toggle to local main: Playwright specs
(TP1-TP8), the ce-compound solution doc + CONCEPTS.md Published/Draft concept,
and the plan/spec docs. Bumps the user submodule pin to 543e8e3 (the merged
user/ main containing the feature + denmark-2026 cover content).
Local landing only — nothing pushed.
Document the trip publish-toggle cache-invalidation finding: an in-place
trip.md `published` edit under cache.check.method: folder + APCu driver stays
stale because the folder checksum is unchanged AND the web APCu store is
unreachable by a CLI clearcache — fixed with apcu_clear_cache() from the web
request. Cross-link the sibling grav-deleteall doc (the create/delete case) as
necessary-but-not-sufficient here, and add the Published/Draft trip status
concept to CONCEPTS.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
New learning: docker exec defaults to root, so make targets writing into
the ./user bind mount (esp. install-plugins -> gpm) created root-owned
files (11,624 accumulated), breaking worktree-rm. Fix: HOST_UID/HOST_GID +
`-u` on file-writing execs while the grav container still boots as root.
Cross-linked reciprocally with the sibling docker-dev-env upgrade doc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
New ui-bugs learning: cropResize fits-inside (returns a source-aspect
sliver), cropZoom crops-to-fill. Using cropResize for a cover/banner
strip hands the browser a portrait sliver that object-fit:cover then
upscales into a blur. Documents the fix, the empirical op comparison,
and prevention (verify Medium op dimensions; guard retina upscaling;
regression-test composition, not just the URL).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDS6t8wcpbwKvvrxykVQ5K
Document why deleteAll() alone left a freshly-posted entry 404-ing on its
edit-prefill API lookup (regular-pages index keyed on config->checksum, which
survives a create under cache.check.method:folder) and why the fix adds
Cache::invalidateCache(). Note the H1/M8 travelling:true coverage gap as a
tracked residual. Cross-link from bugs-and-fixes.md.
Code review F8 (learnings) + F5 (residual-gap tracking).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mpdu3Dt1iVoozHwAMyjrbn
Update the git-sync secret-exposure solution doc with today's operational
lesson: untracking an already-committed secret under a live bidirectional
sync. Covers the direction:both force-push-revert trap, the freeze-every-
server-first sequence, audit-before-reset (authoritative secret in env/),
the stale origin/main ref + sparse-checkout gotchas, and the ignore:-field
mechanism.
Add Makefile targets that supported the fix:
- remote-secrets-audit: secret-safe (existence + size + git ls-files, never
contents) audit of config/ vs env/<host>/config secret locations
- remote-content-status: also show the .gitignore diff git-sync regenerates
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New learning: docs/solutions/tooling-decisions/upgrade-local-grav-core-rebuild-docker-image.md
— the local Grav core is baked into the Docker image (only ./user is bind-mounted),
so it upgrades by a Dockerfile URL bump + image rebuild + `docker rm -f` recreate,
not the `gpm self-upgrade` the servers use (non-durable in-container).
Refreshed three docs this exposed as stale/incomplete:
- local-setup.md: rewrote the stale "newer Grav RC" section with the durable
rebuild procedure (recreate gotcha, verify, plugin refresh, non-durability note).
- deploy-cycle.md: Phase 0 now upgrades the local core; state-model notes the
image as a fourth surface beyond the three server layers.
- stale-grav-version-blocks-api-plugin-install.md: version-authority surfaces
3 -> 4 (hardcoded Dockerfile URL); clarified .env* GRAV_VERSION governs fresh
remote installs only, never the local Docker core.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
ce-compound-refresh (scope: stale-grav-version-blocks-api-plugin-install).
Update (not consolidate — distinct triggers, moderate overlap): add a Related
back-link to the new grav-plugin-config-without-code-wont-enable sibling and
broaden the "verify ls user/plugins/<name>" prevention bullet to note the
config-without-code desync as a second non-functional-plugin case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
Documents the git-sync prod incident where the plugin was inert despite
`enabled: true`, because its CODE was missing from user/plugins/git-sync/
while its CONFIG survived in the per-host env tree. Fix: GPM reinstall
(make remote-gpm-install-prod PKG=git-sync) + clearcache. Captures the
diagnostic (ls the plugin dir before touching config) and the remote-only
plugin caveat (not in plugins.txt, not restored by the standard flows).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
The prior model claimed git-sync's add-set is scoped to the configured
`folders` (pages/config/themes), so accounts/ and user/env/ were "safe by
construction." That was wrong: prod auto-commit 9337003 pushed the whole
user/env/<host>/config tree (JWT secret, CSRF salt, git-sync token + webhook
secret), accounts/mischa.yaml, and system.yaml to Gitea — all outside the
configured folders. git-sync stages everything under user/ not gitignored;
.gitignore is the only reliable exclusion.
- Rewrite the architecture-patterns doc around the corrected predicate and
document the incident + remediation (gitignore /env/, rotate token+webhook).
- Correct git-sync-notes.md: env/ is NOT safe on folder scope; drop the
"never reaches Gitea" claim; fix the secret-files table intro + add /env/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
Knowledge-track: functional Grav plugin config must live in the tracked
user/config/plugins/<name>.yaml override, not the gitignored (and
gpm-update-wiped) user/plugins/<name>/<name>.yaml. Secrets stay in
gitignored *-private.php. Root of the api plugin being unconfigured on
the fresh prod install. Cross-linked with the api/rc.10 install-failure
doc.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
Fresh prod install ran Grav rc.10 (stale .env.prod GRAV_VERSION), so GPM
would not serve the api plugin (needs >=2.0.4); admin2 (auth via /api/v1)
then 404'd login silently. Documents the dead ends (deploying api config,
gpm index -f, same-channel assumption) and the fix (self-upgrade core +
reinstall + bump .env.prod). Adds reciprocal 'same 2026-07-04 cutover'
cross-links across the three sibling deploy gotchas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
Non-FastCGI host + Apache mod_deflate: Grav's shutdown early-close emits
Content-Encoding: identity while mod_deflate adds gzip -> two conflicting
headers -> browsers render raw gzip bytes. Fix: debugger.shutdown.close_
connection:false in the prod env override. Documents the dead-end
(cache.gzip/allow_webserver_gzip take the same code path) and the
browser-style curl + screenshot verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
On Admin2-only installs, login new-user auto-detects the admin permission
type to api-only, so accounts lack admin.login and silently fail on
access.admin.login-gated pages like /gpx-manager. Document the root cause,
the --admin-type both fix baked into make test-account, and prevention.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Np4cMQLF77i664CAQXySzU
Add docs/solutions/architecture-patterns/dual-repo-submodule-workflow.md
covering the outer-repo + user/ submodule structure, when to bump the pin
(cross-repo feature boundaries, not routine content), the reachable/push-first
rules, and the worktree + per-worktree dev-server flow. Update CLAUDE.md's
folder explanation and add a "Dual-repo submodule structure" section.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Capture the safe procedure + three non-obvious traps from the standalone-
page cleanup: keep the folder as a routable:false data container, repoint
Back-link fallbacks to the grandparent (silent direct-landing regression),
and sync the gitignored demo source + Makefile or the next demo-load undoes it.
Seed CONCEPTS.md (Trip, Entry, Story, Active Trip) and surface docs/solutions/
+ CONCEPTS.md in CLAUDE.md so future sessions discover them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>