# DESIGNS · flagship round 2 · the other 21 designs-module pages · brand-006 RJ Laravel Admin

**Status: pre-approved by Frank (2026-10-19)** — "just assume that the other 21 design module pages are … assumed that I'm good for them". Design only; no code, no ledger, no commits.
**Sheet:** https://designs.pina-brand-006-rj-laravel-admin.pages.dev/r2/ · **artboards:** /r2/canvas/ (3 pages: Combinations · default rules · blockers / Component list · component classes / PT colours) · **one image:** /r2/contact-sheet.png · round 1 (approved, unchanged) stays at `/` + `/canvas/`.
**Boards:** 39 = 1 summary + 21 page heroes (★) + 17 states / phone. Same band + 1440×900 hero in the locked 4b·R shell + annotation panel as round 1; round 1 itself is not restyled.
**Source:** `r2/src/r2_pages.py` (screens, reusing r1 `d_pages` + orders `o_pages` + archetype shell) · `r2/src/r2_data.py` + `r2_catalogue.json` (snapshot by `snap_catalogue.py`, read-only SELECTs on the local prod dump `prod_dump`, 18 Oct) · `r2/src/r2_build.py` (composites, shots, sheet, canvas, dist) · `r2/src/r2.css` (only the new pieces). Build: `python3 r2/src/r2_build.py [--no-shoot] [--only CB1,PT1] [--dist DIR]` (`--dist` assembles the whole `designs` branch: r1 copied as-is at `/`, r2 at `/r2/`).

## Truth rules (same as round 1)

- **Code at 3.0_Backend `origin/main` 6787e7873** (fetched 2026-10-19; same commit as r1 / TRUTH-DESIGNS), read with `git show` only. API ops are the row's ops, each checked present in `newadmin-api-kit/spec/laravel-admin-openapi.yaml`.
- **Real catalogue only**: every main product, combination, express cost, declaration profile, rule, blocker, design id, component, class, vendor book, PT colour and code is the dump. Catalogue data, no personal data. Dotted underline = example value; swatches are stand-in colours for the real `PT001.jpg…PT062.jpg`.
- **Rail item = the real sidebar** (`admin-sidebar.blade.php`): Design Blockers under Designs; Component Default Rules · Component List · Component Classes · PT Colours under Materials; Combinations under Main Products.
- Tags: purple dashed = proposal · amber dashed = needs backend · blue = API only.

## Page → board → sources

| Page (row) | Hero ★ | States / phone | Main sources (file:line at 6787e7873) |
|---|---|---|---|
| `/admin/combinations` | CB1 grouped by main product (18 live) | CB2 inline express cost + profile select · CB5 phone | `CombinationController::index :29-42` · `combinations/index.blade.php:20-163` · `updateCost :82-110` · `updateDeclarationProfile :64-80` · API `.combinations.index/.express-cost/.declaration-profile/.reorder` |
| `/admin/combinations/create` | CB3 piece rows, names derived live | CB5 (not on phone) | `create :44-57` · `store :113-184` · `create.blade.php:29-134` · API `admin.main-products.show` · `.combinations.store` |
| `/admin/combinations/edit/{main_product}` | CB4 3-Piece rows + validation summary | CB5 (not on phone) | `edit :186-196` · `update :198-270` · API `.combinations.index/.update` |
| `/admin/component-default-rules` | CR1 rule read as a sentence (1 real rule) | CR4 filtered-empty · CR5 view-only role · CR6 phone | `ComponentDefaultRuleController::index :51-72` · `index.blade.php:36-109` · API `.index/.restore/.toggle/.destroy` |
| `/admin/component-default-rules/create` | CR2 WHEN · SET · TO sentence form | CR6 | `_fields.blade.php:17-140` · `pickerData :230-260` · `validatePayload :307-338` · API `admin.fabric-codes.index` · `.store` |
| `/admin/component-default-rules/{componentDefaultRule}/edit` | CR3 rule #1 + conflict-guard dialog | CR6 | `update :127-169` · `conflictResponse :287-300` · API `.show/.update` |
| `/admin/design-blockers` | DB1 the 8 real rules, grouped by product | DB6 hard-delete confirm · DB7 not available (non-admin) · DB8 phone | `DesignBlockerController::index :32-62` · `toggle` · `exportJson` · `role:admin` ctor · API `.index/.export/.toggle/.destroy` |
| `/admin/design-blockers/create` | DB2 same-product pickers + preset impact + deadlock | DB8 | `create/store :64-117` · `presetImpact` · `create.blade.php:13-190` · API `admin.products.designs` · `.preset-impact` · `.store` |
| `/admin/design-blockers/upload` | DB4 JSON upload → preview → confirm | DB5 refused · expired · done · DB8 | `showUpload/processUpload/confirmUpload/cancelUpload` · `upload.blade.php` · API `.upload/.confirm-upload/.cancel-upload` |
| `/admin/design-blockers/{design_blocker}/edit` | DB3 rule #5 + related rule #8 | DB8 | `edit/update :119-176` · API `.show/.update/.preset-impact` |
| `/admin/design-components` | DC1 27 placements + used-by count | DC4 retire with used-by · DC5 phone | `DesignComponentController::index :32-52` · `index.blade.php:28-163` · API `.index/.restore/.destroy/.reorder` |
| `/admin/design-components/create` | DC2 four names + spine | DC5 | `store :54-98` · `spinePickerData :228-251` · `spinePayload` · `_spine_fields.blade.php` · API `admin.pt-components.index` · `.store` |
| `/admin/design-components/{design_component}/edit` | DC3 Navy Sleeve Lining (7) | DC5 | `edit/update :105-159` · API `.show/.update` |
| `/admin/pt-colors` | PT1 the A12 matrix (62 × 7 columns) | PT2 cell editor · PT3 thread colours tab · PT9 phone | `PtColorController::index :66-92` · `_matrix.blade.php` · `_shell.blade.php` · `_cell_modal.blade.php` · `_threads.blade.php` · API `.index/.export/.online/.offline/.destroy/.restore/.bindings.*` |
| `/admin/pt-colors/create` | PT4 three fields + what it still needs | PT9 | `create/store :94-117` · `create.blade.php` · API `.store` |
| `/admin/pt-colors/import` | PT6 PT_MASTER CSV preview, six counts, retire ack | PT7 blocking errors · PT9 | `PtColorImportController` · `PtColorImportPreviewService` (deriveColumns, summary :1228-1240) · `import.blade.php` · API `.export/.import.preview/.confirm/.cancel` |
| `/admin/pt-colors/reports/codeless` | PT8 today: PT025 Plum, nothing affected | PT9 | `codelessReport :512-567` · `codeless-report.blade.php` · API `.reports.codeless` |
| `/admin/pt-colors/{ptColor}/edit` | PT5 PT001 White + provider bindings | PT9 | `edit :119-185` · `update :192-221` · `edit.blade.php` · `PtStringService::componentString/fullString` · API `.show/.update` · `admin.media-library.list` |
| `/admin/pt-components` | PC1 13 classes + books in mill order | PC5 phone | `PtComponentController::index :42-46` · `vendorCodePickerData :177-245` · API `.index/.restore/.reorder` |
| `/admin/pt-components/create` | PC2 four fields | PC5 | `store :53-79` · API `admin.material-types.index` · `.store` |
| `/admin/pt-components/{ptComponent}/edit` | PC3 Plain Lining · accepted books + mill order | PC4 main-mill change (wholesale) · PC5 | `update :89-133` · `syncPreferenceSlots` · `_vendor_code_picker.blade.php` · API `.accepted-vendor-codes.index/.update` · `.preference-order.update` |

**Phone.** Read-mostly pages (the six indexes + the codeless report) get a dedicated `/m/` list and a read-only detail; every create / edit / import page shows the not-on-phone notice (`/m/not-on-phone?to=…`, Copy link · Back; the import notice says the job still shows in the phone Jobs tray). One trio board per sub-area: CB5 · CR6 · DB8 · DC5 · PC5 · PT9.

**Page map.** Each of the 21 rows' `hero_ref` now points at its r2 hero board, via `phase-3/waves/proposals_data.py` `DESIGNS_R2_HERO` (folded into `ROW_PATCHES`) plus a one-line guard in `build_page_map.py` that drops the stale "queued for round 2" note on drawn rows. Re-ran `build_page_map.py` + `check_page_map.py` (OK): exactly these 21 rows changed (`hero_ref`, `notes`); otherwise only the `generated` date.

## Data findings (real, surfaced while grounding)

1. **Combination create stores Printer name nested** — combination 484 (Overshirt): `printer_name = {"0":{"en":…,"cn":…},"en":…,"cn":…}`. `store()` builds a list then appends to a new key (`:137-158`); `update()` writes the flat pair. → G-C1.
2. **Combination churn**: 18 live combinations sit on 420 rows (3-Piece 1 live / 155 soft-deleted, 2-Piece 3 / 86). Rows match on (position, pieces), so changes create rows rather than rename.
3. **The web combinations sorter is dead**: it posts `type=Combination` to `/admin/sorting` and gets a 400 (spec note on `combinations.reorder`); the table also lacks the `sortTable` hook. The API reorder is the working path.
4. **Design blocker import is JSON, not CSV** (TRUTH-DESIGNS §17 says CSV); and `design_blockers` has no soft delete — Delete is permanent.
5. **PT025 Plum is the one codeless colour** (no plain-lining code; only QF138 · RM3690); no swatch is bound to it.
6. **Plain Lining backup in use** on 6 colours with no SHONG code (PT015 · PT024 · PT032 · PT033 · PT049 · PT053); PT025 has no plain code at all, so it shows +, not ◎.
7. **Sleeve Lining is PT-based but accepts no book** → no matrix column; **Shirt Monogram** (owner factory) accepts the supplier book QF (SQF). Thread classes carry no parent primary: every thread cell is a per-colour override (61 · 54 · 43 · 35).
8. Seven component classes still sit at sort 999999; blend / weight columns on provider codes are newer than the 18 Oct dump (drawn as examples on PT2 / PT5).

## Needs backend

| # | Gap | Evidence | Proposal |
|---|---|---|---|
| **G-C1** | Combination create stores `printer_name` nested | `CombinationController::store :137-158`; prod combination 484 | write the flat {en, cn} pair in store (web + API); backfill the nested rows |
| G-B1 | Design blocker delete is a hard delete | `DesignBlocker` model has no SoftDeletes; `destroy :215-221` | soft delete + Restore, like the other designs lists |
| G-D1 | No "used by" read for a design component | count shown on DC1/DC3/DC4 is from the dump (`designs.design_component_id`, `fields.design_component_id`) | read by component id → Used column, where-used panel, retire confirm |
| PR-021 | Paste from Excel into the PT colour import | row `needs_backend` (existing proposal) | unchanged |

## Open questions (only where the code is genuinely ambiguous) · recommended default first

1. **Who may bind a declaration profile to a combination?** The web route is `role:admin`; the API op requires `view pt_colors AND edit combinations`. **1a (recommended):** follow the API permission so the control is permission-shaped like everything else · 1b: keep admin-only on both.
2. **Design Blockers access.** The whole module is `role:admin` (no permission names), while the blockers also appear on each design's Rules section for `edit designs`. **2a (recommended):** keep admin-only for the module (drawn, DB7) · 2b: gate it on `edit designs` like the design page.
3. **Thread classes' main mill.** Thread classes have no parent primary — every cell is its own override — so PC4's wholesale "change the main mill" would reset 35–61 overrides there. **3a (recommended):** show the mill-order control only for classes that already have a parent primary (Plain Lining today), and show "per-colour primaries" for thread classes · 3b: show it on every PT-based class, with the reset count in the confirm.

## Could not verify

- Live role permissions (CR5 / DB7 assume a view-only / non-admin role).
- `DesignBlockerService::presetImpact` counts (DB2 shows an example); PtStringService's status gate per book (the dump has no per-cell status column; all cells drawn live).
- The real swatch photos (stand-in colours; paths are real, e.g. `PT001.jpg`).
