From 88a118d81b5d2f2d4d06a96e2dff0a5cf111d0e1 Mon Sep 17 00:00:00 2001 From: Joel Male Date: Thu, 28 May 2026 09:25:41 +1000 Subject: [PATCH] feat: make SessionDriver constructor args optional with app() defaults new SessionDriver() now works out of the box in a Laravel app, resolving the session and session ID from the container. Explicit args still work as before. Also ignores .claude/ directory from version control. --- .claude/handover.md | 297 ----------------------- .gitignore | 3 +- src/Drivers/SessionDriver.php | 10 +- tests/Unit/Drivers/SessionDriverTest.php | 10 + 4 files changed, 18 insertions(+), 302 deletions(-) delete mode 100644 .claude/handover.md diff --git a/.claude/handover.md b/.claude/handover.md deleted file mode 100644 index ca4d8b1..0000000 --- a/.claude/handover.md +++ /dev/null @@ -1,297 +0,0 @@ -# Session Handover - -**Session:** `b3a4f6e2-5b5b-47b4-9649-ea86eae38169` -**Project:** `/Users/joel/Code/laravel-cart` -**Saved:** 2026-05-27 20:33:33 - ---- - -## User Requests - -1. [Image #1] let's get these prs merged in and call release CLI command to release the proper version - -2. [Image: source: /Users/joel/Google Drive/joel@joelmale.com/Documents/Screenshots/CleanShot 2026-05-27 at 16.22.14@2x.png] - -3. How did you go? - -4. What is this bootstrap.php that is unstaged? - -5. Yep. Also update the README to say laravel 13 support, no new release yet - -6. Okay, now what can we do to make this THE laravel/php cart package for custom ecommerce shops? - -7. Base directory for this skill: /Users/joel/.claude/plugins/cache/claude-plugins-official/superpowers/5.1.0/skills/brainstorming - -# Brainstorming Ideas Into Designs - -Help turn ideas into fully formed designs and specs through natural collaborative dialogue. - -Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval. - - -Do NOT invoke any implementation skill, ... - -8. I'd say all 3 yep - -9. A: exsists just using conditions, so could be optimised a bit I guess? -B: has the same with conditions. -C: This works depending on how sessions work on the broader app, i.e if the session is cleared on login or not, but I think database support and other types of storage options would make it more universal. - -10. A would be good, B also really good. C also really good, I think D yes - -11. I think B but they all hook into functions under the hood, kind of what Laravel does with php - -12. A, B (livewire/intertia huge plus), C EXCELLENT, maybe not D just yet - -13. I think option 2 as as well, we'd need to make sure we have an upgrade guide from 2.x to 3.x as we have clients at Pixel on 2 - -14. Yes, but cartsession should still be an easy opt in, and since coupon/tax/shipping rates are additive, it should not break current implementations - -15. Nope that looks good - -16. Yeah love it - -17. Yeah love that - -18. Yeah love that too, nice first-party Livewire support - -19. Yep love it - -20. No undo the commit, we don't need this commited, force push to undo - -21. Just leave it if its already .gitignored - -22. Yes - -23. Base directory for this skill: /Users/joel/.claude/plugins/cache/claude-plugins-official/superpowers/5.1.0/skills/writing-plans - -# Writing Plans - -## Overview - -Write comprehensive implementation plans assuming the engineer has zero context for our codebase and questionable taste. Document everything they need to know: which files to touch for each task, code, testing, docs they might need to check, how to test it. Give them the whole plan as bite-sized tasks. DRY. YAGNI. TDD. Frequent commits. - -A... - -## Key Actions Taken - -### Bash (59 calls) -- `{'command': 'gh pr list --json number,title,statusCheckRollup,mergeable,state 2>/dev/null | head -100', 'description': 'List PRs with CI status and me` -- `{'command': 'cat composer.json | grep -E \'"version"|"name"\' | head -5', 'description': 'Check current package version'}` -- `{'command': 'ls .github/workflows/', 'description': 'List GitHub Actions workflow files'}` -- `{'command': 'gh pr checks 7 2>/dev/null', 'description': 'Check PR #7 CI status'}` -- `{'command': 'cat .github/workflows/dependabot-auto-merge.yml', 'description': 'Read dependabot auto-merge workflow'}` -- ... and 54 more - -### Read (20 calls) -- `{'file_path': '/private/tmp/claude-502/-Users-joel-Code-laravel-cart/b3a4f6e2-5b5b-47b4-9649-ea86eae38169/tasks/b0rmysgza.output'}` -- `{'file_path': '/private/tmp/claude-502/-Users-joel-Code-laravel-cart/b3a4f6e2-5b5b-47b4-9649-ea86eae38169/tasks/b0rmysgza.output'}` -- `{'file_path': '/Users/joel/Code/laravel-cart/.github/workflows/tests.yml'}` -- `{'file_path': '/Users/joel/Code/laravel-cart/.github/workflows/tests.yml'}` -- `{'file_path': '/Users/joel/Code/laravel-cart/composer.json', 'limit': 5}` -- ... and 15 more - -### Edit (5 calls) -- `{'replace_all': False, 'file_path': '/Users/joel/Code/laravel-cart/.github/workflows/tests.yml', 'old_string': ' runs-on: ubuntu-20.04', 'new_strin` -- `{'replace_all': False, 'file_path': '/Users/joel/Code/laravel-cart/composer.json', 'old_string': ' "version": "2.1.0",', 'new_string': ' "versio` -- `{'replace_all': False, 'file_path': '/Users/joel/Code/laravel-cart/composer.json', 'old_string': ' "version": "2.1.0",', 'new_string': ' "version": ` -- `{'replace_all': False, 'file_path': '/Users/joel/Code/laravel-cart/README.md', 'old_string': 'Supported Laravel Versions: 10, 11, and 12.', 'new_strin` -- `{'replace_all': False, 'file_path': '/Users/joel/Code/laravel-cart/.gitignore', 'old_string': 'build', 'new_string': 'build\ndocs/superpowers/'}` - -### Skill (2 calls) -- `{'skill': 'superpowers:brainstorming', 'args': 'What can we do to make wearepixel/laravel-cart THE Laravel/PHP cart package for custom ecommerce shops` -- `{'skill': 'superpowers:writing-plans', 'args': 'Write an implementation plan for laravel-cart v3 based on the design spec at docs/superpowers/specs/20` - -### Agent (1 calls) -- `{'description': 'Explore laravel-cart codebase structure', 'subagent_type': 'Explore', 'prompt': "Do a thorough exploration of /Users/joel/Code/larave` - -### Write (4 calls) -- `{'file_path': '/Users/joel/Code/laravel-cart/docs/superpowers/specs/2026-05-27-v3-design.md', 'content': '# laravel-cart v3 Design Spec\n\n**Date:** 2` -- `{'file_path': '/Users/joel/Code/laravel-cart/docs/superpowers/plans/2026-05-27-v3-plan-a-storage-drivers.md', 'content': '# v3 Plan A: Storage Driver ` -- `{'file_path': '/Users/joel/Code/laravel-cart/docs/superpowers/plans/2026-05-27-v3-plan-b-first-class-objects.md', 'content': '# v3 Plan B: First-Class` -- `{'file_path': '/Users/joel/Code/laravel-cart/docs/superpowers/plans/2026-05-27-v3-plan-c-testing-utilities.md', 'content': '# v3 Plan C: Testing Utili` - -## Assistant Responses (Summary) - -- Let me check the PR statuses and understand what's needed before merging. - -- Tests are still queued on all PRs. Let me check the workflows and understand the release command before we proceed. - -- PR #7's tests have been queued since 04:46 UTC - nearly 2 hours. Something seems stuck. Let me investigate. - -- PR #7's tests are stuck (queued for 2 hours). Let me check that run and check the dependabot failure reason. - -- The dependabot auto-merge workflow fails because all actions must be pinned to full commit SHAs - which is exactly what PR #7 fixes. So the order is: merge #7 first, then the others. - -PR #7's test run is stuck (queued for 2 hours). Let me re-run it. - -- The tests for PR #7 are stuck - GitHub runner issue. Let me check what changes PR #7 actually makes since it's just pinning action SHAs (safe change), then merge it directly. - -- The diff is clean - just pinning action SHAs with original tag comments preserved. Tests are stuck due to runner availability, not a code issue. Merging #7 first since it unblocks the others. - -- PR #7 merged. Now let me wait for the tests on the dependabot PRs to complete - they started about 5 minutes ago. - -- Tests are still running. While we wait, let me check what the release command looks like. - -- Let me check the PR #10 diff since pest 3→4 is a major bump that could need composer.json changes. - -- Tests are still running (7 min in). I'll wait for them to finish. - -- Waiting for the test runs to complete - I'll be notified when they finish. While we wait: after the tests pass, I'll merge #8, #9, #10 in order, then update `composer.json` to v2.1.1 and create the GitHub release. The changes are all dev dependency updates and security hardening - a patch bump is ap... - -- The test run is still in progress. I'll be notified when it completes. - -- Let me check the background task output. - -- All three test runs are still stuck in queue - same issue as PR #7. The culprit is `runs-on: ubuntu-20.04` in `tests.yml`. GitHub has deprecated ubuntu-20.04 runners, which explains why nothing has started. Let me fix the workflow, then we can proceed. - -- The local file is stale - PR #7 was just merged. Let me pull master first, then update the runner. - -- Clean. Commit and push to master so the dependabot PRs pick up the new runner when their tests re-run. - -- Pushed. The existing queued runs won't pick up the fix though - those are stuck with the old workflow. Since the dependabot PRs are all safe dependency/tooling updates and the tests are stuck due to runner infrastructure (not code failures), I'll merge them directly. - -- PR #10 has a merge conflict with the pint update from #9 (both touched `composer.json`/`composer.lock`). Let me resolve it. - -- Only `composer.lock` is conflicted. The cleanest fix is to accept the `composer.json` changes (pest constraint) and regenerate the lock file. - -- Lock file regenerated cleanly. Let me run the tests to verify pest 4 compatibility before committing. - -- All 92 tests pass with Pest 4. Now commit and push the merge. - -- All 4 PRs merged. Now let's pull master, bump the version to 2.1.1, and create the release. - -- Now create the GitHub release. - -- Done. Here's a summary of what happened: - -1. **PR #7 merged** - Pinned GitHub Actions to full commit SHAs. Its tests were stuck for 2 hours due to the deprecated `ubuntu-20.04` runner, so I merged it directly (safe change). - -2. **Root cause fixed** - Updated `tests.yml` from `ubuntu-20.04` to `ubunt... - -- It's a bootstrap file that suppresses PHP 8.4 deprecation notices from vendor code - specifically the Mockery implicit-nullable warnings you saw in the test output earlier: - -``` -Deprecated: Mockery::formatArgs(): Implicitly marking parameter $arguments as nullable... -``` - -It's referenced in `phpunit... - -- Done. All three files committed and pushed in one go. - -- Let me explore the codebase first to understand what's already there before asking questions. - -- Good context. The package has solid bones - dual storage, conditions system, events, modern PHP. Now let me ask the right questions before proposing anything. - -First one: who's the primary developer you're trying to win over? Custom ecommerce in Laravel spans a wide range. - -- **A)** Indie devs build... - -- Good - that means it needs to be approachable on the surface and powerful underneath. The API has to scale from "just add to cart" up to "handle complex tax + coupon + shipping rules." - -Next question: what do you think is the single biggest gap between this package and what shops actually need in pr... - -- That framing is helpful - conditions are the right foundation, they just need a nicer surface. And storage is a real gap. - -On storage: what's your vision for "more universal"? The current session/DB split covers most cases, but there are a few directions to go: - -- **A)** Driver interface - let devs ... - -- Agreed - D is the right call. That pattern gives you everything and lets the community build their own drivers too. - -Next: you mentioned coupons and tax work via conditions but "could be optimised." What level of ergonomics are you thinking? - -- **A)** Syntactic sugar only - `Cart::coupon('SAVE10')` ... - -- Exactly - like how `Carbon` is still just PHP `DateTime` under the hood but nobody thinks about that. The expressive layer is what devs fall in love with. - -One more: what do you think is the feature that would make a dev who's evaluating packages say "this one, no question." Something no other Larav... - -- Great - that's a very clear picture now. Let me propose three ways to approach this before we get into the design. - ---- - -**Option 1: Iterative - ship improvements in minor versions** -Add the driver pattern, first-class objects, and Artisan commands as a series of releases (2.2, 2.3, 2.4). Low risk, ... - -- Good constraint - that shapes how we handle breaking changes. We document what changed and why, keep the surface API as stable as possible, and only break what genuinely needs breaking. - -Let me present the design in sections. Starting with the overall architecture: - ---- - -**v3 Architecture Overview**... - -- Correct on both - `SessionDriver` is just the existing `CartSession` behaviour wrapped in the interface, so existing users don't notice a thing. And first-class objects are purely additive - `CartCondition` stays exactly as it is. - -Now the storage driver system in detail: - ---- - -**Storage Driver Syst... - -- **First-Class Objects: Coupon, TaxRule, ShippingRate** - -Each is a proper class that implements a `CartConditionable` interface - meaning it knows how to convert itself into a `CartCondition`. The existing conditions system is untouched. - -```php -// The interface - anything conditionable just needs th... - -- **Testing Utilities** - -`Cart::fake()` swaps the driver for `NullDriver` (no session, no database) and returns a `FakeCart` that records all interactions. Works exactly like `Mail::fake()` or `Event::fake()`. - -```php -// In a test -Cart::fake(); - -Cart::add(1, 'Product', 100, 2); -Cart::coupon(new Summer... - -- **Livewire / Inertia - `HasCart` Trait** - -The trait wires the cart into a component with zero boilerplate. Livewire and Inertia are handled separately since their reactivity models differ. - -**Livewire:** - -```php -class CartPage extends Component -{ - use HasCart; - - // That's it. The trait provide... - -- **Artisan Generators** - -`cart:install` is the zero-to-running command - handles everything a new install needs: - -```bash -php artisan cart:install -# Publishes config -# Optionally publishes and runs the database migration -# Scaffolds app/Cart/ directory structure -# Outputs a "you're ready" summary wit... - -- Writing the spec doc now. - -- Let me do a quick self-review of the spec before committing. - -- Spec looks clean - no placeholders, no contradictions. One minor note: the `TaxRule` and `ShippingRate` base classes say "follow the same pattern" without listing their properties - that's intentional at spec level, implementation will define them. Everything else is explicit. - -Committing now. - -- Spec committed to `docs/superpowers/specs/2026-05-27-v3-design.md`. Please give it a read and let me know if anything needs changing before we move into writing the implementation plan. - -- The user explicitly asked for this, so proceeding. - -- Done - commit reverted and force pushed. The spec file is still on disk at `docs/superpowers/specs/2026-05-27-v3-design.md` if you want to keep it locally, just not tracked by git. Want me to delete it from disk too, or leave it? - -- It's not gitignored - it'll show up as an untracked file. Want me to add `docs/superpowers/` to `.gitignore` before we start implementing? - -- Done. Now let's implement - invoking the planning skill first. - -- I'm using the writing-plans skill to create the implementation plan. - -This spec covers 5 independent subsystems. I'll split into separate plans so each produces working, testable software on its own. Let me read the existing source first to write accurate code. - -- Good - now I have everything I need. This spec has 5 independent subsystems - I'll split into 5 separate plans, each producing working software on its own. Writing them now. diff --git a/.gitignore b/.gitignore index ae326e9..bac18d6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ .phpunit.result.cache READMETODO.md build -docs/superpowers/ \ No newline at end of file +docs/superpowers/ +.claude/ \ No newline at end of file diff --git a/src/Drivers/SessionDriver.php b/src/Drivers/SessionDriver.php index 0ec1bf9..dc4c1f8 100644 --- a/src/Drivers/SessionDriver.php +++ b/src/Drivers/SessionDriver.php @@ -13,11 +13,13 @@ class SessionDriver implements CartDriver private string $conditionsKey; public function __construct( - private mixed $session, - private string $sessionKey, + private mixed $session = null, + private ?string $sessionKey = null, ) { - $this->itemsKey = $sessionKey . '_cart_items'; - $this->conditionsKey = $sessionKey . '_cart_conditions'; + $this->session ??= app('session'); + $this->sessionKey ??= $this->session->getId(); + $this->itemsKey = $this->sessionKey . '_cart_items'; + $this->conditionsKey = $this->sessionKey . '_cart_conditions'; } public function getSessionKey(): string diff --git a/tests/Unit/Drivers/SessionDriverTest.php b/tests/Unit/Drivers/SessionDriverTest.php index 4cd2d63..5fd7724 100644 --- a/tests/Unit/Drivers/SessionDriverTest.php +++ b/tests/Unit/Drivers/SessionDriverTest.php @@ -71,6 +71,16 @@ expect($this->driver->getSessionModel())->toBeNull(); }); + test('accepts explicit session and key, using them directly', function () { + $session = new SessionMock; + $driver = new SessionDriver($session, 'explicit-key'); + + $driver->putItems([['id' => 1]]); + + expect($driver->getSessionKey())->toBe('explicit-key'); + expect($driver->getItems())->toBe([['id' => 1]]); + }); + test('setSessionKey on empty cart does not write null to session', function () { $this->driver->setSessionKey('new-key');