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
5.0 KiB
title, date, category, module, problem_type, component, severity, symptoms, root_cause, resolution_type, related_components, tags
| title | date | category | module | problem_type | component | severity | symptoms | root_cause | resolution_type | related_components | tags | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cache.deleteAll() doesn't rebuild the page-tree index — a freshly-posted entry 404s when opened for editing | 2026-07-07 | integration-issues | cache-on-save | integration_issue | plugin | high |
|
incomplete_setup | code_fix |
|
|
cache.deleteAll() doesn't rebuild the page-tree index
Context — this is BUG-001 Part 2
BUG-001 ("new entry not visible after form
submission") was fixed by wiring $this->grav['cache']->deleteAll() into the
cache-on-save plugin's onFormProcessed hook. That made new entries appear in
the trip feed immediately. It was not the whole story: deleteAll() drops
the Doctrine store (rendered-page cache, feed HTML, etc.) but does not force
Grav to rebuild its regular-pages index.
The gap only surfaced once the shared /post form gained an edit mode
(?edit=<route>), whose prefill does GET /api/v1/pages{route}. On a fresh
create that request would 404 — so the owner opening the entry they had just
posted saw "This entry no longer exists."
Root cause
Grav's regular-pages index is keyed on:
md5(dirs + folderHash + config->checksum() + lang) // Pages::buildRegularPages
With cache.check.method: folder (our setting), the folderHash component does
not necessarily change when a new child folder is added inside an existing
tree — so the index key stays the same and the stale index (missing the new
entry) is reused. deleteAll() clears cache stores but does not change any of
the inputs to that key, so the tree is not rebuilt. The new page is on disk and
in the feed (which re-reads children), but the API lookup by route resolves
through the cached index and 404s.
Fix
Add a second invalidation step alongside deleteAll():
use Grav\Common\Cache;
// ...
$this->grav['cache']->deleteAll();
Cache::invalidateCache(); // touch(system.yaml) → bumps config->checksum()
Cache::invalidateCache() is lightweight and idempotent — it touch()es
system.yaml, calls clearstatcache() and opcache_reset() (verified in Grav
core Cache.php). Touching system.yaml bumps config->checksum(), which
changes the index key, so the tree rebuilds on the next request and the new
entry becomes resolvable by route.
Latch it — the hook fires 4× per submit
onFormProcessed fires once per process: action, and post-form.md has four
(add_page, upload, message, reset). Without a guard the
deleteAll() + invalidateCache() pair runs four times per post (a full store
wipe + system.yaml touch each time). Gate it with a once-per-request latch
($cacheInvalidated), the same pattern already used for photo reconciliation
($photosReconciled). See user/plugins/cache-on-save/cache-on-save.php.
How to verify
- Post a new entry via
/post. - From the trip feed, click the new card's Edit link.
- The form prefills with the entry's title/body — no "no longer exists" banner.
Regression test: tests/ui/post/edit-mode.spec.js ES1 (create → open the
feed card's Edit link → change title + body → Save → assert on disk).
Residual coverage gap (tracked, not fixed here)
tests/ui/home/home.spec.js H1 and tests/ui/maps/maps.spec.js M8
require site.travelling: true to exercise the active-trip home feed + home GPX
map. The committed local site.yaml runs travelling: false (owner's testing
config, intentionally not committed as true), so both specs skip loudly
with a reason rather than fail misleadingly. They validate whenever the site is
in travelling mode. This is a known gap in this environment, not a silent hole —
provisioning travelling: true in a dedicated test config would close it.
Related — Part 3: in-place edits + APCu
The Cache::invalidateCache() fix above completes deleteAll() for the
create/delete case, because a new or removed child folder advances
folderHash and the system.yaml touch bumps config->checksum(). It is
necessary but not sufficient for an in-place frontmatter edit (e.g. a
trip publish toggle) under cache.driver: auto (APCu): the folder structure is
unchanged, and APCu lives in web-server shared memory that a CLI bin/grav clearcache cannot reach. That case additionally requires apcu_clear_cache()
called from the web request. See
grav-in-place-header-edit-apcu-cache-stale.md.