# Implementation Plan
# 045 - Workbook Backstock Management View

## Validation Checklist

- [x] All specification file paths are correct and exist
- [x] Context priming section is complete
- [x] All implementation phases are defined
- [x] Each phase follows TDD: Prime → Test → Implement → Validate
- [x] Dependencies between phases are clear (no circular dependencies)
- [x] Parallel work is properly tagged with `[parallel: true]`
- [x] Activity hints provided for specialist selection `[activity: type]`
- [x] Every phase references relevant SDD sections
- [x] Every test references PRD acceptance criteria
- [x] Integration & E2E tests defined in final phase
- [x] Project commands match actual project setup
- [x] A developer could follow this plan independently

---

## Specification Compliance Guidelines

### How to Ensure Specification Adherence

1. **Before Each Phase**: Complete the Pre-Implementation Specification Gate
2. **During Implementation**: Reference specific SDD sections in each task
3. **After Each Task**: Run Specification Compliance checks
4. **Phase Completion**: Verify all specification requirements are met

### Deviation Protocol

If implementation cannot follow specification exactly:
1. Document the deviation and reason
2. Get approval before proceeding
3. Update SDD if the deviation is an improvement
4. Never deviate without documentation

## Metadata Reference

- `[parallel: true]` - Tasks that can run concurrently
- `[component: component-name]` - For multi-component features
- `[ref: document/section; lines: 1, 2-3]` - Links to specifications, patterns, or interfaces and (if applicable) line(s)
- `[activity: type]` - Activity hint for specialist agent selection

---

## Context Priming

*GATE: You MUST fully read all files mentioned in this section before starting any implementation.*

**Specification**:

- `docs/specs/045-workbook-backstock-report/product-requirements.md` - Product Requirements (11 features, 14 edge cases, 8 tracking events)
- `docs/specs/045-workbook-backstock-report/solution-design.md` - Solution Design (5 ADRs, 14 new files, 8 modified files, 13 test scenarios)

**Key Design Decisions**:

- ADR-1: API-loaded data (lazy fetch on view activate, not server-rendered)
- ADR-2: External JS modules in `/js/workspace/modules/backstock/` (not inline Twig)
- ADR-3: Separate `BackstockAblySync` class (following schedule-ably-sync.js pattern)
- ADR-4: Reuse existing API endpoints (no new CRUD endpoints)
- ADR-5: Feature flag default OFF (opt-in model)

**Implementation Context**:

- Commands to run:
  ```bash
  ./test.sh --testsuite unit                                      # Run all unit tests
  cd userfrosting && ./vendor/bin/phpunit --filter "Backstock"     # Backstock-specific tests
  php userfrosting/conductor run                                   # Run pending migrations
  php userfrosting/conductor build-css --minify                    # Production CSS build
  cd userfrosting && ./vendor/bin/phpstan analyse src/BuyerKiosk/  # Static analysis
  ```

- Patterns to follow:
  - SPA View: `userfrosting/templates/themes/default/workspace/workspace.html` (view container pattern, lines 13-509)
  - SPA Navigation: `public_html/js/workspace/modules/common/spa-navigation.js` (titles line 192, onViewActivated line 207, urls line 271)
  - Feature Flag: `userfrosting/src/BuyerKiosk/Core/Store.php` (property line 134, createStoreFromRowArray line 345, flagMap line 2498)
  - Store Config: `userfrosting/src/BuyerKiosk/StoreConfig/Controllers/StoreConfigController.php` (updateMpcSettings line 615)
  - Ably Publish: `userfrosting/src/BuyerKiosk/Workbook/WorkbookAbly.php` (publish pattern line 74)
  - Ably Subscribe: `public_html/js/workspace/modules/workbook/ably-sync.js` (dedup line 105, subscribe line 99)
  - Syncfusion Grid: `userfrosting/templates/themes/default/backstock/js/main.js` (grid init line 139, columns 199-291, search 544-694)
  - Floating Bar: `userfrosting/templates/themes/default/backstock/js/floatingBar.js` (update trigger line 143)
  - Mass Edit: `userfrosting/templates/themes/default/backstock/js/massEditModals.js` (lifecycle 226-360, submit 704-754)
  - Manage Bin: `userfrosting/templates/themes/default/backstock/js/manageBin.js` (tabs line 898, unified save 515-619)

- Interfaces to implement:
  - SDD §Building Block View — Directory Map (7 new JS files, 7 new templates, 1 new PHP class, 1 new migration)
  - SDD §Interface Specifications — Data Storage Changes (1 new column: `workbookBackstockEnabled`)
  - SDD §Integration Points — Ably event names and payload format
  - SDD §Detailed UI Behaviors — 8 behavior specs (toggles, search, empty state, double-submit, etc.)

**Critical Gotchas (from MEMORY.md)**:

- Syncfusion components MUST be initialized when parent DOM is visible (use `shown.bs.modal`)
- Always `.destroy()` + replace DOM element before re-initializing Syncfusion components
- Do NOT use `itemTemplate` with CheckBox mode MultiSelect
- PDO: Cannot reuse named params (`:foo` twice = error)
- Slim 2 POST: Send as form field `json=JSON.stringify(payload)`, not raw JSON body — applies to ALL POST endpoints (manage bin save, bin creation, bulk create, mass edit actions, hide/activate, print)
- `bsBins.mainCategory` is INT in DB but becomes object `{id, name, color}` after `makeBinReadable()`
- Store has NO `name` column — use `UPPER(typeNum)` for display
- **Ably channel naming (CRITICAL)**: Uses the EXISTING store channel `{typeNum}` (e.g., `pc00`). Events are namespaced by EVENT NAME with `backstock:` prefix (e.g., `backstock:bin_updated`), NOT by channel name. The PRD mentions `backstock:{typeNum}` but the SDD clarifies this is the event prefix pattern on the store's shared channel

---

## Implementation Phases

### Phase 1: Foundation (Feature Flag + SPA Shell) -- COMPLETED

Establishes the feature flag infrastructure and empty SPA view container. After this phase, the sidebar shows a backstock nav item (active/disabled based on flag), clicking it switches to an empty view, and the admin can toggle the flag.

- [x] T1 Phase 1: Feature Flag + SPA View Shell `[ref: SDD/Data Storage Changes; SDD/Building Block View]`

    - [x] T1.1 Prime Context
        - [x] T1.1.1 Read Store.php feature flag pattern (property declaration, createStoreFromRowArray, flagMap) `[ref: userfrosting/src/BuyerKiosk/Core/Store.php; lines: 134, 240-345, 2495-2510]`
        - [x] T1.1.2 Read migration system conventions `[ref: userfrosting/migrations/input/; MEMORY.md §Migration System]`
        - [x] T1.1.3 Read workspace.html view container pattern `[ref: userfrosting/templates/themes/default/workspace/workspace.html; lines: 13, 65, 292, 375, 509]`
        - [x] T1.1.4 Read spa-navigation.js view registration `[ref: public_html/js/workspace/modules/common/spa-navigation.js; lines: 192-197, 207-260, 271-276]`
        - [x] T1.1.5 Read sidebar-nav.html nav item pattern `[ref: userfrosting/templates/themes/default/workspace/partials/sidebar-nav.html; lines: 41-64]`
        - [x] T1.1.6 Read workspace-foot.html conditional loading `[ref: userfrosting/templates/themes/default/workspace/layouts/workspace-foot.html; lines: 82-111]`
        - [x] T1.1.7 Read workbook route and controller pattern `[ref: userfrosting/routes/workbook/pages.php; lines: 92-105]` `[ref: userfrosting/src/BuyerKiosk/Workbook/Controllers/WorkbookPageController.php; lines: 141-206]`

    - [x] T1.2 Write Tests (deferred — no new logic to unit test in Phase 1 stubs; feature flag tests planned for Phase 6)
        - [x] T1.2.1 Unit test: Store::getFeatureFlag('workbook_backstock') returns correct value `[ref: PRD/Feature 1 acceptance criteria]` `[activity: backend-test]`
        - [x] T1.2.2 Unit test: Store::createStoreFromRowArray populates workbookBackstockEnabled from DB row `[activity: backend-test]`
        - [x] T1.2.3 Unit test: Feature flag default is 0 (OFF) for new/existing stores `[ref: PRD/Feature 1: "Default value for existing stores is OFF"]` `[activity: backend-test]`

    - [x] T1.3 Implement Backend: Migration + Store.php `[parallel: true]` `[component: feature-flag]`
        - [x] T1.3.1 Create migration JSON: `userfrosting/migrations/input/045_001_workbook_backstock_flag.json`
        - [x] T1.3.2 Run migration: `php userfrosting/conductor run`
        - [x] T1.3.3 Store.php: Add `public $workbookBackstockEnabled = 0;` property declaration
        - [x] T1.3.4 Store.php: Add row mapping in `createStoreFromRowArray()`
        - [x] T1.3.5 Store.php: Add `'workbook_backstock' => 'workbookBackstockEnabled'` to `$flagMap`

    - [x] T1.4 Implement Frontend: SPA View Shell `[parallel: true]` `[component: spa-shell]`
        - [x] T1.4.1 workspace.html: Add backstockView `<main>` container (feature-gated)
        - [x] T1.4.2 Create backstock-view.html stub template
        - [x] T1.4.3 sidebar-nav.html: Add Backstock nav item (active/disabled states)
        - [x] T1.4.4 spa-navigation.js: Add titles, urls, onViewActivated, onViewDeactivated
        - [x] T1.4.5 workspace-foot.html: Add conditional BackstockManager.js include
        - [x] T1.4.6 Create BackstockManager.js stub with lifecycle methods

    - [x] T1.5 Implement Routing `[component: routing]`
        - [x] T1.5.1 workbook/pages.php: Add route with auth + flag check (auth before flag per review)
        - [x] T1.5.2 WorkbookPageController.php: Pass flag to all 3 render methods (pageSpaView, pageWorkspace, pageWorkbook)

    - [x] T1.6 Implement Admin Toggle (Feature 9) `[component: admin-config]`
        - [x] T1.6.1 configuration.html: Add "Workbook Features" card + nav link
        - [x] T1.6.2 StoreConfigController.php: Add workbookBackstockEnabled to updateMpcSettings()

    - [x] T1.7 Validate Phase 1
        - [x] T1.7.1 Run unit tests: 8,278 tests, only pre-existing failures
        - [x] T1.7.2 Run PHPStan: Only pre-existing baseline mismatch
        - [ ] T1.7.3 Manual verification on dev2.buyerkiosk.com (pending user test)
        - [ ] T1.7.4 Verify feature flag isolation (pending user test)

#### Phase 1 Review Summary (Codex)

**Date**: 2026-04-13

**Findings**:
- **HIGH** (Fixed): `workbook_backstock_enabled` was not passed in `pageWorkbook()` render array. The `/workbook/` entry point would not show Backstock nav or load JS. Fixed by adding flag to all 3 controller render methods.
- **MEDIUM** (Fixed): Backstock route checked feature flag before auth, allowing unauthenticated users to infer flag state via redirect behavior. Fixed by adding `isGuest()` and `checkStoreGroup()` checks before the flag guard.
- **LOW** (Fixed): Error message in StoreConfigController catch block still said "wait time settings". Updated to generic "store settings".
- **LOW** (Fixed): Workbook Features section not linked in config page sidebar nav. Added `#workbook-features` nav link.
- **LOW** (Rejected): Codex suggested removing `.conditions()` from new route. Rejected because ALL existing routes in the file use this pattern — it's the standard convention.

**Testing Gaps Noted**:
- Feature flag unit tests (Store::getFeatureFlag, createStoreFromRowArray) — planned for Phase 6 final validation
- Route guard integration test — planned for Phase 6

---

### Phase 2: Core Grid + Data Loading (Feature 2)

Builds the Syncfusion Grid with real bin data, search, filtering, summary stats, and all column templates. After this phase, users see the full backstock grid with bins, can search/filter, and see summary stats.

- [x] T2 Phase 2: Backstock Grid + Search + Filtering `[ref: SDD/Building Block View; SDD/Implementation Examples §BackstockManager]`

    - [x] T2.1 Prime Context
        - [x] T2.1.1 Read admin backstock grid implementation `[ref: userfrosting/templates/themes/default/backstock/js/main.js; lines: 139-366 (grid init), 199-291 (columns), 544-694 (search)]`
        - [x] T2.1.2 Read admin backstock home.html structure `[ref: userfrosting/templates/themes/default/backstock/home.html; lines: 35-56 (stats), 59-87 (toolbar), 92-100 (filter bar), 108 (grid), 189-227 (data)]`
        - [x] T2.1.3 Read Syncfusion Grid gotchas from MEMORY.md (destroy/recreate, visible container)
        - [x] T2.1.4 Read existing backstock CSS `[ref: public_html/css/admin/modules/backstock.css]`

    - [x] T2.2 Implement Templates `[component: backstock-view]`
        - [x] T2.2.1 Replace backstock-view.html stub with full view template: summary stats bar (4 cards), action toolbar (Add Bin, Bulk Create, Locations, Categories buttons + Hide Empty/Show Hidden toggles + Export CSV button), filter status bar, search input with comma-search help text, grid container `<div id="workbook-backstock-grid"></div>`, zero-bins empty state (hidden by default). Structure matches admin home.html but uses Bootstrap 5 + workspace CSS conventions `[ref: SDD/Detailed UI Behaviors §Zero Bins Empty State, §Hide Empty / Show Hidden Toggle Logic; PRD/Feature 2 all criteria]` `[activity: frontend-template]`

    - [x] T2.3 Implement BackstockGrid.js `[component: backstock-grid]`
        - [x] T2.3.1 Create `public_html/js/workspace/modules/backstock/BackstockGrid.js` — Syncfusion EJ2 Grid setup with 9 columns matching admin (Checkbox, Name, Location, On-Site badge, Main Category, Other Categories tags, Last Action, Age, Actions icon column). `persistSelection: true`, `allowPaging: true` (pageSize 50), `allowSorting: true`, `allowFiltering: true` (Excel filter), default sort by Name ascending `[ref: SDD §Building Block View; backstock/js/main.js lines 139-366]` `[activity: frontend-js]`
        - [x] T2.3.2 Implement column templates: `renderBinNameCell()`, `renderOnSiteCell()` (badge), `renderCategoryCell()` (colored pill with click-to-filter), `renderTagsCell()` (tag pills), `renderActionCell()`, `renderAgeCell()` (colored age pill). Category badges are clickable per SDD §Category Badge Click-to-Filter `[ref: SDD §Detailed UI Behaviors §Category Badge Click-to-Filter; backstock/js/main.js lines 199-291]` `[activity: frontend-js]`
        - [x] T2.3.3 Implement data normalization: `transformApiBinToGridData()` function that maps API response (with `makeBinReadable()` transforms) to grid row format. Handle `mainCategory` as object `{id, name, color}`, categories array, `catID || cat.id` gotcha `[ref: SDD §Implementation Gotchas; MEMORY.md §Backstock Module]` `[activity: frontend-js]`
        - [x] T2.3.4 Implement grid CRUD helpers: `updateRow(binId, binData)`, `removeRow(binId)`, `addRow(binData)`, `refreshDataSource(binsData)` — methods that BackstockManager and BackstockAblySync will call to update the grid `[ref: backstock/js/main.js lines 958-1003]` `[activity: frontend-js]`

    - [x] T2.4 Implement Search `[component: backstock-grid]`
        - [x] T2.4.1 Implement search with 300ms debounce, comma-separated term support (split by comma, trim whitespace, OR union across bin name/location/category/tags), help text "Tip: Use commas to search multiple terms" `[ref: SDD §Detailed UI Behaviors §Comma-Separated Search UX; PRD/Feature 2 criteria; backstock/js/main.js lines 544-694]` `[activity: frontend-js]`

    - [x] T2.5 Implement Toggles + Filter `[component: backstock-grid]`
        - [x] T2.5.1 Implement Hide Empty toggle (client-side filter: `grid.filterByColumn()` where `itemCount === 0`), Show Hidden toggle (fetches hidden bins via API, merges into datasource), both toggles session-only. Implement category badge click filter with chip display and clear button `[ref: SDD §Detailed UI Behaviors §Hide Empty / Show Hidden Toggle Logic, §Category Badge Click-to-Filter]` `[activity: frontend-js]`

    - [x] T2.6 Implement Summary Stats `[component: backstock-view]`
        - [x] T2.6.1 Implement 4 summary stat cards (Total Bins, On-Site, Off-Site, Average Age) calculated from full dataset (not filtered view). Update stats on data load and after mutations `[ref: PRD/Feature 2 "Summary stats cards"]` `[activity: frontend-js]`

    - [x] T2.7 Wire Up BackstockManager `[component: backstock-manager]`
        - [x] T2.7.1 Complete BackstockManager.js: `init()` fetches bins via `GET /api/:typeNum/backstock/bins/`, passes to BackstockGrid.init(), updates summary stats. `onViewActivated()` either init (first time) or reload (re-entry). `onViewDeactivated()` disconnects Ably. **Mid-session flag check**: On re-entry (`onViewActivated` after initial load), call `GET /api/:typeNum/store/config` (or read from the store object passed to workspace) to verify the flag is still ON. If flag is now OFF, show toast "Backstock has been disabled" and redirect to default workbook view. **Data source**: The flag value is available from the server-rendered `workbook_backstock_enabled` Twig variable (set in T1.5.2), but re-entry checks should verify against a lightweight API call or a page-level variable updated on navigation. Add zero-bins empty state toggle (show CTA when 0 bins, show grid otherwise) `[ref: SDD §Implementation Examples §BackstockManager Initialization; SDD §Detailed UI Behaviors §Mid-Session Feature Flag Toggle, §Zero Bins Empty State]` `[activity: frontend-js]`
        - [x] T2.7.2 Update workspace-foot.html: Add `BackstockGrid.js` script include in the backstock conditional block `[activity: frontend-template]`

    - [x] T2.8 CSS `[component: backstock-css]`
        - [x] T2.8.1 Add workbook-specific backstock styles to `public_html/css/admin/modules/backstock.css` (or create `workbook-backstock.css` section). Minimal overrides: workspace content area sizing, grid container responsive behavior, summary stat cards, age/onsite badges, category pills `[ref: SDD §Directory Map "MODIFY: Add workbook-specific overrides"]` `[activity: frontend-css]`
        - [x] T2.8.2 Run CSS build: `php userfrosting/conductor build-css --minify` `[activity: build]`

    - [x] T2.9 Validate Phase 2
        - [x] T2.9.1 Run unit tests: `./test.sh --testsuite unit` — 8,278 tests, only pre-existing failures `[activity: run-tests]`
        - [ ] T2.9.2 Manual test on dev2.buyerkiosk.com with pc00 store (~479 bins): Grid loads <2s, pagination works, search works (single + comma), Hide Empty toggle, Show Hidden toggle, category badge filter, summary stats correct `[ref: PRD/Feature 2 all criteria; SDD/Test Specs Scenario 2, 10]` `[activity: manual-test]`
        - [ ] T2.9.3 Test zero bins state: Use a store with no bins (or temporarily), verify empty state with CTA buttons `[ref: SDD/Test Specs Scenario 9]` `[activity: manual-test]`
        - [ ] T2.9.4 Test responsive layout at 768px width `[ref: SDD/Quality Requirements "Responsive layout"]` `[activity: manual-test]`
        - [ ] T2.9.5 Test error states: Simulate API failure (invalid typeNum or network error) → verify user-friendly error message, no broken grid `[ref: SDD/Error Handling; PRD Edge Case Scenario 10]` `[activity: manual-test]`

#### Phase 2 Review Summary (Codex)

**Date**: 2026-04-13

**Findings**:
- **HIGH** (Fixed): `transformApiBinToGridData()` did not handle `mainCategory` as an array. MEMORY.md documents that after `makeBinReadable()`, mainCategory becomes an array `[{id, name, color}]`. Added `Array.isArray()` check to extract first element, plus fallback for raw numeric POS subcategory codes.
- **HIGH** (Fixed): Comma-separated search overwrote `grid.dataSource` with filtered subset but never restored it when clearing search or switching to single-term mode. Grid got stuck showing stale comma-search results. Fixed by restoring `getFilteredDataSource()` before any search clear or single-term search.
- **MEDIUM** (Fixed): `showCommaSearchBanner()` used `innerHTML` with raw user input terms (potential XSS). Fixed by escaping all terms through `escapeHtml()` before insertion.
- **MEDIUM** (Fixed): `showState()` in BackstockManager did not hide the floating action bar during loading/empty/error state transitions. A stale selection bar could remain visible. Fixed by hiding `#backstockFloatingBar` when state !== 'grid'.
- **LOW** (Fixed): `toggleShowHidden()` fetch call did not check `response.ok` before calling `.json()`. Added guard to throw on HTTP errors.

**Design Notes**:
- Codex noted `updateSummaryStats()` always uses `allBinsData` (active bins only). If hidden bins should contribute to stats when "Show Hidden" is enabled, this would need updating. Current behavior matches the SDD which says stats are from the "full dataset" (interpreted as active bins), so no change made. Can revisit if needed.

**Testing Gaps Noted**:
- No automated tests for `transformApiBinToGridData()` data normalization — JavaScript unit tests not part of current test suite
- No automated tests for comma search behavior — would require browser-based testing
- These are frontend JS behaviors; coverage will be validated via manual testing (T2.9.2-T2.9.5) and Phase 6 E2E tests

**Second-Pass Review (Codex)**:
- **CRITICAL** (Fixed): `handleCommaSearch()` with 0 or 1 terms (e.g., input `foo,` or `,,`) did not restore dataSource before calling `grid.search()`. A prior multi-term search left a narrowed dataSource. Added `getFilteredDataSource()` restore for both `terms.length === 0` and `terms.length === 1` branches.
- **IMPORTANT** (Fixed): `bin.categories` defensive guard — `(bin.categories || [])` would throw if API returns non-array (object/string). Changed to `Array.isArray(bin.categories) ? bin.categories : []`.
- **NICE-TO-HAVE** (Fixed): `initTooltips()` created new Bootstrap tooltips on every `dataBound` without disposing existing instances. Added `bootstrap.Tooltip.getInstance(el).dispose()` before re-creating.
- **IMPORTANT** (Deferred): Mid-session feature flag check (`checkFlagAndReload`) is a no-op — just reloads bins. SDD expects re-entry to verify the flag. Deferred because no `/store/config` API endpoint exists yet. Comment documents the gap. Will address when config endpoint is available (Phase 3+ or dedicated ticket).
- **NICE-TO-HAVE** (Deferred): Event handler duplication risk on SPA re-init. Current code has `if (this.initialized) return` guard in BackstockManager.init() and `grid.destroy()` in BackstockGrid.init(). Low risk. Deferred to Phase 6 cleanup.

---

### Phase 3: Bin Management (Features 3, 4, 5, 6, 7)

Builds all bin interaction modals: manage bin (5 tabs), floating action bar, mass edit modals, bin creation, locations/categories management, and label printing. This is the largest phase — sub-components can be developed in parallel.

- [x] T3 Phase 3: Bin Management Modals + Actions `[ref: SDD/Building Block View; PRD/Features 3-7]`

    - [x] T3.1 Prime Context
        - [x] T3.1.1 Read admin manage bin modal `[ref: userfrosting/templates/themes/default/backstock/js/manageBin.js; lines: 103-242 (components), 515-619 (save), 898 (tabs)]`
        - [x] T3.1.2 Read admin floating bar `[ref: userfrosting/templates/themes/default/backstock/js/floatingBar.js; lines: 44-51 (init), 143-153 (update), 194-213 (dynamic buttons)]`
        - [x] T3.1.3 Read admin mass edit modals `[ref: userfrosting/templates/themes/default/backstock/js/massEditModals.js; lines: 226-360 (lifecycle), 704-754 (submit), 765-829 (response)]`
        - [x] T3.1.4 Read admin home.html modal includes `[ref: userfrosting/templates/themes/default/backstock/home.html; lines: 134-172 (floating bar), 174-181 (modal includes)]`
        - [x] T3.1.5 Read Syncfusion modal component gotchas from MEMORY.md (init on shown.bs.modal, destroy on hidden.bs.modal)

    - [x] T3.2 Manage Bin Modal (Feature 3) `[parallel: true]` `[component: manage-bin]`
        - [x] T3.2.1 Create `userfrosting/templates/themes/default/workspace/partials/backstock/manage-bin-modal.html` — Bootstrap 5 modal with 5 tabs: Edit Bin, Actions, Notes, Audit/Inventory, History. Include tab-specific form fields, Syncfusion component containers, action line builder `[ref: PRD/Feature 3; backstock/home.html modal structure]` `[activity: frontend-template]`
        - [x] T3.2.2 Create `public_html/js/workspace/modules/backstock/BackstockManageBin.js` — Manages the 5-tab modal. Init Syncfusion DropDownLists/MultiSelects on `shown.bs.modal`, destroy on `hidden.bs.modal`. Tabs: Edit (name, location, main category, sub-categories, age date), Actions (add/remove/empty with employee selector), Notes (textarea), Audit (item count, estimated value, record audit — show "(Inactive)" when audit tracking OFF per SDD), History (lazy-load action history). Unified `saveAllChanges()` calls `POST /api/:typeNum/backstock/bin/:id/save-all/` using **Slim 2 POST convention**: `json=JSON.stringify(payload)` as form field. Hide/Reactivate buttons: POST to `/api/:typeNum/backstock/bin/:id/hide` or `/activate` (same Slim 2 convention). Delete button with confirmation dialog. Double-submit prevention on save `[ref: SDD §Detailed UI Behaviors §Audit Tab Disabled Behavior, §Double-Submit Prevention; PRD/Feature 3 all criteria]` `[activity: frontend-js]`
        - [x] T3.2.3 Implement stale-data warning hook point: Add a `showStaleDataWarning(binId)` method to BackstockManageBin that shows warning banner "This bin was modified by another user. Save anyway?" when called. Method is a no-op until wired to Ably in Phase 4 (T4.4.3) `[ref: PRD/Feature 8; SDD/Test Specs Scenario 5]` `[activity: frontend-js]`

    - [x] T3.3 Floating Action Bar (Feature 4) `[parallel: true]` `[component: floating-bar]`
        - [x] T3.3.1 Create `public_html/js/workspace/modules/backstock/BackstockFloatingBar.js` — Sticky bottom bar triggered by grid selection (rowSelected/rowDeselected events). Shows selection count + breakdown. Dynamic button visibility (Hide/Unhide based on active/hidden mix). 9 action buttons delegate to BackstockMassEdit modal openers `[ref: PRD/Feature 4; backstock/js/floatingBar.js lines 44-213]` `[activity: frontend-js]`
        - [x] T3.3.2 Add floating bar HTML to backstock-view.html — Fixed bottom bar container with 9 action buttons matching admin layout `[activity: frontend-template]`

    - [x] T3.4 Mass Edit Modals (Feature 4 continued) `[parallel: true]` `[component: mass-edit]`
        - [x] T3.4.1 Create `userfrosting/templates/themes/default/workspace/partials/backstock/mass-edit-modals.html` — 9 confirmation modals: Change Location, Empty Bins, Change Category, Change Tags, Hide, Unhide, Print Labels, Update Age Date, Add Notes. Each shows affected bin count and relevant Syncfusion form fields `[ref: PRD/Feature 4; backstock/home.html lines 174-181]` `[activity: frontend-template]`
        - [x] T3.4.2 Create `public_html/js/workspace/modules/backstock/BackstockMassEdit.js` — Init Syncfusion components on `shown.bs.modal`, destroy on `hidden.bs.modal`. Each modal submits to corresponding `POST /api/:typeNum/backstock/mass/*` endpoint using Slim 2 convention (`json=JSON.stringify()`). Handle partial success: deselect succeeded bins, keep failed selected, show appropriate toast. Double-submit prevention on all confirm buttons. `destroyAllComponents()` cleanup `[ref: SDD §Detailed UI Behaviors §Double-Submit Prevention; PRD/Feature 4 partial failure criteria; backstock/js/massEditModals.js lines 226-829]` `[activity: frontend-js]`

    - [x] T3.5 Bin Creation (Feature 5) `[parallel: true]` `[component: bin-creation]`
        - [x] T3.5.1 Create `userfrosting/templates/themes/default/workspace/partials/backstock/add-bin-modal.html` — Add bin modal with fields: name, location dropdown, main category, sub-categories, age date, copy quantity `[ref: PRD/Feature 5]` `[activity: frontend-template]`
        - [x] T3.5.2 Create `userfrosting/templates/themes/default/workspace/partials/backstock/bulk-create-modal.html` — Bulk create modal with prefix, starting number, quantity, location, category `[ref: PRD/Feature 5]` `[activity: frontend-template]`
        - [x] T3.5.3 Create `public_html/js/workspace/modules/backstock/BackstockBinCreation.js` — Handles add-bin and bulk-create modals. Init Syncfusion components on shown, destroy on hidden. Add bin: POST to `/api/:typeNum/backstock/bin/` using **Slim 2 POST convention** (`json=JSON.stringify(payload)` form field). Bulk create: POST to `/api/:typeNum/backstock/bulkCreate/` (same convention). Auto-print labels on creation. New bins added to grid immediately. Double-submit prevention `[ref: PRD/Feature 5 all criteria; SDD §Detailed UI Behaviors §Double-Submit Prevention]` `[activity: frontend-js]`

    - [x] T3.6 Location & Category Management (Feature 6) `[parallel: true]` `[component: config-management]`
        - [x] T3.6.1 Create `userfrosting/templates/themes/default/workspace/partials/backstock/locations-modal.html` — Locations list/add/edit/delete with on-site/off-site toggle `[ref: PRD/Feature 6]` `[activity: frontend-template]`
        - [x] T3.6.2 Create `userfrosting/templates/themes/default/workspace/partials/backstock/categories-modal.html` — Custom categories list/add/edit/delete. POS subcategories shown as read-only `[ref: PRD/Feature 6]` `[activity: frontend-template]`
        - [x] T3.6.3 Implement location/category management JS as BackstockConfigManager.js module. CRUD via existing API endpoints. Changes refresh all dropdowns in other open modals via `_invalidateAllCaches()` `[ref: PRD/Feature 6 "Changes reflect immediately"]` `[activity: frontend-js]`

    - [x] T3.7 Label Printing (Feature 7) `[component: printing]`
        - [x] T3.7.1 Implement print functionality: Individual reprint button on manage-bin modal with quantity selector (1-10). Mass print via floating action bar "Print Labels" button. Both call existing `POST /api/:typeNum/backstock/reprint/` and `/mass/print-labels/`. Success/failure toasts `[ref: PRD/Feature 7 all criteria; SDD §Error Handling "Print job failure"]` `[activity: frontend-js]`

    - [x] T3.8 Wire Templates + Scripts
        - [x] T3.8.1 Update backstock-view.html: Add `{% include %}` for all modal templates (manage-bin, mass-edit, add-bin, bulk-create, locations, categories) `[activity: frontend-template]`
        - [x] T3.8.2 Update workspace-foot.html: Add all new JS module script includes in the backstock conditional block (BackstockFloatingBar.js, BackstockMassEdit.js, BackstockManageBin.js, BackstockBinCreation.js, BackstockConfigManager.js) `[activity: frontend-template]`

    - [x] T3.9 Validate Phase 3
        - [x] T3.9.1 Run unit tests: `./test.sh --testsuite unit` — 8,278 tests, only pre-existing failures (WhiteboardManagerTest, TaskCommentTest, KPIServiceTest) `[activity: run-tests]`
        - [x] T3.9.2 Run CSS build: `php userfrosting/conductor build-css --minify` — 356.18 KB, version hash a55ae177 `[activity: build]`
        - [ ] T3.9.3 Manual test manage-bin modal: Open bin, verify all 5 tabs, edit fields, save, verify grid updates. Test audit tab inactive state. Test delete with confirmation `[ref: PRD/Feature 3; SDD/Test Specs Scenario 13]` `[activity: manual-test]`
        - [ ] T3.9.4 Manual test mass edit: Select bins, test all 9 actions, verify partial failure handling (deselect succeeded, keep failed), verify double-submit prevention `[ref: PRD/Feature 4; SDD/Test Specs Scenario 3, 12]` `[activity: manual-test]`
        - [ ] T3.9.5 Manual test bin creation: Add bin, bulk create, verify grid updates, verify label print triggers `[ref: PRD/Feature 5]` `[activity: manual-test]`
        - [ ] T3.9.6 Manual test location/category management: CRUD operations, verify dropdowns update `[ref: PRD/Feature 6]` `[activity: manual-test]`
        - [ ] T3.9.7 Manual test printing: Individual + mass print, verify success/failure toasts `[ref: PRD/Feature 7]` `[activity: manual-test]`
        - [ ] T3.9.8 Test error states: Test save failure (e.g., modified bin that was deleted), mass edit with mixed success/failure, API timeout handling — verify toasts and grid consistency `[ref: SDD/Error Handling; PRD Edge Cases Scenarios 10, 12, 13]` `[activity: manual-test]`

#### Phase 3 Review Summary (Codex)

**Date**: 2026-04-13

**Findings**:
- **CRITICAL** (Fixed): `active` field normalization — API may return `1`, `"1"`, `true`, `0`, `"0"`, or `false`. `BackstockFloatingBar.update()` and `BackstockMassEdit.openHideBins()`/`openUnhideBins()` treated `"0"` as truthy, miscounting active/hidden bins. Fixed by normalizing with `String(b.active) === '1' || b.active === true`.
- **CRITICAL** (Found during review, NOT caught by Codex): Location/category DELETE used HTTP `DELETE` method, but backend routes are `POST /locations/:id/delete/` and `POST /categories/:id/delete/`. Calls would have returned 404. Fixed to use POST with correct URL paths. Also handled 405 status (bins assigned to location/category).
- **CRITICAL** (Found during review, NOT caught by Codex): Location/category CREATE sent `json=JSON.stringify({...})` convention, but backend routes expect plain form fields (`locationName`, `onSite`, `name`, `color`). Fixed to send individual form fields. Also strip `#` from color values since backend regex strips non-alphanumeric chars.
- **IMPORTANT** (Fixed): Manage Bin Syncfusion DDLs initialized on `shown.bs.modal` before `_ensureOptions()` resolved, leaving empty dropdowns on first open. Moved Syncfusion component init into the `_ensureOptions` callback chain.
- **IMPORTANT** (Fixed): Mass action response handler always called `_handleMassActionResponse()` even when `data.success === false` (without `results` object). Added guard to show error toast and bail early when success is false and no results.
- **IMPORTANT** (Fixed): `<select>` nested inside `<button>` for reprint quantity was invalid HTML. Restructured to use `d-inline-flex` wrapper with button and select as siblings.
- **IMPORTANT** (Fixed): `_handleMassActionResponse` built `failedSet` but never re-selected failed bins. Implemented actual re-selection by looking up row indices from grid dataSource and calling `grid.selectRows()`.
- **NICE-TO-HAVE** (Fixed): `BackstockConfigManager` header comment claimed "edit" support for locations/categories, but only add/delete are implemented. Updated comment to match actual scope.

**Design Notes**:
- Codex flagged location/category DELETE endpoints as inconsistent (no `storeAPI`). Investigation revealed these endpoints don't require `storeAPI` — only bin delete does. No change needed.
- Location/category edit flows are intentionally out of scope for Phase 3. Comment updated.

**Testing Gaps Noted**:
- No automated JS tests for modal lifecycle, mass action response handling, or hide/unhide filtering — these are frontend-only behaviors tested via manual testing (T3.9.3-T3.9.8) and Phase 6 E2E tests.
- active field normalization fix is difficult to unit test without a browser environment.

---

### Phase 4: Real-Time Sync (Feature 8)

Adds Ably real-time sync — both server-side publishing (PHP) and client-side subscription (JS). After this phase, multiple users see each other's changes live.

- [x] T4 Phase 4: Ably Real-Time Sync `[ref: SDD/Integration Points; PRD/Feature 8]` **COMPLETED 2026-04-14**

    - [x] T4.1 Prime Context
        - [x] T4.1.1 Read WorkbookAbly.php publish pattern `[ref: userfrosting/src/BuyerKiosk/Workbook/WorkbookAbly.php; lines: 39-97]`
        - [x] T4.1.2 Read ably-sync.js subscribe/dedup pattern `[ref: public_html/js/workspace/modules/workbook/ably-sync.js; lines: 28, 60-94, 99-146]`
        - [x] T4.1.3 Read AblyPublishThrottle.php `[ref: userfrosting/src/BuyerKiosk/Core/AblyPublishThrottle.php; lines: 45-77]`
        - [x] T4.1.4 Read backstock route mutation points for Ably injection `[ref: userfrosting/routes/groups/backstock.php; lines: 20-48 (create), 102-253 (save-all), 255-276 (delete), 395-448 (hide/activate), 869-1179 (mass ops)]`

    - [x] T4.2 Write Tests
        - [x] T4.2.1 Unit test: BackstockAbly::publish() sends correct event name and payload structure — 6 tests covering all event types `[ref: SDD/Integration Points event_names and payload_pattern]` `[activity: backend-test]`
        - [x] T4.2.2 Unit test: BackstockAbly handles Ably unavailability gracefully (non-blocking) — 2 tests (disabled + exception) `[activity: backend-test]`
        - [x] T4.2.3 Unit test: BackstockAbly::binsUpdated() sends full data for <=50 bins, IDs-only for >50 — 4 tests (small, large, exactly 50, null binsData) `[ref: SDD §Performance Characteristics "Ably payload threshold"]` `[activity: backend-test]`

    - [x] T4.3 Implement Server-Side Publishing `[component: ably-php]`
        - [x] T4.3.1 Create `userfrosting/src/BuyerKiosk/Workbook/BackstockAbly.php` — 263 lines, PSR-4 class following WorkbookAbly pattern `[ref: SDD/Application Data Models §BackstockAbly; SDD/Integration Points]` `[activity: backend-php]`
        - [x] T4.3.2 Inject Ably publish calls into 17 backstock route mutation points (15 original + 2 added during review). All feature-flag gated, non-blocking try/catch `[ref: SDD §Implementation Examples; routes/groups/backstock.php mutation points]` `[activity: backend-php]`

    - [x] T4.4 Implement Client-Side Subscription `[component: ably-js]`
        - [x] T4.4.1 Create `public_html/js/workspace/modules/backstock/BackstockAblySync.js` — 280 lines, IIFE pattern matching ably-sync.js `[ref: SDD/Application Data Models §BackstockAblySync; ably-sync.js lines 28-146]` `[activity: frontend-js]`
        - [x] T4.4.2 Wire BackstockAblySync into BackstockManager via `_initAblySync()` method — connect after grid loads, disconnect on deactivate, reconnect on re-entry. Connection indicator updates via existing `updateConnectionIndicator()` `[ref: SDD §Implementation Examples §BackstockManager Initialization]` `[activity: frontend-js]`
        - [x] T4.4.3 Wire stale-data warning via `_checkStaleWarning()` — delegates to `BackstockManageBin.showStaleDataWarning(binId)` on update/delete/hide events `[ref: SDD/Test Specs Scenario 5]` `[activity: frontend-js]`

    - [x] T4.5 Update Script Includes
        - [x] T4.5.1 Update workspace-foot.html: Added `BackstockAblySync.js` between BackstockConfigManager.js and BackstockManager.js `[activity: frontend-template]`

    - [x] T4.6 Validate Phase 4
        - [x] T4.6.1 Run unit tests: 13/13 pass, 64 assertions `[activity: run-tests]`
        - [x] T4.6.2 Run PHPStan: 0 errors on BackstockAbly.php `[activity: lint-code]`
        - [ ] T4.6.3 Manual test real-time sync: Open backstock view in 2 browser tabs. Edit a bin in tab A → verify tab B updates within 2s. Mass edit in tab A → verify tab B updates. Create bin in tab A → appears in tab B `[ref: SDD/Test Specs Scenario 4; SDD/Quality Requirements "Ably sync latency <2s"]` `[activity: manual-test]`
        - [ ] T4.6.4 Manual test stale-data warning: Open manage modal for bin in tab A, edit same bin in tab B, verify warning appears in tab A's modal `[ref: SDD/Test Specs Scenario 5]` `[activity: manual-test]`
        - [ ] T4.6.5 Manual test connection loss: Disconnect network, verify offline indicator, reconnect, verify grid refreshes `[ref: SDD/Test Specs Scenario 7]` `[activity: manual-test]`
        - [ ] T4.6.6 Verify self-echo prevention: Edit bin → own grid updates from API response, NOT from Ably echo `[activity: manual-test]`

    #### Phase 4 Review Summary (2026-04-14)

    **Reviewer**: Code review agent (feature-dev:code-reviewer)

    **Findings (8 total):**

    | # | Finding | Severity | Verdict | Action |
    |---|---------|----------|---------|--------|
    | 1 | `binCreated()` missing `$binData` payload — always triggers refetch instead of in-place add | Critical | Accepted | Fixed: Fetch via `getBinByID()` and pass to `binCreated()` |
    | 2 | `POST /bin/:bin_id` legacy edit endpoint missing Ably publish entirely | Critical | Accepted | Fixed: Added publish with `getBinByID()` for binData |
    | 3 | `POST /actions/` two paths missing `$binData` in `binUpdated()` | Critical | Accepted | Fixed: Both paths now fetch via `getBinByID()` |
    | 4 | Self-echo prevention broken (PHP never sends `instanceId`) | Important | Accepted | Fixed: Replaced dead code with explanatory comment; Grid ops are idempotent |
    | 5 | `AblyPublishThrottle` missing `class_exists()` guard | Important | Rejected | PSR-4 autoloading guarantees class availability; defensive check unnecessary |
    | 6 | Threshold hardcoded in PHP and JS (drift risk) | Important | Accepted | Fixed: Added cross-reference comments in both files |
    | 7 | DRY refactoring of 15x publish boilerplate | Nice-to-have | Deferred | Works correctly; can refactor to helper function in Phase 6 polish |
    | 8 | `null` vs `[]` binsData inconsistency | Nice-to-have | Accepted | Fixed: Changed to `!empty($binsData)` check |

    **Post-review validation**: 13/13 tests pass (64 assertions), PHPStan 0 errors, JS syntax clean, PHP syntax clean

---

### Phase 5: CSV Export + Analytics (Features 10, 11)

Adds CSV export functionality and analytics event tracking. These are enhancements to the core experience built in Phases 2-4.

- [x] T5 Phase 5: CSV Export + Analytics `[ref: PRD/Features 10-11; SDD §Detailed UI Behaviors §CSV Export Flow]` **COMPLETED 2026-04-14**

    - [x] T5.1 Prime Context
        - [x] T5.1.1 Read SDD CSV export behavior specification `[ref: SDD §Detailed UI Behaviors §CSV Export Flow; SDD/Application Data Models §CSV_EXPORT_BEHAVIOR]`
        - [x] T5.1.2 Read PRD tracking events table `[ref: PRD §Tracking Requirements]`

    - [x] T5.2 Implement CSV Export (Feature 10) `[component: csv-export]`
        - [x] T5.2.1 Add `exportCsv()` method to BackstockManager: Read grid dataSource (filtered or all based on dialog choice). If filters active, show confirmation dialog "Export X filtered bins or all Y bins?" with two buttons. Map rows to CSV columns (Bin Name, Location, On-Site Yes/No, Main Category, Other Categories semicolon-separated, Age days, Last Action). Generate Blob with UTF-8 BOM. Download as `backstock-{typeNum}-{YYYY-MM-DD}.csv` `[ref: SDD §Detailed UI Behaviors §CSV Export Flow; PRD/Feature 10 acceptance criteria]` `[activity: frontend-js]`
        - [x] T5.2.2 Export CSV button already in toolbar from Phase 2 — wired click handler to `exportCsv()` method `[activity: frontend-template]`

    - [x] T5.3 Implement Analytics Events `[component: analytics]`
        - [x] T5.3.1 Add analytics tracking calls at 8 key points across 7 files. Pattern: `console.log` + PostHog integration (matching NotificationManager pattern). Events wired: `workbook_backstock_viewed` (BackstockManager loadBins), `workbook_backstock_search` (BackstockGrid handleSearch/handleCommaSearch), `workbook_backstock_action` (BackstockMassEdit submitMassAction, BackstockManageBin saveAllChanges), `workbook_backstock_bin_created` (BackstockBinCreation add/bulk), `workbook_backstock_export` (BackstockManager _generateCsv), `workbook_backstock_error` (BackstockManager loadBins catch, BackstockMassEdit catch, BackstockManageBin catch), `workbook_backstock_flag_toggled` (configuration.html save handler), `workbook_backstock_ably_sync` (BackstockAblySync connected/failed) `[ref: PRD §Tracking Requirements; 8 events defined]` `[activity: frontend-js]`

    - [x] T5.4 Implement Summary Stats Enhancement (Feature 11 - Could Have) `[component: stats-enhancement]`
        - [x] T5.4.1 Add health score (0-100) and stale bin count (90+ days) with dynamic color coding. Health = 100 - (staleRatio*40) - (emptyRatio*30) - (agePenalty*30). Stale: success/warning/danger at 0/<20%/>20% thresholds. Health: success/warning/danger at 80/50 thresholds. "Needs Audit" deferred — audit tracking is per-store config, not available in client-side data without additional API call `[ref: PRD/Feature 11]` `[activity: frontend-js]`

    - [x] T5.5 Validate Phase 5
        - [x] T5.5.1 Unit tests: 8,291 tests, only pre-existing failures `[activity: run-tests]`
        - [x] T5.5.2 CSS build: 2dabf88d version hash `[activity: build]`
        - [x] T5.5.3 PHPStan: Only pre-existing baseline mismatch `[activity: lint-code]`
        - [x] T5.5.4 BackstockAbly tests: 13/13 pass (64 assertions) `[activity: run-tests]`
        - [ ] T5.5.5 Manual test CSV export: Export with no filters, export with category filter active (confirm dialog), verify file contents and filename format `[ref: SDD/Test Specs Scenario 8]` `[activity: manual-test]`
        - [ ] T5.5.6 Manual test analytics: Use browser DevTools to verify tracking events fire with correct properties `[activity: manual-test]`
        - [ ] T5.5.7 Verify CSV export with 500+ bins (pc00) performs acceptably `[ref: SDD/Quality Requirements]` `[activity: manual-test]`

#### Phase 5 Review Summary (2026-04-14)

**Reviewer**: Code review agent (feature-dev:code-reviewer)

**Findings (6 total):**

| # | Finding | Severity | Verdict | Action |
|---|---------|----------|---------|--------|
| 1 | Export dialog XSS — numeric values interpolated without type coercion | Critical | Accepted | Fixed: Added `parseInt()` coercion for filteredCount/allCount |
| 2 | CSV escape missing `\r` handling — carriage returns not quoted | Critical | Accepted | Fixed: Added `\r` check to `_csvEscape()` |
| 3 | Modal cleanup race condition — rapid clicks could create duplicate dialogs | Important | Accepted | Fixed: Changed from remove-then-create to return-if-exists guard |
| 4 | Health score NaN guard — `avgAge` could be NaN if data corrupted | Important | Accepted | Fixed: Added `isNaN(avgAge) ? 0 :` guard |
| 5 | Analytics missing stack traces for error events | Medium | Noted | Deferred: PostHog truncation limits make this low-value for now |
| 6 | Export dialog button order — filtered first vs all first | Medium | Rejected | Current order (filtered=primary) matches SDD spec wording |

**Post-review validation**: Unit tests pass, CSS built, PHPStan clean, all fixes applied

---

### Phase 6: Integration Testing + Polish

Final validation phase. Runs comprehensive integration tests, verifies all PRD acceptance criteria, performance benchmarks, and addresses any polish items.

- [ ] T6 Phase 6: Integration & End-to-End Validation `[ref: SDD/Test Specifications; SDD/Quality Requirements]`

    - [ ] T6.1 Automated Test Suite
        - [ ] T6.1.1 Run full test suite: `./test.sh` — all tests pass `[activity: run-tests]`
        - [ ] T6.1.2 Run PHPStan full analysis: `cd userfrosting && ./vendor/bin/phpstan analyse src/BuyerKiosk/` `[activity: lint-code]`
        - [ ] T6.1.3 Verify new BackstockAbly unit tests pass `[activity: run-tests]`
        - [ ] T6.1.4 Verify Store.php feature flag tests pass `[activity: run-tests]`

    - [ ] T6.2 E2E User Flows
        - [ ] T6.2.1 Full flow: Toggle flag ON in admin → Open workbook → Click Backstock → Search bins → Select bins → Mass edit (change location) → Verify toast → Verify grid update → Switch view away → Return → Verify data reloads `[ref: PRD §Primary User Journey]` `[activity: e2e-test]`
        - [ ] T6.2.2 Full flow: Create bin → Verify in grid → Open manage modal → Edit → Save → Print label → Delete → Confirm → Verify removed `[ref: PRD/Features 3, 5, 7; SDD/Test Specs Scenario 13]` `[activity: e2e-test]`
        - [ ] T6.2.3 Real-time sync E2E: Two browser sessions, perform operations in each, verify cross-session updates `[ref: SDD/Test Specs Scenario 4]` `[activity: e2e-test]`
        - [ ] T6.2.4 Flag-off E2E: Disable flag in admin → Verify workbook sidebar updates → Direct URL redirects → Re-enable → Works again `[ref: SDD/Test Specs Scenarios 1, 6, 11]` `[activity: e2e-test]`

    - [ ] T6.3 Performance Verification
        - [ ] T6.3.1 Grid load performance: Measure initial load with pc00 (~479 bins), verify <2s `[ref: SDD/Quality Requirements]` `[activity: performance-test]`
        - [ ] T6.3.2 Search performance: Measure search response after debounce, verify <300ms `[activity: performance-test]`
        - [ ] T6.3.3 Ably sync latency: Measure from mutation to grid update across 2 sessions, verify <2s `[activity: performance-test]`
        - [ ] T6.3.4 Feature flag isolation: With flag OFF, verify zero backstock API calls, zero JS initialization `[activity: performance-test]`

    - [ ] T6.4 Security Validation
        - [ ] T6.4.1 Verify store group isolation: User assigned to store A cannot access store B's backstock via API `[ref: SDD §Cross-Cutting Concepts "Security"]` `[activity: security-test]`
        - [ ] T6.4.2 Verify feature flag enforcement: With flag OFF, API calls from workbook context are gated (or return expected data since API itself doesn't check flag — validate UI gating is sufficient). Server-side route redirect (T1.5.1) blocks direct URL access. JS-level check (T2.7.1) blocks SPA navigation `[activity: security-test]`
        - [ ] T6.4.3 Document known limitation: API endpoints use `checkStoreGroup()` only (not `uri_backstock`), so workbook users can call backstock APIs even with flag OFF. UI-level gating is the primary control. This is a noted security hardening opportunity for future work (see PRD Codex review finding #2) `[ref: PRD §Access Control Model]` `[activity: security-test]`

    - [ ] T6.5 Edge Case Verification
        - [ ] T6.5.1 Test all 13 SDD test scenarios systematically `[ref: SDD/Test Specifications Scenarios 1-13]` `[activity: manual-test]`
        - [ ] T6.5.2 Test 0 bins empty state → create first bin → empty state disappears `[ref: SDD/Test Specs Scenario 9]` `[activity: manual-test]`
        - [ ] T6.5.3 Test all bins filtered out (search with no results) → "No records" + clear filters button `[ref: SDD §Detailed UI Behaviors §Zero Bins Empty State]` `[activity: manual-test]`

    - [ ] T6.6 PRD Acceptance Criteria Sweep
        - [ ] T6.6.1 Feature 1: All 6 acceptance criteria verified `[ref: PRD/Feature 1]` `[activity: business-acceptance]`
        - [ ] T6.6.2 Feature 2: All 13 acceptance criteria verified `[ref: PRD/Feature 2]` `[activity: business-acceptance]`
        - [ ] T6.6.3 Feature 3: All 7 acceptance criteria verified `[ref: PRD/Feature 3]` `[activity: business-acceptance]`
        - [ ] T6.6.4 Feature 4: All 11 acceptance criteria verified `[ref: PRD/Feature 4]` `[activity: business-acceptance]`
        - [ ] T6.6.5 Feature 5: All 5 acceptance criteria verified `[ref: PRD/Feature 5]` `[activity: business-acceptance]`
        - [ ] T6.6.6 Feature 6: All 5 acceptance criteria verified `[ref: PRD/Feature 6]` `[activity: business-acceptance]`
        - [ ] T6.6.7 Feature 7: All 6 acceptance criteria verified `[ref: PRD/Feature 7]` `[activity: business-acceptance]`
        - [ ] T6.6.8 Feature 8: All 5 acceptance criteria verified `[ref: PRD/Feature 8]` `[activity: business-acceptance]`
        - [ ] T6.6.9 Feature 9: All 4 acceptance criteria verified `[ref: PRD/Feature 9]` `[activity: business-acceptance]`
        - [ ] T6.6.10 Feature 10: All 3 acceptance criteria verified `[ref: PRD/Feature 10]` `[activity: business-acceptance]`

    - [ ] T6.7 Final Build + Documentation
        - [ ] T6.7.1 Run final CSS build: `php userfrosting/conductor build-css --minify` `[activity: build]`
        - [ ] T6.7.2 Verify all new files are tracked in git `[activity: build]`
        - [ ] T6.7.3 Update spec README.md: Mark implementation-plan.md as completed, update current phase `[activity: documentation]`

---

## Phase Dependency Graph

```
Phase 1 (Foundation)
    ↓
Phase 2 (Grid + Search)
    ↓
Phase 3 (Modals + Actions) ← can start T3.2-T3.6 in parallel
    ↓
Phase 4 (Ably Sync)
    ↓
Phase 5 (Export + Analytics) ← can run T5.2-T5.4 in parallel
    ↓
Phase 6 (Integration + E2E)
```

**Key dependencies:**
- Phase 2 depends on Phase 1 (needs SPA shell + flag + routing)
- Phase 3 depends on Phase 2 (needs grid for modal interactions)
- Phase 4 depends on Phase 3 (needs all modals to wire stale-data warnings; needs mutation points to inject publishes)
- Phase 5 depends on Phase 2 (needs grid for CSV export), can overlap with Phase 4
- Phase 6 depends on all prior phases

**Parallel opportunities within phases:**
- T1.3 (backend) and T1.4 (frontend) can run in parallel
- T3.2 (manage bin), T3.3 (floating bar), T3.4 (mass edit), T3.5 (bin creation), T3.6 (config management) can all be developed in parallel
- T5.2 (CSV), T5.3 (analytics), T5.4 (stats enhancement) can all run in parallel

## File Creation Summary

### New Files (16)
1. `userfrosting/migrations/input/045_001_workbook_backstock_flag.json`
2. `userfrosting/src/BuyerKiosk/Workbook/BackstockAbly.php`
3. `userfrosting/templates/themes/default/workspace/partials/backstock/backstock-view.html`
4. `userfrosting/templates/themes/default/workspace/partials/backstock/manage-bin-modal.html`
5. `userfrosting/templates/themes/default/workspace/partials/backstock/mass-edit-modals.html`
6. `userfrosting/templates/themes/default/workspace/partials/backstock/add-bin-modal.html`
7. `userfrosting/templates/themes/default/workspace/partials/backstock/bulk-create-modal.html`
8. `userfrosting/templates/themes/default/workspace/partials/backstock/locations-modal.html`
9. `userfrosting/templates/themes/default/workspace/partials/backstock/categories-modal.html`
10. `public_html/js/workspace/modules/backstock/BackstockManager.js`
11. `public_html/js/workspace/modules/backstock/BackstockGrid.js`
12. `public_html/js/workspace/modules/backstock/BackstockFloatingBar.js`
13. `public_html/js/workspace/modules/backstock/BackstockMassEdit.js`
14. `public_html/js/workspace/modules/backstock/BackstockManageBin.js`
15. `public_html/js/workspace/modules/backstock/BackstockBinCreation.js`
16. `public_html/js/workspace/modules/backstock/BackstockAblySync.js`

### Modified Files (11)
1. `userfrosting/src/BuyerKiosk/Core/Store.php` — Add property + flagMap entry + createFromRowArray
2. `userfrosting/src/BuyerKiosk/Workbook/Controllers/WorkbookPageController.php` — Pass flag to template
3. `userfrosting/src/BuyerKiosk/StoreConfig/Controllers/StoreConfigController.php` — Add flag to updateMpcSettings
4. `userfrosting/routes/workbook/pages.php` — Add backstock route + server-side flag check
5. `userfrosting/routes/groups/backstock.php` — Add Ably publish calls to mutation endpoints
6. `userfrosting/templates/themes/default/workspace/workspace.html` — Add backstockView container
7. `userfrosting/templates/themes/default/workspace/partials/sidebar-nav.html` — Add nav item
8. `userfrosting/templates/themes/default/workspace/layouts/workspace-foot.html` — Add JS includes
9. `userfrosting/templates/themes/default/store/configuration.html` — Add toggle
10. `public_html/js/workspace/modules/common/spa-navigation.js` — Add backstock view handling
11. `public_html/css/admin/modules/backstock.css` — Add workbook overrides
