output:
Chunk ID: f8850b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 5338
Output:
## 11. Settings Pages

### 11.1 General Settings

**Route:** `GET /admin/settings`

**Livewire component:** `App\Livewire\Admin\Settings\General`

**Blade view:** `resources/views/livewire/admin/settings/general.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `storeName` | `string` | Store display name |
| `storeHandle` | `string` | Store handle (read-only or with warning) |
| `defaultCurrency` | `string` | Currency code (e.g., "EUR") |
| `defaultLocale` | `string` | Locale code (e.g., "en") |
| `timezone` | `string` | Timezone string |

| Action | Behavior |
|--------|----------|
| `save()` | Validates and saves store settings |

#### Layout

Two-column layout per section: left side has section label and description, right side has form fields.

**Store details section:**
- Left: Heading "Store details" + description "Basic information about your store."
- Right: Store name input, Store handle input (disabled, with description: "The store handle cannot be changed after creation.")

Separator between sections.

**Defaults section:**
- Left: Heading "Defaults" + description "Currency, language, and timezone settings."
- Right:
  - Default currency: Select (EUR/USD/GBP and more)
  - Default locale: Select (English/German/French and more)
  - Timezone: Select (populated from PHP timezone list)

Save button at the bottom (primary variant, right-aligned).

### 11.2 Domains Settings

Domains is a tab within the Settings page (`/admin/settings`), not a separate route. The Settings page has tabs: General, Domains, Shipping, Taxes, Checkout, Notifications.

**Livewire component:** `App\Livewire\Admin\Settings\Domains`

**Blade view:** `resources/views/livewire/admin/settings/domains.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `domains` | `Collection` | Store domains |
| `newHostname` | `string` | Hostname for new domain |
| `newType` | `string` | Type for new domain: `storefront`, `admin`, `api` |

| Action | Behavior |
|--------|----------|
| `addDomain()` | Creates a new domain record |
| `removeDomain(int $domainId)` | Removes a domain |
| `setPrimary(int $domainId)` | Sets domain as primary |

#### Layout

- Heading "Domains"
- Table of existing domains:

| Column | Content |
|--------|---------|
| Hostname | Domain hostname |
| Type | Flux badge: storefront/admin/api |
| Primary | Flux badge (green) "Primary" if `is_primary` |
| TLS | Flux badge showing TLS mode |
| Actions | "Set Primary" button, "Delete" button |

- "Add domain" button opens a modal

#### Add Domain Modal

| Property | Value |
|----------|-------|
| Modal name | `add-domain` |
| Max width | md |
| Heading | "Add domain" |

**Form fields:**
- Hostname input (placeholder: "shop.example.com")
- Type select: Storefront, Admin, API
- Cancel button (ghost) and "Add domain" button (primary)

### 11.3 Shipping Settings

**Route:** `GET /admin/settings/shipping`

**Livewire component:** `App\Livewire\Admin\Settings\Shipping`

**Blade view:** `resources/views/livewire/admin/settings/shipping.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `zones` | `Collection` | Shipping zones with rates |
| `editingZone` | `?ShippingZone` | Zone being edited |
| `zoneName` | `string` | Name for zone form |
| `zoneCountries` | `array` | Selected countries for zone |
| `editingRate` | `?ShippingRate` | Rate being edited |
| `rateName` | `string` | Rate name |
| `rateType` | `string` | `flat`, `weight`, `price`, `carrier` |
| `rateConfig` | `array` | Rate configuration (depends on type) |
| `rateActive` | `bool` | Whether rate is active |
| `testAddress` | `array` | Address fields for test tool |
| `testResult` | `?array` | Result of test address lookup |

| Action | Behavior |
|--------|----------|
| `openZoneModal(?ShippingZone $zone)` | Opens zone create/edit modal |
| `saveZone()` | Saves shipping zone |
| `deleteZone(int $zoneId)` | Deletes a shipping zone |
| `openRateModal(int $zoneId, ?ShippingRate $rate)` | Opens rate create/edit modal |
| `saveRate()` | Saves shipping rate |
| `deleteRate(int $rateId)` | Deletes a rate |
| `testShippingAddress()` | Tests which zone/rate matches a given address |

#### Layout Wireframe

```
+==================================================================+
| Shipping                                         [+ Add zone]    |
+==================================================================+
|                                                                   |
| +--------------------------------------------------------------+ |
| | ZONE: Domestic                            [Edit] [Delete]    | |
| | Countries: US, CA                                             | |
| |                                                               | |
| | Name      | Type    | Config           | Active | Actions    | |
| |-----------|---------|------------------|--------|------------|  |
| | Standard  | flat    | $5.00            | [on]   | [Ed] [Del]| |
| | Express   | flat    | $15.00           | [on]   | [Ed] [Del]| |
| | [+ Add rate]                                                  | |
| +--------------------------------------------------------------+ |
|                                                                   |
| +--------------------------------------------------------------+ |
| | ZONE: Europe                              [Edit] [Delete]    | |
| | Countries: DE, FR, GB, ...                                    | |
| | (rates table...)                                              | |
| +--------------------------------------------------------------+ |
|                                                                   |
| TEST SHIPPING ADDRESS                                             |
| Country: [v]  State: [    ]  City: [    ]  ZIP: [    ]           |
| [Test]                                                            |
| Result: Matched zone: Domestic                                    |
|         Standard - $5.00 / Express - $15.00                      |
+==================================================================+
```

Each zone is displayed as a card with:
- Zone name heading and Edit/Delete buttons in the header
- Countries list as muted text
- Rates table inside the zone card with columns: Name, Type (as badge), Config summary, Active (toggle switch), Actions (Edit/Delete buttons)
- "Add rate" button at the bottom of each zone card

"Add zone" button at the top of the page.

#### Zone Modal

| Property | Value |
|----------|-------|
| Modal name | `zone-form` |
| Max width | md |
| Heading | "Edit shipping zone" or "Add shipping zone" (dynamic) |

**Form fields:**
- Zone name input (placeholder: "Domestic, Europe, International...")
- Countries: scrollable checklist of all available countries with checkboxes
- Cancel button (ghost) and "Save zone" button (primary)

#### Rate Modal

| Property | Value |
|----------|-------|
| Modal name | `rate-form` |
| Max width | md |
| Heading | "Edit shipping rate" or "Add shipping rate" (dynamic) |

**Form fields:**
- Rate name input (placeholder: "Standard, Express...")
- Rate type select: Flat rate, Weight-based, Price-based, Carrier-calculated
- Dynamic config fields based on rate type:
  - **Flat:** Price input
  - **Weight-based:** Min weight (g) input, Max weight (g) input, Price input
  - **Price-based:** Min order amount input, Max order amount input, Price input
  - **Carrier:** Info callout: "Carrier-calculated rates require a carrier integration to be configured."
- Active toggle switch with "Active" text label
- Cancel button (ghost) and "Save rate" button (primary)

#### Test Shipping Address Tool

A card section at the bottom of the page:
- Heading "Test shipping address"
- Description: "Enter an address to see which shipping zone and rates match."
- 2-column grid of fields: Country (select), State/Region (input), City (input), ZIP/Postal code (input)
- "Test" button
- Result area (shown after test):
  - If matched: "Matched zone: [zone name]" and list of available rates with prices
  - If no match: Warning callout "No shipping zone matches this address."

### 11.4 Tax Settings

**Route:** `GET /admin/settings/taxes`

**Livewire component:** `App\Livewire\Admin\Settings\Taxes`

**Blade view:** `resources/views/livewire/admin/settings/taxes.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `mode` | `string` | `manual` or `provider` |
| `pricesIncludeTax` | `bool` | Whether prices include tax |
| `provider` | `string` | Provider name (e.g., `stripe_tax`) |
| `providerApiKey` | `string` | API key for tax provider |
| `manualRates` | `array` | Array of `{zone_name, rate_percentage}` |

| Action | Behavior |
|--------|----------|
| `save()` | Saves tax settings |
| `addManualRate()` | Adds a new empty rate row |
| `removeManualRate(int $index)` | Removes a manual rate |

#### Layout Wireframe

```
+==================================================================+
| TAXES                                                             |
+==================================================================+
| MODE SELECTION                                                    |
|  ( ) Manual tax rates - Define tax rates per zone manually       |
|  (o) Tax provider - Use an automated tax calculation service     |
+==================================================================+
| MANUAL RATES TABLE (shown when mode = "manual")                  |
| Zone name       | Rate (%)    |                                   |
|-----------------|-------------|                                   |
| [EU           ] | [19.00    ] | [trash]                          |
| [US-CA        ] | [7.25     ] | [trash]                          |
| [+ Add rate]                                                      |
+==================================================================+
| PROVIDER CONFIG (shown when mode = "provider")                   |
|  Provider: [Stripe Tax v]                                        |
|  API key:  [*************]                                       |
+==================================================================+
| [separator]                                                       |
| TAX-INCLUSIVE TOGGLE                                              |
|  [switch: ON]  Prices include tax                                |
|  Description: When enabled, the listed price includes tax.       |
|  Tax is calculated backwards from the price.                      |
+==================================================================+
| [Save]                                                            |
+==================================================================+
```

**Mode selection:** Two radio buttons - "Manual tax rates" (description: "Define tax rates per zone manually") and "Tax provider" (description: "Use an automated tax calculation service"). Live-bound.

**Manual rates table:** Shown when mode = "manual". Inline-editable table with columns: Zone name (text input), Rate % (number input with step 0.01). Each row has a trash button. "Add rate" ghost button with plus icon.

**Provider config:** Shown when mode = "provider". Provider select (Stripe Tax / None) and API key password input.

**Tax-inclusive toggle:** Toggle switch with "Prices include tax" label and description text explaining the behavior.

Save button at the bottom.

---

## 12. Themes Management

### 12.1 Themes List

**Route:** `GET /admin/themes`

**Livewire component:** `App\Livewire\Admin\Themes\Index`

**Blade view:** `resources/views/livewire/admin/themes/index.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `themes` | `Collection` | All themes for the store |

| Action | Behavior |
|--------|----------|
| `publishTheme(int $themeId)` | Publishes a theme (sets as active) |
| `duplicateTheme(int $themeId)` | Creates a copy of the theme |
| `deleteTheme(int $themeId)` | Deletes a theme |

#### Layout Wireframe

```
+==================================================================+
| Themes                                                            |
+==================================================================+
|                                                                   |
| +------------------+ +------------------+ +------------------+   |
| | [preview image]  | | [preview image]  | | [preview image]  |   |
| |                  | |                  | |                  |   |
| | My Theme   v1.0  | | Dark Theme v1.2  | | Minimal    v2.0  |   |
| | [Published]      | | [Draft]          | | [Draft]          |   |
| |                  | |                  | |                  |   |
| | [Customize] [...] | | [Customize] [...] | | [Customize] [...] |   |
| +------------------+ +------------------+ +------------------+   |
|                                                                   |
+==================================================================+
```

**Theme grid:** Responsive 3-column grid (1 on mobile, 2 on md, 3 on lg).

Each theme card contains:
- **Thumbnail area:** Aspect-video container with theme preview image or placeholder
- **Info section:**
  - Theme name (heading, size md) and version text
  - Status badge (published=green, draft=zinc)
- **Actions:**
  - "Customize" primary button (links to theme editor)
  - More actions dropdown (triggered by ellipsis icon button):
    - "Preview" menu item
    - "Publish" menu item (hidden if already published)
    - "Duplicate" menu item
    - Separator
    - "Delete" menu item (styled red/danger)

The published theme card has a blue ring border highlight.

### 12.2 Theme Editor

**Route:** `GET /admin/themes/{theme}/editor`

**Livewire component:** `App\Livewire\Admin\Themes\Editor`

**Blade view:** `resources/views/livewire/admin/themes/editor.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `theme` | `Theme` | The theme being edited |
| `sections` | `array` | List of theme sections from settings schema |
| `selectedSection` | `?string` | Currently selected section key |
| `sectionSettings` | `array` | Settings values for the selected section |
| `previewUrl` | `string` | URL for the live preview iframe |

| Action | Behavior |
|--------|----------|
| `mount(Theme $theme)` | Loads theme and its sections |
| `selectSection(string $sectionKey)` | Changes the selected section and loads its settings |
| `updateSetting(string $key, mixed $value)` | Updates a setting value |
| `save()` | Saves all section settings to theme_settings |
| `publish()` | Publishes the theme |
| `refreshPreview()` | Reloads the preview iframe |

#### Layout Wireframe (Full Width, Three-Panel)

```
+==================================================================+
| [<- Back to themes]                     [Save]  [Save & publish] |
+==================================================================+
| SECTIONS   |  LIVE PREVIEW (iframe)       |  SETTINGS PANEL      |
| (264px)    |  (flexible width)            |  (320px)             |
|            |                              |                      |
| Header     |  +------------------------+ |  Header Settings     |
| Hero       |  |                        | |  [separator]         |
| Features*  |  |  Live storefront       | |                      |
| Products   |  |  preview rendered      | |  Logo text:          |
| Footer     |  |  in an iframe          | |  [My Store        ]  |
|            |  |                        | |                      |
|            |  |                        | |  Background color:   |
|            |  |                        | |  [color picker]      |
|            |  |                        | |                      |
|            |  |                        | |  Show search:        |
|            |  |                        | |  [x] checkbox        |
|            |  |                        | |                      |
|            |  +------------------------+ |  Layout:             |
|            |                              |  [Centered       v]  |
+==================================================================+
  * = currently selected section (highlighted)
```

**Full-width layout** (no standard admin shell padding). Three-panel layout:

**Top toolbar:** A horizontal bar with:
- "Back to themes" button (ghost, with left arrow icon) linking to themes index
- "Save" ghost button and "Save and publish" primary button

**Left panel (264px):** Section list
- Heading "Sections"
- Vertical list of section buttons
- Active section gets highlighted background and bold text
- Clicking a section calls `selectSection()`

**Center panel (flexible):** Live preview
- Background color behind the iframe
- Full-height iframe loading the storefront preview URL
- Iframe has rounded border and shadow

**Right panel (320px):** Settings form
- Shows settings for the selected section
- Heading with section label
- Separator
- Dynamic form fields based on section field definitions:

| Field Type | UI Element |
|-----------|------------|
| `text` | Flux input (live-debounced at 500ms) |
| `textarea` | Flux textarea (live-debounced at 500ms, 3 rows) |
| `color` | Native color picker input |
| `select` | Flux select with options from field definition |
| `checkbox` | Flux checkbox |

- When no section is selected: centered text "Select a section to edit its settings."

---

## 13. Pages Management

### 13.1 Pages List

**Route:** `GET /admin/pages`

**Livewire component:** `App\Livewire\Admin\Pages\Index`

**Blade view:** `resources/views/livewire/admin/pages/index.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `search` | `string` | Search by page title |

| Computed Property | Purpose |
|-------------------|---------|
| `pages` | Paginated pages for the current store |

#### Layout

- Heading "Pages" with "Add page" primary button
- Search input
- Table columns: Title (link to edit), Handle, Status (Flux badge), Updated date
- Pagination

### 13.2 Page Form

**Route:** `GET /admin/pages/create` and `GET /admin/pages/{page}/edit`

**Livewire component:** `App\Livewire\Admin\Pages\Form`

**Blade view:** `resources/views/livewire/admin/pages/form.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `page` | `?Page` | Page being edited |
| `title` | `string` | Page title |
| `handle` | `string` | URL handle |
| `bodyHtml` | `string` | Page body content (HTML) |
| `status` | `string` | `draft` or `active` |
| `publishedAt` | `?string` | Publish date |

| Action | Behavior |
|--------|----------|
| `save()` | Validates and saves the page |
| `deletePage()` | Deletes the page |

#### Layout (Two-Column)

**Left column (2/3):**
- Title: Flux input
- Handle: Flux input
- Body: Flux textarea (16 rows)

**Right column (1/3):**
- Status: Flux select with Draft/Active
- Published at: Datetime-local input

Sticky save bar (same pattern as other forms).

---

## 14. Navigation Management

**Route:** `GET /admin/navigation`

**Livewire component:** `App\Livewire\Admin\Navigation\Index`

**Blade view:** `resources/views/livewire/admin/navigation/index.blade.php`

### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `menus` | `Collection` | Navigation menus (Main Menu, Footer Menu) |
| `editingMenu` | `?NavigationMenu` | Menu being edited |
| `menuItems` | `array` | Nested array of menu items for the editing menu |
| `editingItem` | `?array` | Item currently being edited in the modal |
| `editingItemIndex` | `?int` | Index of item being edited |
| `itemLabel` | `string` | Item label |
| `itemType` | `string` | `link`, `page`, `collection`, `product` |
| `itemUrl` | `string` | URL if type is link |
| `itemResourceId` | `?int` | Resource ID if type is page/collection/product |

| Action | Behavior |
|--------|----------|
| `selectMenu(int $menuId)` | Loads a menu for editing |
| `addItem()` | Opens the item form modal in create mode |
| `editItem(int $index)` | Opens the item form modal in edit mode |
| `saveItem()` | Saves the item (add or update) |
| `removeItem(int $index)` | Removes an item |
| `reorderItems(array $order)` | Updates item positions (supports nesting) |
| `saveMenu()` | Persists all menu items |

### Layout Wireframe

```
+==================================================================+
| Navigation                                                        |
+==================================================================+
|                                                                   |
| MENUS                                                             |
| +----------------------------+  +----------------------------+   |
| | Main Menu        [Edit]    |  | Footer Menu       [Edit]   |   |
| +----------------------------+  +----------------------------+   |
|                                                                   |
| MENU EDITOR (shown when a menu is selected)                      |
| +--------------------------------------------------------------+ |
| | Main Menu                                    [+ Add item]    | |


output:
Chunk ID: 627d57
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 340
Output:
**Files to create:**

| Component | Description |
|-----------|-------------|
| `Admin\Collections\Index` | Collection list |
| `Admin\Collections\Form` | Shared form component for create/edit collection with product picker, reorder products |
| `Admin\Customers\Index` | Customer list with search |
| `Admin\Customers\Show` | Customer detail: info, orders, addresses |
| `Admin\Discounts\Index` | Discount list with status/type filters |
| `Admin\Discounts\Form` | Shared form component for create/edit discount: code, type, value, dates, rules |
| `Admin\Settings\Index` | Tabbed settings: General, Domains, Shipping, Taxes, Checkout, Notifications |
| `Admin\Settings\Shipping` | Shipping zones CRUD, rates CRUD per zone |
| `Admin\Settings\Taxes` | Tax mode toggle, manual rate configuration |
| `Admin\Themes\Index` | Theme cards with Publish/Duplicate/Delete actions |
| `Admin\Themes\Editor` | Left: sections. Center: preview. Right: settings. |
| `Admin\Pages\Index` | Content pages list |
| `Admin\Pages\Form` | Shared form component for create/edit page with rich text editor |
| `Admin\Navigation\Index` | Menu management with drag-and-drop item ordering |
| `Admin\Analytics\Index` | Sales chart, traffic, funnel visualization, date range |
| `Admin\Search\Settings` | Synonyms, stop words, reindex button |
| `Admin\Apps\Index` | App directory |


command: sed -n '1550,1565p' specs/09-IMPLEMENTATION-ROADMAP.md

type: read

aggregated_output:
---

#### tests/Feature/Admin/SettingsTest.php

| Test Name | Setup / Preconditions | Key Assertions | Category |
|-----------|----------------------|----------------|----------|
| renders the settings page | GET /admin/settings | 200 response, sees settings tabs | happy path |
| updates general store settings | update store name | store_settings updated | happy path |
| configures shipping zones | create zone with countries and rates | shipping_zone and shipping_rates in DB | happy path |
| configures tax settings | set mode=manual, rate=1900, prices_include_tax=true | tax_settings updated | happy path |
| restricts settings to owner and admin roles | staff user GET /admin/settings | 403 response | authorization |
| manages store domains | add a new domain | store_domain created | happy path |

---

status: completed


output:
Chunk ID: ffc1ed
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 204
Output:

---

#### tests/Feature/Admin/SettingsTest.php

| Test Name | Setup / Preconditions | Key Assertions | Category |
|-----------|----------------------|----------------|----------|
| renders the settings page | GET /admin/settings | 200 response, sees settings tabs | happy path |
| updates general store settings | update store name | store_settings updated | happy path |
| configures shipping zones | create zone with countries and rates | shipping_zone and shipping_rates in DB | happy path |
| configures tax settings | set mode=manual, rate=1900, prices_include_tax=true | tax_settings updated | happy path |
| restricts settings to owner and admin roles | staff user GET /admin/settings | 403 response | authorization |
| manages store domains | add a new domain | store_domain created | happy path |

---


command: rg --files app/Models app/Policies app/Services database/factories database/seeders | rg "StoreSettings|Theme|Page|Navigation|TaxSettings|Shipping"

path: Models

type: search

query: StoreSettings|Theme|Page|Navigation|TaxSettings|Shipping

aggregated_output:
app/Policies/PagePolicy.php
app/Policies/ThemePolicy.php
app/Services/ShippingCalculator.php
app/Services/NavigationService.php
database/seeders/ThemeFileSeeder.php
database/seeders/ThemeSeeder.php
database/seeders/NavigationMenuSeeder.php
database/seeders/ShippingRateSeeder.php
database/seeders/TaxSettingsSeeder.php
database/seeders/ShippingZoneSeeder.php
database/seeders/NavigationItemSeeder.php
database/seeders/PageSeeder.php
database/seeders/StoreSettingsSeeder.php
database/seeders/ThemeSettingsSeeder.php
database/factories/PageFactory.php
database/factories/NavigationItemFactory.php
database/factories/ThemeSettingsFactory.php
database/factories/ThemeFactory.php
database/factories/StoreSettingsFactory.php
database/factories/TaxSettingsFactory.php
database/factories/ThemeFileFactory.php
app/Models/Theme.php
database/factories/NavigationMenuFactory.php
database/factories/ShippingRateFactory.php
app/Models/NavigationMenu.php
app/Models/NavigationItem.php
database/factories/ShippingZoneFactory.php
app/Models/ThemeFile.php
app/Models/ShippingZone.php
app/Models/TaxSettings.php
app/Models/ThemeSettings.php
app/Models/StoreSettings.php
app/Models/ShippingRate.php
app/Models/Page.php
app/Services/ThemeSettingsService.php


output:
Chunk ID: 96a0e5
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 310
Output:
app/Policies/PagePolicy.php
app/Policies/ThemePolicy.php
app/Services/ShippingCalculator.php
app/Services/NavigationService.php
database/seeders/ThemeFileSeeder.php
database/seeders/ThemeSeeder.php
database/seeders/NavigationMenuSeeder.php
database/seeders/ShippingRateSeeder.php
database/seeders/TaxSettingsSeeder.php
database/seeders/ShippingZoneSeeder.php
database/seeders/NavigationItemSeeder.php
database/seeders/PageSeeder.php
database/seeders/StoreSettingsSeeder.php
database/seeders/ThemeSettingsSeeder.php
database/factories/PageFactory.php
database/factories/NavigationItemFactory.php
database/factories/ThemeSettingsFactory.php
database/factories/ThemeFactory.php
database/factories/StoreSettingsFactory.php
database/factories/TaxSettingsFactory.php
database/factories/ThemeFileFactory.php
app/Models/Theme.php
database/factories/NavigationMenuFactory.php
database/factories/ShippingRateFactory.php
app/Models/NavigationMenu.php
app/Models/NavigationItem.php
database/factories/ShippingZoneFactory.php
app/Models/ThemeFile.php
app/Models/ShippingZone.php
app/Models/TaxSettings.php
app/Models/ThemeSettings.php
app/Models/StoreSettings.php
app/Models/ShippingRate.php
app/Models/Page.php
app/Services/ThemeSettingsService.php


cmd: sed -n '1905,1970p' specs/03-ADMIN-UI.md


cmd: sed -n '1,220p' app/Policies/PagePolicy.php app/Policies/ThemePolicy.php app/Models/Page.php app/Models/NavigationMenu.php app/Models/NavigationItem.php


cmd: sed -n '1,220p' app/Models/Theme.php app/Models/ThemeSettings.php app/Models/StoreSettings.php app/Models/TaxSettings.php app/Models/ShippingZone.php app/Models/ShippingRate.php


command: sed -n '1905,1970p' specs/03-ADMIN-UI.md

aggregated_output:
| Navigation                                                        |
+==================================================================+
|                                                                   |
| MENUS                                                             |
| +----------------------------+  +----------------------------+   |
| | Main Menu        [Edit]    |  | Footer Menu       [Edit]   |   |
| +----------------------------+  +----------------------------+   |
|                                                                   |
| MENU EDITOR (shown when a menu is selected)                      |
| +--------------------------------------------------------------+ |
| | Main Menu                                    [+ Add item]    | |
| |                                                               | |
| | [drag] Home               link: /            [edit] [trash]  | |
| | [drag] Products           collection: All    [edit] [trash]  | |
| | [drag] About Us           page: About        [edit] [trash]  | |
| | [drag] Contact            link: /contact     [edit] [trash]  | |
| |                                                               | |
| |                                            [Save menu]       | |
| +--------------------------------------------------------------+ |
+==================================================================+
```

**Menu list:** Each menu displayed as a card with title and "Edit" button.

**Menu editor (shown when a menu is selected):**
- Menu title as heading
- "Add item" button (ghost, plus icon) in the header
- Sortable list of menu items, each row containing:
  - Drag handle (bars icon)
  - Item label (bold text)
  - Item type and target (small, muted text: e.g., "link: /about" or "collection: Summer Sale")
  - Edit button (pencil icon) and Delete button (trash icon)
- "Save menu" primary button at the bottom

#### Menu Item Form Modal

| Property | Value |
|----------|-------|
| Modal name | `item-form` |
| Max width | md |
| Heading | "Edit menu item" or "Add menu item" (dynamic) |

**Form fields:**
- Label input (placeholder: "About Us")
- Type select: Custom link, Page, Collection, Product (live-bound)
- Conditional fields based on type:
  - **Custom link:** URL input (placeholder: "https://...")
  - **Page/Collection/Product:** Resource select dropdown populated with available resources of that type
- Cancel button (ghost) and "Save item" button (primary)

---

## 15. Apps Page

**Route:** `GET /admin/apps`

**Livewire component:** `App\Livewire\Admin\Apps\Index`

**Blade view:** `resources/views/livewire/admin/apps/index.blade.php`

### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `installedApps` | `Collection` | Apps installed for this store |


command: sed -n '1,220p' app/Models/Theme.php app/Models/ThemeSettings.php app/Models/StoreSettings.php app/Models/TaxSettings.php app/Models/ShippingZone.php app/Models/ShippingRate.php

aggregated_output:
<?php

namespace App\Models;

use App\Enums\ThemeStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

class Theme extends Model
{
    /** @use HasFactory<\Database\Factories\ThemeFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'name',
        'version',
        'status',
        'published_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'draft',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<ThemeFile, $this>
     */
    public function files(): HasMany
    {
        return $this->hasMany(ThemeFile::class)->orderBy('path');
    }

    /**
     * @return HasOne<ThemeSettings, $this>
     */
    public function settings(): HasOne
    {
        return $this->hasOne(ThemeSettings::class);
    }

    public function isPublished(): bool
    {
        return $this->status === ThemeStatus::Published && $this->published_at !== null;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => ThemeStatus::class,
            'published_at' => 'datetime',
        ];
    }
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class ThemeSettings extends Model
{
    /** @use HasFactory<\Database\Factories\ThemeSettingsFactory> */
    use HasFactory;

    public $incrementing = false;

    public const CREATED_AT = null;

    protected $primaryKey = 'theme_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'theme_id',
        'settings_json',
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('theme', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<Theme, $this>
     */
    public function theme(): BelongsTo
    {
        return $this->belongsTo(Theme::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'settings_json' => 'array',
        ];
    }
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class StoreSettings extends Model
{
    /** @use HasFactory<\Database\Factories\StoreSettingsFactory> */
    use HasFactory;

    public $incrementing = false;

    public const CREATED_AT = null;

    protected $primaryKey = 'store_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'settings_json',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'settings_json' => 'array',
        ];
    }
}
<?php

namespace App\Models;

use App\Enums\TaxMode;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class TaxSettings extends Model
{
    /** @use HasFactory<\Database\Factories\TaxSettingsFactory> */
    use BelongsToStore, HasFactory;

    public $incrementing = false;

    public $timestamps = false;

    protected $primaryKey = 'store_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'mode',
        'provider',
        'prices_include_tax',
        'config_json',
    ];

    /**


output:
Chunk ID: bfe70c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 700
Output:
| Navigation                                                        |
+==================================================================+
|                                                                   |
| MENUS                                                             |
| +----------------------------+  +----------------------------+   |
| | Main Menu        [Edit]    |  | Footer Menu       [Edit]   |   |
| +----------------------------+  +----------------------------+   |
|                                                                   |
| MENU EDITOR (shown when a menu is selected)                      |
| +--------------------------------------------------------------+ |
| | Main Menu                                    [+ Add item]    | |
| |                                                               | |
| | [drag] Home               link: /            [edit] [trash]  | |
| | [drag] Products           collection: All    [edit] [trash]  | |
| | [drag] About Us           page: About        [edit] [trash]  | |
| | [drag] Contact            link: /contact     [edit] [trash]  | |
| |                                                               | |
| |                                            [Save menu]       | |
| +--------------------------------------------------------------+ |
+==================================================================+
```

**Menu list:** Each menu displayed as a card with title and "Edit" button.

**Menu editor (shown when a menu is selected):**
- Menu title as heading
- "Add item" button (ghost, plus icon) in the header
- Sortable list of menu items, each row containing:
  - Drag handle (bars icon)
  - Item label (bold text)
  - Item type and target (small, muted text: e.g., "link: /about" or "collection: Summer Sale")
  - Edit button (pencil icon) and Delete button (trash icon)
- "Save menu" primary button at the bottom

#### Menu Item Form Modal

| Property | Value |
|----------|-------|
| Modal name | `item-form` |
| Max width | md |
| Heading | "Edit menu item" or "Add menu item" (dynamic) |

**Form fields:**
- Label input (placeholder: "About Us")
- Type select: Custom link, Page, Collection, Product (live-bound)
- Conditional fields based on type:
  - **Custom link:** URL input (placeholder: "https://...")
  - **Page/Collection/Product:** Resource select dropdown populated with available resources of that type
- Cancel button (ghost) and "Save item" button (primary)

---

## 15. Apps Page

**Route:** `GET /admin/apps`

**Livewire component:** `App\Livewire\Admin\Apps\Index`

**Blade view:** `resources/views/livewire/admin/apps/index.blade.php`

### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `installedApps` | `Collection` | Apps installed for this store |


command: sed -n '1,220p' app/Policies/PagePolicy.php app/Policies/ThemePolicy.php app/Models/Page.php app/Models/NavigationMenu.php app/Models/NavigationItem.php

aggregated_output:
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class PagePolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isOwnerAdminOrStaff($user);
    }

    public function view(User $user, object $page): bool
    {
        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($page));
    }

    public function create(User $user): bool
    {
        return $this->isOwnerAdminOrStaff($user);
    }

    public function update(User $user, object $page): bool
    {
        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($page));
    }

    public function delete(User $user, object $page): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($page));
    }
}
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class ThemePolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isOwnerOrAdmin($user);
    }

    public function view(User $user, object $theme): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($theme));
    }

    public function create(User $user): bool
    {
        return $this->isOwnerOrAdmin($user);
    }

    public function update(User $user, object $theme): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($theme));
    }

    public function publish(User $user, object $theme): bool
    {
        return $this->update($user, $theme);
    }

    public function delete(User $user, object $theme): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($theme));
    }
}
<?php

namespace App\Models;

use App\Enums\PageStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Page extends Model
{
    /** @use HasFactory<\Database\Factories\PageFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'title',
        'handle',
        'body_html',
        'status',
        'published_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'draft',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    public function isPublished(): bool
    {
        return $this->status === PageStatus::Published && $this->published_at !== null;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => PageStatus::class,
            'published_at' => 'datetime',
        ];
    }
}
<?php

namespace App\Models;

use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class NavigationMenu extends Model
{
    /** @use HasFactory<\Database\Factories\NavigationMenuFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'handle',
        'title',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<NavigationItem, $this>
     */
    public function items(): HasMany
    {
        return $this->hasMany(NavigationItem::class, 'menu_id')->orderBy('position');
    }
}
<?php

namespace App\Models;

use App\Enums\NavigationItemType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class NavigationItem extends Model
{
    /** @use HasFactory<\Database\Factories\NavigationItemFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'menu_id',
        'type',
        'label',
        'url',
        'resource_id',
        'position',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'link',
        'position' => 0,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');


output:
Chunk ID: 80136a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1204
Output:
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class PagePolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isOwnerAdminOrStaff($user);
    }

    public function view(User $user, object $page): bool
    {
        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($page));
    }

    public function create(User $user): bool
    {
        return $this->isOwnerAdminOrStaff($user);
    }

    public function update(User $user, object $page): bool
    {
        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($page));
    }

    public function delete(User $user, object $page): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($page));
    }
}
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class ThemePolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isOwnerOrAdmin($user);
    }

    public function view(User $user, object $theme): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($theme));
    }

    public function create(User $user): bool
    {
        return $this->isOwnerOrAdmin($user);
    }

    public function update(User $user, object $theme): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($theme));
    }

    public function publish(User $user, object $theme): bool
    {
        return $this->update($user, $theme);
    }

    public function delete(User $user, object $theme): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($theme));
    }
}
<?php

namespace App\Models;

use App\Enums\PageStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Page extends Model
{
    /** @use HasFactory<\Database\Factories\PageFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'title',
        'handle',
        'body_html',
        'status',
        'published_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'draft',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    public function isPublished(): bool
    {
        return $this->status === PageStatus::Published && $this->published_at !== null;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => PageStatus::class,
            'published_at' => 'datetime',
        ];
    }
}
<?php

namespace App\Models;

use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class NavigationMenu extends Model
{
    /** @use HasFactory<\Database\Factories\NavigationMenuFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'handle',
        'title',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<NavigationItem, $this>
     */
    public function items(): HasMany
    {
        return $this->hasMany(NavigationItem::class, 'menu_id')->orderBy('position');
    }
}
<?php

namespace App\Models;

use App\Enums\NavigationItemType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class NavigationItem extends Model
{
    /** @use HasFactory<\Database\Factories\NavigationItemFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'menu_id',
        'type',
        'label',
        'url',
        'resource_id',
        'position',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'link',
        'position' => 0,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');


output:
Chunk ID: 1b86b4
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1171
Output:
<?php

namespace App\Models;

use App\Enums\ThemeStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

class Theme extends Model
{
    /** @use HasFactory<\Database\Factories\ThemeFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'name',
        'version',
        'status',
        'published_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'draft',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<ThemeFile, $this>
     */
    public function files(): HasMany
    {
        return $this->hasMany(ThemeFile::class)->orderBy('path');
    }

    /**
     * @return HasOne<ThemeSettings, $this>
     */
    public function settings(): HasOne
    {
        return $this->hasOne(ThemeSettings::class);
    }

    public function isPublished(): bool
    {
        return $this->status === ThemeStatus::Published && $this->published_at !== null;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => ThemeStatus::class,
            'published_at' => 'datetime',
        ];
    }
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class ThemeSettings extends Model
{
    /** @use HasFactory<\Database\Factories\ThemeSettingsFactory> */
    use HasFactory;

    public $incrementing = false;

    public const CREATED_AT = null;

    protected $primaryKey = 'theme_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'theme_id',
        'settings_json',
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('theme', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<Theme, $this>
     */
    public function theme(): BelongsTo
    {
        return $this->belongsTo(Theme::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'settings_json' => 'array',
        ];
    }
}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class StoreSettings extends Model
{
    /** @use HasFactory<\Database\Factories\StoreSettingsFactory> */
    use HasFactory;

    public $incrementing = false;

    public const CREATED_AT = null;

    protected $primaryKey = 'store_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'settings_json',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'settings_json' => 'array',
        ];
    }
}
<?php

namespace App\Models;

use App\Enums\TaxMode;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class TaxSettings extends Model
{
    /** @use HasFactory<\Database\Factories\TaxSettingsFactory> */
    use BelongsToStore, HasFactory;

    public $incrementing = false;

    public $timestamps = false;

    protected $primaryKey = 'store_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'mode',
        'provider',
        'prices_include_tax',
        'config_json',
    ];

    /**


cmd: sed -n '220,520p' app/Models/NavigationItem.php app/Models/TaxSettings.php app/Models/ShippingZone.php app/Models/ShippingRate.php


cmd: rg --files app/Enums | xargs -n1 basename


cmd: sed -n '1,220p' app/Traits/ChecksStoreRole.php app/Providers/AuthServiceProvider.php bootstrap/app.php


command: sed -n '220,520p' app/Models/NavigationItem.php app/Models/TaxSettings.php app/Models/ShippingZone.php app/Models/ShippingRate.php

aggregated_output:
    /**
     * @var list<string>
     */
    protected $fillable = [
        'zone_id',
        'name',
        'type',
        'config_json',
        'is_active',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'flat',
        'config_json' => '{}',
        'is_active' => true,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('zone', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<ShippingZone, $this>
     */
    public function zone(): BelongsTo
    {
        return $this->belongsTo(ShippingZone::class, 'zone_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => ShippingRateType::class,
            'config_json' => 'array',
            'is_active' => 'bool',
        ];
    }
}


output:
Chunk ID: f2de99
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 344
Output:
    /**
     * @var list<string>
     */
    protected $fillable = [
        'zone_id',
        'name',
        'type',
        'config_json',
        'is_active',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'flat',
        'config_json' => '{}',
        'is_active' => true,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('zone', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<ShippingZone, $this>
     */
    public function zone(): BelongsTo
    {
        return $this->belongsTo(ShippingZone::class, 'zone_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => ShippingRateType::class,
            'config_json' => 'array',
            'is_active' => 'bool',
        ];
    }
}


command: sed -n '1,220p' app/Traits/ChecksStoreRole.php app/Providers/AuthServiceProvider.php bootstrap/app.php

aggregated_output:
sed: app/Providers/AuthServiceProvider.php: No such file or directory
<?php

namespace App\Traits;

use App\Enums\StoreUserRole;
use App\Models\Store;
use App\Models\User;

trait ChecksStoreRole
{
    public function getStoreRole(User $user, ?int $storeId = null): ?StoreUserRole
    {
        $storeId ??= $this->currentStoreId();

        if (! $storeId) {
            return null;
        }

        return $user->roleForStoreId($storeId);
    }

    /**
     * @param  array<int, StoreUserRole>  $roles
     */
    public function hasRole(User $user, ?int $storeId, array $roles): bool
    {
        $role = $this->getStoreRole($user, $storeId);

        return $role !== null && in_array($role, $roles, true);
    }

    public function isOwnerOnly(User $user, ?int $storeId = null): bool
    {
        return $this->hasRole($user, $storeId, [StoreUserRole::Owner]);
    }

    public function isOwnerOrAdmin(User $user, ?int $storeId = null): bool
    {
        return $this->hasRole($user, $storeId, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }

    public function isOwnerAdminOrStaff(User $user, ?int $storeId = null): bool
    {
        return $this->hasRole($user, $storeId, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function isAnyRole(User $user, ?int $storeId = null): bool
    {
        return $this->getStoreRole($user, $storeId) !== null;
    }

    protected function currentStoreId(): ?int
    {
        if (! app()->bound('current_store')) {
            return null;
        }

        $store = app('current_store');

        return $store instanceof Store ? $store->getKey() : null;
    }

    protected function storeIdForModel(object $model): ?int
    {
        if (method_exists($model, 'getAttribute')) {
            $storeId = $model->getAttribute('store_id');

            return is_numeric($storeId) ? (int) $storeId : null;
        }

        if (property_exists($model, 'store_id')) {
            return is_numeric($model->store_id) ? (int) $model->store_id : null;
        }

        return null;
    }
}
<?php

use App\Http\Middleware\CheckStoreRole;
use App\Http\Middleware\ResolveStore;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->alias([
            'role.check' => CheckStoreRole::class,
            'store.resolve' => ResolveStore::class,
        ]);

        $middleware->appendToGroup('storefront', [
            ResolveStore::class,
        ]);

        $middleware->appendToGroup('admin', [
            ResolveStore::class,
            CheckStoreRole::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();

status: failed


command: rg --files app/Enums | xargs -n1 basename

cmd: rg --files app/Enums

path: Enums

aggregated_output:
PaymentStatus.php
ThemeStatus.php
StoreStatus.php
FulfillmentStatus.php
ProductStatus.php
StoreDomainType.php
CheckoutStatus.php
FinancialStatus.php
ShippingRateType.php
MediaStatus.php
CollectionStatus.php
DiscountValueType.php
DiscountStatus.php
NavigationItemType.php
VariantStatus.php
DiscountType.php
PageStatus.php
OrderStatus.php
MediaType.php
FulfillmentShipmentStatus.php
TaxMode.php
CollectionType.php
CartStatus.php
RefundStatus.php
InventoryPolicy.php
StoreUserRole.php
PaymentMethod.php


output:
Chunk ID: fb1ffa
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 125
Output:
PaymentStatus.php
ThemeStatus.php
StoreStatus.php
FulfillmentStatus.php
ProductStatus.php
StoreDomainType.php
CheckoutStatus.php
FinancialStatus.php
ShippingRateType.php
MediaStatus.php
CollectionStatus.php
DiscountValueType.php
DiscountStatus.php
NavigationItemType.php
VariantStatus.php
DiscountType.php
PageStatus.php
OrderStatus.php
MediaType.php
FulfillmentShipmentStatus.php
TaxMode.php
CollectionType.php
CartStatus.php
RefundStatus.php
InventoryPolicy.php
StoreUserRole.php
PaymentMethod.php


output:
Chunk ID: 56e151
Wall time: 0.0000 seconds
Process exited with code 1
Original token count: 773
Output:
sed: app/Providers/AuthServiceProvider.php: No such file or directory
<?php

namespace App\Traits;

use App\Enums\StoreUserRole;
use App\Models\Store;
use App\Models\User;

trait ChecksStoreRole
{
    public function getStoreRole(User $user, ?int $storeId = null): ?StoreUserRole
    {
        $storeId ??= $this->currentStoreId();

        if (! $storeId) {
            return null;
        }

        return $user->roleForStoreId($storeId);
    }

    /**
     * @param  array<int, StoreUserRole>  $roles
     */
    public function hasRole(User $user, ?int $storeId, array $roles): bool
    {
        $role = $this->getStoreRole($user, $storeId);

        return $role !== null && in_array($role, $roles, true);
    }

    public function isOwnerOnly(User $user, ?int $storeId = null): bool
    {
        return $this->hasRole($user, $storeId, [StoreUserRole::Owner]);
    }

    public function isOwnerOrAdmin(User $user, ?int $storeId = null): bool
    {
        return $this->hasRole($user, $storeId, [StoreUserRole::Owner, StoreUserRole::Admin]);
    }

    public function isOwnerAdminOrStaff(User $user, ?int $storeId = null): bool
    {
        return $this->hasRole($user, $storeId, [StoreUserRole::Owner, StoreUserRole::Admin, StoreUserRole::Staff]);
    }

    public function isAnyRole(User $user, ?int $storeId = null): bool
    {
        return $this->getStoreRole($user, $storeId) !== null;
    }

    protected function currentStoreId(): ?int
    {
        if (! app()->bound('current_store')) {
            return null;
        }

        $store = app('current_store');

        return $store instanceof Store ? $store->getKey() : null;
    }

    protected function storeIdForModel(object $model): ?int
    {
        if (method_exists($model, 'getAttribute')) {
            $storeId = $model->getAttribute('store_id');

            return is_numeric($storeId) ? (int) $storeId : null;
        }

        if (property_exists($model, 'store_id')) {
            return is_numeric($model->store_id) ? (int) $model->store_id : null;
        }

        return null;
    }
}
<?php

use App\Http\Middleware\CheckStoreRole;
use App\Http\Middleware\ResolveStore;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->alias([
            'role.check' => CheckStoreRole::class,
            'store.resolve' => ResolveStore::class,
        ]);

        $middleware->appendToGroup('storefront', [
            ResolveStore::class,
        ]);

        $middleware->appendToGroup('admin', [
            ResolveStore::class,
            CheckStoreRole::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();


cmd: sed -n '1,220p' app/Models/NavigationItem.php


cmd: sed -n '1,220p' app/Models/TaxSettings.php


cmd: sed -n '1,220p' app/Models/ShippingZone.php


cmd: sed -n '1,220p' app/Models/ShippingRate.php


command: sed -n '1,220p' app/Models/TaxSettings.php

aggregated_output:
<?php

namespace App\Models;

use App\Enums\TaxMode;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class TaxSettings extends Model
{
    /** @use HasFactory<\Database\Factories\TaxSettingsFactory> */
    use BelongsToStore, HasFactory;

    public $incrementing = false;

    public $timestamps = false;

    protected $primaryKey = 'store_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'mode',
        'provider',
        'prices_include_tax',
        'config_json',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'mode' => 'manual',
        'provider' => 'none',
        'prices_include_tax' => false,
        'config_json' => '{}',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'mode' => TaxMode::class,
            'prices_include_tax' => 'bool',
            'config_json' => 'array',
        ];
    }
}


command: sed -n '1,220p' app/Models/NavigationItem.php

aggregated_output:
<?php

namespace App\Models;

use App\Enums\NavigationItemType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class NavigationItem extends Model
{
    /** @use HasFactory<\Database\Factories\NavigationItemFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'menu_id',
        'type',
        'label',
        'url',
        'resource_id',
        'position',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'link',
        'position' => 0,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('menu', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<NavigationMenu, $this>
     */
    public function menu(): BelongsTo
    {
        return $this->belongsTo(NavigationMenu::class, 'menu_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => NavigationItemType::class,
            'resource_id' => 'integer',
            'position' => 'integer',
        ];
    }
}


output:
Chunk ID: 9e7a6a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 446
Output:
<?php

namespace App\Models;

use App\Enums\NavigationItemType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class NavigationItem extends Model
{
    /** @use HasFactory<\Database\Factories\NavigationItemFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'menu_id',
        'type',
        'label',
        'url',
        'resource_id',
        'position',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'link',
        'position' => 0,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('menu', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<NavigationMenu, $this>
     */
    public function menu(): BelongsTo
    {
        return $this->belongsTo(NavigationMenu::class, 'menu_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => NavigationItemType::class,
            'resource_id' => 'integer',
            'position' => 'integer',
        ];
    }
}


output:
Chunk ID: 4fa8fe
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 325
Output:
<?php

namespace App\Models;

use App\Enums\TaxMode;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class TaxSettings extends Model
{
    /** @use HasFactory<\Database\Factories\TaxSettingsFactory> */
    use BelongsToStore, HasFactory;

    public $incrementing = false;

    public $timestamps = false;

    protected $primaryKey = 'store_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'mode',
        'provider',
        'prices_include_tax',
        'config_json',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'mode' => 'manual',
        'provider' => 'none',
        'prices_include_tax' => false,
        'config_json' => '{}',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'mode' => TaxMode::class,
            'prices_include_tax' => 'bool',
            'config_json' => 'array',
        ];
    }
}


command: sed -n '1,220p' app/Models/ShippingZone.php

aggregated_output:
<?php

namespace App\Models;

use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class ShippingZone extends Model
{
    /** @use HasFactory<\Database\Factories\ShippingZoneFactory> */
    use BelongsToStore, HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'name',
        'countries_json',
        'regions_json',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'countries_json' => '[]',
        'regions_json' => '[]',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<ShippingRate, $this>
     */
    public function rates(): HasMany
    {
        return $this->hasMany(ShippingRate::class, 'zone_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'countries_json' => 'array',
            'regions_json' => 'array',
        ];
    }
}


output:
Chunk ID: e847f3
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 326
Output:
<?php

namespace App\Models;

use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class ShippingZone extends Model
{
    /** @use HasFactory<\Database\Factories\ShippingZoneFactory> */
    use BelongsToStore, HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'name',
        'countries_json',
        'regions_json',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'countries_json' => '[]',
        'regions_json' => '[]',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<ShippingRate, $this>
     */
    public function rates(): HasMany
    {
        return $this->hasMany(ShippingRate::class, 'zone_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'countries_json' => 'array',
            'regions_json' => 'array',
        ];
    }
}


command: sed -n '1,220p' app/Models/ShippingRate.php

aggregated_output:
<?php

namespace App\Models;

use App\Enums\ShippingRateType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class ShippingRate extends Model
{
    /** @use HasFactory<\Database\Factories\ShippingRateFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'zone_id',
        'name',
        'type',
        'config_json',
        'is_active',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'flat',
        'config_json' => '{}',
        'is_active' => true,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('zone', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<ShippingZone, $this>
     */
    public function zone(): BelongsTo
    {
        return $this->belongsTo(ShippingZone::class, 'zone_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => ShippingRateType::class,
            'config_json' => 'array',
            'is_active' => 'bool',
        ];
    }
}


output:
Chunk ID: faedb0
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 447
Output:
<?php

namespace App\Models;

use App\Enums\ShippingRateType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class ShippingRate extends Model
{
    /** @use HasFactory<\Database\Factories\ShippingRateFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'zone_id',
        'name',
        'type',
        'config_json',
        'is_active',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'type' => 'flat',
        'config_json' => '{}',
        'is_active' => true,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('zone', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<ShippingZone, $this>
     */
    public function zone(): BelongsTo
    {
        return $this->belongsTo(ShippingZone::class, 'zone_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'type' => ShippingRateType::class,
            'config_json' => 'array',
            'is_active' => 'bool',
        ];
    }
}


cmd: rg -n "Policy|Gate|can\(" app/Policies app/Providers app/Livewire/Admin tests/Feature/Admin | head -200


cmd: rg -n "StoreDomain|StoreSettings|TaxSettings|ShippingZone|NavigationMenu" app/Policies app/Models app/Traits tests/Feature


cmd: sed -n '1,140p' app/Policies/ProductPolicy.php app/Policies/CollectionPolicy.php app/Policies/DiscountPolicy.php app/Policies/OrderPolicy.php


command: rg -n "Policy|Gate|can\(" app/Policies app/Providers app/Livewire/Admin tests/Feature/Admin | head -200

type: search

cmd: rg -n "Policy|Gate|can\\(" app/Policies app/Providers app/Livewire/Admin tests/Feature/Admin

query: Policy|Gate|can\(

path: Policies

aggregated_output:
app/Policies/OrderPolicy.php:8:class OrderPolicy
app/Policies/StorePolicy.php:9:class StorePolicy
app/Policies/RefundPolicy.php:8:class RefundPolicy
app/Policies/ThemePolicy.php:8:class ThemePolicy
app/Policies/FulfillmentPolicy.php:8:class FulfillmentPolicy
app/Policies/CollectionPolicy.php:8:class CollectionPolicy
app/Policies/PagePolicy.php:8:class PagePolicy
app/Policies/DiscountPolicy.php:8:class DiscountPolicy
app/Policies/CustomerPolicy.php:8:class CustomerPolicy
app/Policies/ProductPolicy.php:8:class ProductPolicy


output:
Chunk ID: 1e79ec
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 132
Output:
app/Policies/OrderPolicy.php:8:class OrderPolicy
app/Policies/StorePolicy.php:9:class StorePolicy
app/Policies/RefundPolicy.php:8:class RefundPolicy
app/Policies/ThemePolicy.php:8:class ThemePolicy
app/Policies/FulfillmentPolicy.php:8:class FulfillmentPolicy
app/Policies/CollectionPolicy.php:8:class CollectionPolicy
app/Policies/PagePolicy.php:8:class PagePolicy
app/Policies/DiscountPolicy.php:8:class DiscountPolicy
app/Policies/CustomerPolicy.php:8:class CustomerPolicy
app/Policies/ProductPolicy.php:8:class ProductPolicy


command: sed -n '1,140p' app/Policies/ProductPolicy.php app/Policies/CollectionPolicy.php app/Policies/DiscountPolicy.php app/Policies/OrderPolicy.php

aggregated_output:
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class ProductPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isAnyRole($user);
    }

    public function view(User $user, object $product): bool
    {
        return $this->isAnyRole($user, $this->storeIdForModel($product));
    }

    public function create(User $user): bool
    {
        return $this->isOwnerAdminOrStaff($user);
    }

    public function update(User $user, object $product): bool
    {
        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($product));
    }

    public function delete(User $user, object $product): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($product));
    }

    public function archive(User $user, object $product): bool
    {
        return $this->delete($user, $product);
    }

    public function restore(User $user, object $product): bool
    {
        return $this->delete($user, $product);
    }
}
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class CollectionPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isAnyRole($user);
    }

    public function view(User $user, object $collection): bool
    {
        return $this->isAnyRole($user, $this->storeIdForModel($collection));
    }

    public function create(User $user): bool
    {
        return $this->isOwnerAdminOrStaff($user);
    }

    public function update(User $user, object $collection): bool
    {
        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($collection));
    }

    public function delete(User $user, object $collection): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($collection));
    }
}
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class DiscountPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isAnyRole($user);
    }

    public function view(User $user, object $discount): bool
    {
        return $this->isAnyRole($user, $this->storeIdForModel($discount));
    }

    public function create(User $user): bool
    {
        return $this->isOwnerAdminOrStaff($user);
    }

    public function update(User $user, object $discount): bool
    {
        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($discount));
    }

    public function delete(User $user, object $discount): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($discount));
    }
}
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class OrderPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isAnyRole($user);
    }

    public function view(User $user, object $order): bool
    {
        return $this->isAnyRole($user, $this->storeIdForModel($order));
    }

    public function update(User $user, object $order): bool


command: rg -n "StoreDomain|StoreSettings|TaxSettings|ShippingZone|NavigationMenu" app/Policies app/Models app/Traits tests/Feature

type: search

cmd: rg -n 'StoreDomain|StoreSettings|TaxSettings|ShippingZone|NavigationMenu' app/Policies app/Models app/Traits tests/Feature

query: StoreDomain|StoreSettings|TaxSettings|ShippingZone|NavigationMenu

path: Policies

aggregated_output:
app/Models/StoreDomain.php:5:use App\Enums\StoreDomainType;
app/Models/StoreDomain.php:10:class StoreDomain extends Model
app/Models/StoreDomain.php:12:    /** @use HasFactory<\Database\Factories\StoreDomainFactory> */
app/Models/StoreDomain.php:42:            'type' => StoreDomainType::class,
tests/Feature/Orders/OrderServiceTest.php:19:use App\Models\ShippingZone;
tests/Feature/Orders/OrderServiceTest.php:21:use App\Models\StoreSettings;
tests/Feature/Orders/OrderServiceTest.php:22:use App\Models\TaxSettings;
tests/Feature/Orders/OrderServiceTest.php:36:    StoreSettings::query()->create([
tests/Feature/Orders/OrderServiceTest.php:44:    TaxSettings::withoutGlobalScopes()->create([
tests/Feature/Orders/OrderServiceTest.php:76:    $zone = ShippingZone::withoutGlobalScopes()
tests/Feature/Orders/OrderServiceTest.php:81:    if (! $zone instanceof ShippingZone) {
tests/Feature/Orders/OrderServiceTest.php:82:        $zone = ShippingZone::withoutGlobalScopes()->create([
app/Models/Store.php:40:     * @return HasMany<StoreDomain, $this>
app/Models/Store.php:44:        return $this->hasMany(StoreDomain::class);
app/Models/Store.php:58:     * @return HasOne<StoreSettings, $this>
app/Models/Store.php:62:        return $this->hasOne(StoreSettings::class);
app/Models/Store.php:82:     * @return HasMany<NavigationMenu, $this>
app/Models/Store.php:86:        return $this->hasMany(NavigationMenu::class);
app/Models/Store.php:106:     * @return HasMany<ShippingZone, $this>
app/Models/Store.php:110:        return $this->hasMany(ShippingZone::class);
app/Models/Store.php:114:     * @return HasOne<TaxSettings, $this>
app/Models/Store.php:118:        return $this->hasOne(TaxSettings::class);
tests/Feature/Orders/BankTransferOrderTest.php:19:use App\Models\StoreSettings;
tests/Feature/Orders/BankTransferOrderTest.php:34:    StoreSettings::query()->create([
app/Models/StoreSettings.php:9:class StoreSettings extends Model
app/Models/StoreSettings.php:11:    /** @use HasFactory<\Database\Factories\StoreSettingsFactory> */
app/Models/ShippingRate.php:60:     * @return BelongsTo<ShippingZone, $this>
app/Models/ShippingRate.php:64:        return $this->belongsTo(ShippingZone::class, 'zone_id');
app/Models/NavigationMenu.php:11:class NavigationMenu extends Model
app/Models/NavigationMenu.php:13:    /** @use HasFactory<\Database\Factories\NavigationMenuFactory> */
tests/Feature/Checkout/PricingServicesTest.php:11:use App\Models\ShippingZone;
tests/Feature/Checkout/PricingServicesTest.php:13:use App\Models\TaxSettings;
tests/Feature/Checkout/PricingServicesTest.php:27:    TaxSettings::withoutGlobalScopes()->create([
tests/Feature/Checkout/PricingServicesTest.php:89:    $zone = ShippingZone::withoutGlobalScopes()->create([
tests/Feature/Checkout/PricingServicesTest.php:117:    $zone = ShippingZone::withoutGlobalScopes()->create([
tests/Feature/Checkout/PricingServicesTest.php:208:    $zone = ShippingZone::withoutGlobalScopes()->create([
app/Models/ShippingZone.php:11:class ShippingZone extends Model
app/Models/ShippingZone.php:13:    /** @use HasFactory<\Database\Factories\ShippingZoneFactory> */
tests/Feature/Checkout/CheckoutServiceTest.php:12:use App\Models\ShippingZone;
tests/Feature/Checkout/CheckoutServiceTest.php:14:use App\Models\TaxSettings;
tests/Feature/Checkout/CheckoutServiceTest.php:26:    TaxSettings::withoutGlobalScopes()->create([
tests/Feature/Checkout/CheckoutServiceTest.php:78:    $zone = ShippingZone::withoutGlobalScopes()->create([
tests/Feature/Checkout/CheckoutServiceTest.php:119:    ShippingZone::withoutGlobalScopes()->create([
tests/Feature/Checkout/CheckoutServiceTest.php:151:    $zone = ShippingZone::withoutGlobalScopes()->create([
tests/Feature/Checkout/CheckoutServiceTest.php:181:    $zone = ShippingZone::withoutGlobalScopes()->create([
app/Models/TaxSettings.php:11:class TaxSettings extends Model
app/Models/TaxSettings.php:13:    /** @use HasFactory<\Database\Factories\TaxSettingsFactory> */
app/Models/NavigationItem.php:60:     * @return BelongsTo<NavigationMenu, $this>
app/Models/NavigationItem.php:64:        return $this->belongsTo(NavigationMenu::class, 'menu_id');
tests/Feature/Foundation/TenancyTest.php:5:use App\Models\StoreDomain;
tests/Feature/Foundation/TenancyTest.php:22:        ->and(StoreDomain::query()->where('hostname', 'shop.test')->exists())->toBeTrue()
tests/Feature/Foundation/TenancyTest.php:40:    StoreDomain::factory()->create([
tests/Feature/Storefront/ThemeDataTest.php:5:use App\Models\NavigationMenu;
tests/Feature/Storefront/ThemeDataTest.php:37:        ->and(NavigationMenu::withoutGlobalScopes()->count())->toBe(4)
tests/Feature/Storefront/ThemeDataTest.php:49:    NavigationMenu::factory()->create(['store_id' => $firstStore->getKey(), 'handle' => 'first-menu']);
tests/Feature/Storefront/ThemeDataTest.php:50:    NavigationMenu::factory()->create(['store_id' => $secondStore->getKey(), 'handle' => 'second-menu']);
tests/Feature/Storefront/ThemeDataTest.php:56:        ->and(NavigationMenu::query()->pluck('handle')->all())->toBe(['first-menu']);
tests/Feature/Storefront/ThemeDataTest.php:64:    $firstMenu = NavigationMenu::factory()->create(['store_id' => $firstStore->getKey()]);
tests/Feature/Storefront/ThemeDataTest.php:65:    $secondMenu = NavigationMenu::factory()->create(['store_id' => $secondStore->getKey()]);
tests/Feature/Storefront/ThemeDataTest.php:96:    $menu = NavigationMenu::withoutGlobalScopes()


output:
Chunk ID: 32bdca
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1359
Output:
app/Models/StoreDomain.php:5:use App\Enums\StoreDomainType;
app/Models/StoreDomain.php:10:class StoreDomain extends Model
app/Models/StoreDomain.php:12:    /** @use HasFactory<\Database\Factories\StoreDomainFactory> */
app/Models/StoreDomain.php:42:            'type' => StoreDomainType::class,
tests/Feature/Orders/OrderServiceTest.php:19:use App\Models\ShippingZone;
tests/Feature/Orders/OrderServiceTest.php:21:use App\Models\StoreSettings;
tests/Feature/Orders/OrderServiceTest.php:22:use App\Models\TaxSettings;
tests/Feature/Orders/OrderServiceTest.php:36:    StoreSettings::query()->create([
tests/Feature/Orders/OrderServiceTest.php:44:    TaxSettings::withoutGlobalScopes()->create([
tests/Feature/Orders/OrderServiceTest.php:76:    $zone = ShippingZone::withoutGlobalScopes()
tests/Feature/Orders/OrderServiceTest.php:81:    if (! $zone instanceof ShippingZone) {
tests/Feature/Orders/OrderServiceTest.php:82:        $zone = ShippingZone::withoutGlobalScopes()->create([
app/Models/Store.php:40:     * @return HasMany<StoreDomain, $this>
app/Models/Store.php:44:        return $this->hasMany(StoreDomain::class);
app/Models/Store.php:58:     * @return HasOne<StoreSettings, $this>
app/Models/Store.php:62:        return $this->hasOne(StoreSettings::class);
app/Models/Store.php:82:     * @return HasMany<NavigationMenu, $this>
app/Models/Store.php:86:        return $this->hasMany(NavigationMenu::class);
app/Models/Store.php:106:     * @return HasMany<ShippingZone, $this>
app/Models/Store.php:110:        return $this->hasMany(ShippingZone::class);
app/Models/Store.php:114:     * @return HasOne<TaxSettings, $this>
app/Models/Store.php:118:        return $this->hasOne(TaxSettings::class);
tests/Feature/Orders/BankTransferOrderTest.php:19:use App\Models\StoreSettings;
tests/Feature/Orders/BankTransferOrderTest.php:34:    StoreSettings::query()->create([
app/Models/StoreSettings.php:9:class StoreSettings extends Model
app/Models/StoreSettings.php:11:    /** @use HasFactory<\Database\Factories\StoreSettingsFactory> */
app/Models/ShippingRate.php:60:     * @return BelongsTo<ShippingZone, $this>
app/Models/ShippingRate.php:64:        return $this->belongsTo(ShippingZone::class, 'zone_id');
app/Models/NavigationMenu.php:11:class NavigationMenu extends Model
app/Models/NavigationMenu.php:13:    /** @use HasFactory<\Database\Factories\NavigationMenuFactory> */
tests/Feature/Checkout/PricingServicesTest.php:11:use App\Models\ShippingZone;
tests/Feature/Checkout/PricingServicesTest.php:13:use App\Models\TaxSettings;
tests/Feature/Checkout/PricingServicesTest.php:27:    TaxSettings::withoutGlobalScopes()->create([
tests/Feature/Checkout/PricingServicesTest.php:89:    $zone = ShippingZone::withoutGlobalScopes()->create([
tests/Feature/Checkout/PricingServicesTest.php:117:    $zone = ShippingZone::withoutGlobalScopes()->create([
tests/Feature/Checkout/PricingServicesTest.php:208:    $zone = ShippingZone::withoutGlobalScopes()->create([
app/Models/ShippingZone.php:11:class ShippingZone extends Model
app/Models/ShippingZone.php:13:    /** @use HasFactory<\Database\Factories\ShippingZoneFactory> */
tests/Feature/Checkout/CheckoutServiceTest.php:12:use App\Models\ShippingZone;
tests/Feature/Checkout/CheckoutServiceTest.php:14:use App\Models\TaxSettings;
tests/Feature/Checkout/CheckoutServiceTest.php:26:    TaxSettings::withoutGlobalScopes()->create([
tests/Feature/Checkout/CheckoutServiceTest.php:78:    $zone = ShippingZone::withoutGlobalScopes()->create([
tests/Feature/Checkout/CheckoutServiceTest.php:119:    ShippingZone::withoutGlobalScopes()->create([
tests/Feature/Checkout/CheckoutServiceTest.php:151:    $zone = ShippingZone::withoutGlobalScopes()->create([
tests/Feature/Checkout/CheckoutServiceTest.php:181:    $zone = ShippingZone::withoutGlobalScopes()->create([
app/Models/TaxSettings.php:11:class TaxSettings extends Model
app/Models/TaxSettings.php:13:    /** @use HasFactory<\Database\Factories\TaxSettingsFactory> */
app/Models/NavigationItem.php:60:     * @return BelongsTo<NavigationMenu, $this>
app/Models/NavigationItem.php:64:        return $this->belongsTo(NavigationMenu::class, 'menu_id');
tests/Feature/Foundation/TenancyTest.php:5:use App\Models\StoreDomain;
tests/Feature/Foundation/TenancyTest.php:22:        ->and(StoreDomain::query()->where('hostname', 'shop.test')->exists())->toBeTrue()
tests/Feature/Foundation/TenancyTest.php:40:    StoreDomain::factory()->create([
tests/Feature/Storefront/ThemeDataTest.php:5:use App\Models\NavigationMenu;
tests/Feature/Storefront/ThemeDataTest.php:37:        ->and(NavigationMenu::withoutGlobalScopes()->count())->toBe(4)
tests/Feature/Storefront/ThemeDataTest.php:49:    NavigationMenu::factory()->create(['store_id' => $firstStore->getKey(), 'handle' => 'first-menu']);
tests/Feature/Storefront/ThemeDataTest.php:50:    NavigationMenu::factory()->create(['store_id' => $secondStore->getKey(), 'handle' => 'second-menu']);
tests/Feature/Storefront/ThemeDataTest.php:56:        ->and(NavigationMenu::query()->pluck('handle')->all())->toBe(['first-menu']);
tests/Feature/Storefront/ThemeDataTest.php:64:    $firstMenu = NavigationMenu::factory()->create(['store_id' => $firstStore->getKey()]);
tests/Feature/Storefront/ThemeDataTest.php:65:    $secondMenu = NavigationMenu::factory()->create(['store_id' => $secondStore->getKey()]);
tests/Feature/Storefront/ThemeDataTest.php:96:    $menu = NavigationMenu::withoutGlobalScopes()


output:
Chunk ID: 325344
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 779
Output:
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class ProductPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isAnyRole($user);
    }

    public function view(User $user, object $product): bool
    {
        return $this->isAnyRole($user, $this->storeIdForModel($product));
    }

    public function create(User $user): bool
    {
        return $this->isOwnerAdminOrStaff($user);
    }

    public function update(User $user, object $product): bool
    {
        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($product));
    }

    public function delete(User $user, object $product): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($product));
    }

    public function archive(User $user, object $product): bool
    {
        return $this->delete($user, $product);
    }

    public function restore(User $user, object $product): bool
    {
        return $this->delete($user, $product);
    }
}
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class CollectionPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isAnyRole($user);
    }

    public function view(User $user, object $collection): bool
    {
        return $this->isAnyRole($user, $this->storeIdForModel($collection));
    }

    public function create(User $user): bool
    {
        return $this->isOwnerAdminOrStaff($user);
    }

    public function update(User $user, object $collection): bool
    {
        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($collection));
    }

    public function delete(User $user, object $collection): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($collection));
    }
}
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class DiscountPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isAnyRole($user);
    }

    public function view(User $user, object $discount): bool
    {
        return $this->isAnyRole($user, $this->storeIdForModel($discount));
    }

    public function create(User $user): bool
    {
        return $this->isOwnerAdminOrStaff($user);
    }

    public function update(User $user, object $discount): bool
    {
        return $this->isOwnerAdminOrStaff($user, $this->storeIdForModel($discount));
    }

    public function delete(User $user, object $discount): bool
    {
        return $this->isOwnerOrAdmin($user, $this->storeIdForModel($discount));
    }
}
<?php

namespace App\Policies;

use App\Models\User;
use App\Traits\ChecksStoreRole;

class OrderPolicy
{
    use ChecksStoreRole;

    public function viewAny(User $user): bool
    {
        return $this->isAnyRole($user);
    }

    public function view(User $user, object $order): bool
    {
        return $this->isAnyRole($user, $this->storeIdForModel($order));
    }

    public function update(User $user, object $order): bool


cmd: sed -n '1,180p' app/Policies/StorePolicy.php


cmd: sed -n '1,220p' app/Enums/StoreDomainType.php app/Enums/TaxMode.php app/Enums/ThemeStatus.php app/Enums/PageStatus.php app/Enums/NavigationItemType.php app/Enums/ShippingRateType.php


cmd: sed -n '1,160p' app/Models/StoreDomain.php app/Models/Store.php


command: sed -n '1,180p' app/Policies/StorePolicy.php

aggregated_output:
<?php

namespace App\Policies;

use App\Models\Store;
use App\Models\User;
use App\Traits\ChecksStoreRole;

class StorePolicy
{
    use ChecksStoreRole;

    public function view(User $user, Store $store): bool
    {
        return $this->isAnyRole($user, $store->getKey());
    }

    public function update(User $user, Store $store): bool
    {
        return $this->isOwnerOrAdmin($user, $store->getKey());
    }

    public function delete(User $user, Store $store): bool
    {
        return $this->isOwnerOnly($user, $store->getKey());
    }
}


command: sed -n '1,220p' app/Enums/StoreDomainType.php app/Enums/TaxMode.php app/Enums/ThemeStatus.php app/Enums/PageStatus.php app/Enums/NavigationItemType.php app/Enums/ShippingRateType.php

aggregated_output:
<?php

namespace App\Enums;

enum StoreDomainType: string
{
    case Storefront = 'storefront';
    case Admin = 'admin';
    case Api = 'api';
}
<?php

namespace App\Enums;

enum TaxMode: string
{
    case Manual = 'manual';
    case Provider = 'provider';
}
<?php

namespace App\Enums;

enum ThemeStatus: string
{
    case Draft = 'draft';
    case Published = 'published';
}
<?php

namespace App\Enums;

enum PageStatus: string
{
    case Draft = 'draft';
    case Published = 'published';
    case Archived = 'archived';
}
<?php

namespace App\Enums;

enum NavigationItemType: string
{
    case Link = 'link';
    case Page = 'page';
    case Collection = 'collection';
    case Product = 'product';
}
<?php

namespace App\Enums;

enum ShippingRateType: string
{
    case Flat = 'flat';
    case Weight = 'weight';
    case Price = 'price';
    case Carrier = 'carrier';
}