From fb83413b3d15df7c16981497272e50d488472d62 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Thu, 12 Mar 2026 09:52:54 +0000 Subject: [PATCH] Fix Tracker::remove not actually removing --- .gitignore | 1 + CLAUDE.md | 66 ++++++++++++++++++++++++++++++++++++++ src/Tracker/Manager.php | 4 ++- tests/Unit/TrackerTest.php | 31 ++++++++++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index 99d37f1..f49b9d7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ mix-manifest.json package-lock.json yarn.lock .idea/* +.claude/* diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5ee0528 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,66 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Commands + +```bash +# Run all tests +vendor/bin/pest + +# Run a single test file +vendor/bin/pest tests/Unit/TrackerTest.php + +# Run a specific test by name +vendor/bin/pest --filter "test name" + +# Lint (Laravel Pint) +vendor/bin/pint + +# Build CP assets +npm run build + +# Dev CP assets +npm run dev +``` + +## Architecture + +This is a Statamic addon (`thoughtco/statamic-cache-tracker`) that tracks which content items are rendered on each page, then automatically clears those pages from the static cache when content is updated. + +### Core Flow + +1. **Tracking** — `Http/Middleware/CacheTracker` runs on every GET request. It hooks into Statamic's augmentation lifecycle (via `Asset::hook`, `Entry::hook`, `LocalizedTerm::hook`, etc.) to collect "tags" for each content item rendered during the request. After a successful response, it stores a `URL → [tags]` mapping in cache. + +2. **Invalidation** — `Listeners/Subscriber` subscribes to Statamic save/delete events (entries, assets, terms, navs, forms, globals). When content changes, it dispatches `Jobs/InvalidateTags` which calls `Tracker::invalidate($tags)` to find all URLs containing those tags and clear them from the static cache. + +3. **Tag format** — Tags follow patterns like `asset:{id}`, `collection:{handle}:{id}`, `term:{id}`, `form:{handle}`, `nav:{handle}`, `global:{handle}`. Wildcard matching is supported via `*` prefix notation (e.g., `collection:blog:*`). + +### Key Classes + +- **`Tracker/Manager`** — Core logic. Stores `URL → tags[]` in cache under key `tracker::urls`. Handles `add`, `get`, `invalidate`, and `flush`. Tag matching supports wildcards. +- **`Http/Middleware/CacheTracker`** — Hooks into Statamic augmentation to collect tags during rendering. Also fires `TrackContentTags` event for custom integrations. +- **`Listeners/Subscriber`** — Maps Statamic events to tag generation and queues invalidation. +- **`Facades/Tracker`** — Facade for `Manager` (bound as `cache-tracker`). + +### Extending + +Custom tag sources can be registered via `Tracker::addAdditionalTracker()`, which accepts a closure or class with an `__invoke(Request $request, array &$tags)` signature and runs in a pipeline. + +The `TrackContentTags` event can be fired from custom code to inject additional tags into the current request's tracking. + +### CP Features + +- **Utility** — "Cache Tracker" utility in the CP for manually clearing URLs or wildcards. +- **Bulk Actions** — "View Cache Tags" and "Clear Cache" actions available on entry/term listings. +- **Permissions** — `view cache tracker tags` and `clear cache tracker tags`. + +### Testing Notes + +Tests use Pest with an `AddonTestCase` base that: +- Enables static caching with `half` strategy +- Creates a default `pages` collection with a `home` entry +- Disables stache disk writes +- Uses SQLite in-memory database + +Fixture views live in `tests/__fixtures__/resources/views/`. diff --git a/src/Tracker/Manager.php b/src/Tracker/Manager.php index f0fb5ab..9975417 100644 --- a/src/Tracker/Manager.php +++ b/src/Tracker/Manager.php @@ -135,6 +135,8 @@ public function remove(string $url) { $this->invalidateUrls([$url]); - $this->cacheStore()->forget(md5($url)); + $storeData = $this->all(); + unset($storeData[md5($url)]); + $this->cacheStore()->forever($this->cacheKey, $storeData); } } diff --git a/tests/Unit/TrackerTest.php b/tests/Unit/TrackerTest.php index 8886a0a..8bfb315 100644 --- a/tests/Unit/TrackerTest.php +++ b/tests/Unit/TrackerTest.php @@ -92,6 +92,37 @@ public function it_flushes() Event::assertDispatched(UrlInvalidated::class); } + #[Test] + public function it_removes_a_url() + { + Tracker::add('/page1', ['products:1']); + Tracker::add('/page2', ['products:2']); + + $this->assertCount(2, Tracker::all()); + + Tracker::remove('/page1'); + + $this->assertCount(1, Tracker::all()); + $this->assertNull(Tracker::get('/page1')); + $this->assertNotNull(Tracker::get('/page2')); + } + + #[Test] + public function it_removes_a_url_and_does_not_retrack_on_next_visit() + { + $this->get('/'); + + $this->assertCount(1, Tracker::all()); + + Tracker::remove(collect(Tracker::all())->first()['url']); + + $this->assertCount(0, Tracker::all()); + + $this->get('/'); + + $this->assertCount(1, Tracker::all()); + } + #[Test] public function it_invalidates_by_exact_tag_match() {