The features JSON column required defensive fallback stubs in three
places (Plan::resolveForUser, PlanFeatures::__construct, PlanSeeder)
and silently swallowed misspelled keys. Typed columns give Eloquent
type-safe reads, simplify the Filament form (no more dotted JSON
paths), and let resolveForUser fail loud when the free row is
missing.
PlanFeatures public API is unchanged so consumers (jobs, middleware)
need no rewrites — one missed JSON read in SendScheduledWhatsAppJob
was caught and converted to a typed where() query.
tests/Pest.php seeds PlanSeeder in beforeEach so any feature test
that resolves a plan finds the free row, mirroring production where
plans always exist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Consolidate prediction functionality by merging /api/prediction endpoint into /api/stations response. Move prediction logic from PredictionController into StationController, returning prediction data alongside station results. Replace usePrediction composable with unified useStations that returns {stations, meta, prediction}. Remove PredictionRequest, related tests, and unused Vue components (FuelFinderTest, MapTest, RecommendationTest, StationListTest). Add PredictionFull component and UpsellBanner. Extend NationalFuelPredictionService to include weekly_summary (7-day series, yesterday/today averages, cheapest/priciest days) and oil signal from price_predictions table. Update Home.vue to consume prediction from stations response. Add Plan::resolveCadenceForUser helper and configure Cashier to use custom Subscription model.
Reconciles tier docs with `PlanSeeder` reality (basic has price_threshold
and score_alerts; schema is stripe_price_id_monthly + stripe_price_id_annual)
and introduces the display-name layer from pricing-plan.md v2.
- PlanTier::label() + Plan::displayName() + PlanFeatures::displayName()
expose user-facing names (Free/Daily/Smart/Pro); backend identifiers stay
basic/plus/pro so every call site, Stripe mapping, and test keeps working.
- push.frequency key added to features JSON (none/daily/triggered), mirroring
email.frequency so Daily's daily push is distinguishable from Smart/Pro's
triggered push. Seeder, factory, free-tier stubs, and Filament form updated.
- Homepage pricing cards renamed: Basic→Daily, Plus→Smart; badge
"Most Popular"→"Most pick this"; CTAs refreshed.
- docs/tiers.md change log records the full diff.
Fleet tier, 14-day trial copy, and Smart dark-card treatment deferred.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Delete unused Livewire Search test and fuel type select Blade component
- Move subscription webhook listener from EventServiceProvider to AppServiceProvider
- Add FUEL_TYPES global config to app layout for client-side use
- Add Billable trait to User model and include email_verified_at in fillable
- Implement monthly/annual cadence toggle with pricing display and smart CTA routing on homepage
- Update VerifyApiKeyMiddlewareTest to use e10 instead of petrol
- Refactor PollFuelPrices to auto-refresh stale stations based on last_seen_at
- Add incremental polling with cached timestamp and effective-start-timestamp param to FuelPriceService
- Normalize amenities/fuel_types from API objects to flat arrays, skip stations missing required fields
- Log response body on API failures in ApiLogger
- Default homepage sort to 'reliable' instead of 'price'