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
4.3 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.