Merge feat/android-mobile-ux-hardening into main (Stage 6B complete)
Publish Docs / publish-docs (push) Successful in 1m38s Details

Stage 6B: Android mobile workflow hardening

- Restore Material Design 48x48dp minimum touch targets on 12 IconButtons
  in ProductPreviewPanel by removing constraints/padding overrides
- Replace fixed-width SizedBox(width:80) on price edit TextField with
  Expanded for flexible layout on narrow mobile screens
- Add tooltip 'Edit price' for consistency with other edit buttons
- Wrap ProductPreviewPanel in SafeArea in MobileProductDetailPage to
  prevent content clipping under notches/gesture bars
- Add 6 new touch target rendered-size tests verifying >= 48x48dp
- Add .gitignore for feature_orders, untrack .dart_tool and pubspec.lock

Tests: 300/300 feature_wordpress, 14/14 kell_mobile, 24/24 kell_web
This commit is contained in:
Mike Kell 2026-05-30 10:10:09 -04:00
commit 8aec65b46b
11 changed files with 964 additions and 697 deletions

View File

@ -2,10 +2,10 @@
## Current status ## Current status
- main baseline updated through: android-feedback-polish (Stage 6A complete) - main baseline updated through: android-mobile-ux-hardening (Stage 6B complete)
- main baseline commit: merge of `feat/android-feedback-polish` (2026-05-30) - main baseline commit: merge of `feat/android-mobile-ux-hardening` (2026-05-30)
- next branch: feat/android-mobile-ux-hardening (Stage 6B) - next branch: feat/bulk-status-actions (Stage 7)
- current stage: Stage 6A complete — Stage 6B next - current stage: Stage 6 complete — Stage 7 next
## Slice tracker ## Slice tracker
@ -212,3 +212,17 @@
- tests: passed (14/14 kell_mobile) - tests: passed (14/14 kell_mobile)
- analyze: passed (dart analyze — no issues found) - analyze: passed (dart analyze — no issues found)
- brief updated: yes - brief updated: yes
### feat/android-mobile-ux-hardening
- status: ready for merge
- date: 2026-05-30
- inspection: complete
- implementation: complete
- files changed:
- `feature_wordpress/lib/src/presentation/widgets/product_preview_panel.dart` — removed `constraints: BoxConstraints()` and `padding: EdgeInsets.zero` overrides from 12 `IconButton`s (edit/save/cancel for name, price, description, category) to restore Material Design 48×48dp minimum touch targets; replaced fixed-width `SizedBox(width: 80)` on price edit `TextField` with `Expanded` for flexible layout on narrow mobile screens; added `tooltip: 'Edit price'` for consistency with other edit buttons
- `kell_mobile/lib/pages/mobile_product_detail_page.dart` — wrapped `ProductPreviewPanel` in `SafeArea` to prevent content clipping under system UI on devices with notches/gesture bars
- `feature_wordpress/test/widgets/product_preview_panel_test.dart` — added 6 new touch target tests: rendered size ≥ 48×48dp verification for edit name, edit price, edit category, edit description, save/cancel name buttons, and flexible price edit TextField width
- tests: passed (300/300 feature_wordpress, 14/14 kell_mobile, 24/24 kell_web, 41/41 design_system — 379 total)
- analyze: passed
- brief updated: yes

View File

@ -25,6 +25,20 @@ This brief should be treated as the working source of truth for planned developm
--- ---
## Platform Vision
The Kell Creations platform is a **multi-channel commerce operations system** for managing an online product catalog, conducting in-person market sales via a mobile app, taking custom orders, and tracking supplies and inventory — all synchronized across **WooCommerce** (online storefront) and **Square** (point-of-sale and payments).
### Core business workflows
1. **Online Catalog Management** — Create, edit, publish, and manage products on the WooCommerce store from both web and mobile.
2. **Market Sales (Mobile POS)** — Use the Android app at craft markets/shows to browse the catalog, process sales via Square, and capture customer information.
3. **Custom Orders** — Accept and track custom/made-to-order requests from both online and in-person channels, with specifications, deposits, and fulfillment tracking.
4. **Supplies & Inventory** — Track raw materials (supplies) and finished goods (inventory), with stock levels synchronized bidirectionally across WooCommerce and Square.
5. **Multi-Channel Sync** — Keep product catalog, inventory quantities, pricing, and order data consistent between WooCommerce and Square as the two sales channels.
---
## Working model ## Working model
Development must proceed in **small, reviewable vertical slices**. Development must proceed in **small, reviewable vertical slices**.
@ -115,10 +129,10 @@ Rules:
- `kell_mobile` tests passing - `kell_mobile` tests passing
- latest reported count for `core`: `20/20 passed` - latest reported count for `core`: `20/20 passed`
- latest reported count for `design_system`: `41/41 passed` - latest reported count for `design_system`: `41/41 passed`
- latest reported count for `feature_wordpress`: `294/294 passed` - latest reported count for `feature_wordpress`: `300/300 passed`
- latest reported count for `kell_web`: `24/24 passed` - latest reported count for `kell_web`: `24/24 passed`
- latest reported count for `kell_mobile`: `14/14 passed` - latest reported count for `kell_mobile`: `14/14 passed`
- baseline commit: merge of `feat/android-feedback-polish` (2026-05-30) - baseline commit: merge of `feat/android-mobile-ux-hardening` (2026-05-30)
#### Baseline test coverage (established 2026-05-22) #### Baseline test coverage (established 2026-05-22)
@ -134,8 +148,94 @@ No minimum thresholds are enforced — this is visibility-only tracking. Coverag
### Next recommended branch ### Next recommended branch
**`feat/android-mobile-ux-hardening`** — Stage 6B: Android mobile workflow hardening. **`feat/bulk-status-actions`** — Stage 7: Controlled bulk actions.
Branch from latest `main`. Stage 6A (Android feedback and action polish) is complete. Branch from latest `main`. All entry criteria met: single-item edit/status flows stable, multi-select groundwork complete, post-write consistency hardened, Android core publishing surface exists. Stage 6 (Android operational maturity) is complete.
---
## Senior Audit Report (2026-05-30)
### Audit scope
Full structural, feature, and build plan review conducted against the target platform vision: multi-channel commerce operations with online catalog management, mobile market sales, custom orders, supplies/inventory tracking, and WooCommerce + Square integration.
### Audit summary — strengths
| Area | Assessment |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Architecture** | Excellent C4 coverage (19 diagrams), clean package boundaries, well-documented composition pattern. The monorepo structure with shared domain/application layers is sound and extensible. |
| **Code quality** | 393 tests across 5 packages, 78.4% line coverage, `dart analyze` clean. Strict vertical-slice discipline with thorough PR tracking. |
| **WooCommerce integration** | Production-ready product publishing via REST API v3. Clean `WooCommerceApiClient` abstraction with Basic Auth, pagination, and error handling. |
| **Cross-platform** | Shared composition pattern (`KcAppServices`, `KcBootstrap`, `KcAppScope`) enables both web and mobile to reuse identical business logic. Zero logic forking for Android. |
| **Design system** | Foundation-ready with 7 shared widgets, typography, breakpoints, and 100% test coverage. |
| **CI/CD** | Forgejo Actions for analyze + test with per-package coverage reporting. |
| **Mobile app** | Android shell with 5-tab navigation, mobile-optimized publishing surface, haptic feedback, confirmation dialogs. |
### Audit summary — critical gaps for target vision
#### Gap 1: No product creation capability
The platform can only **edit existing** WooCommerce products. There is no workflow to **create new products** — neither in the domain model, repository contract, nor UI. The `ProductPublishingRepository` has no `createProduct()` method. This is a blocker for online catalog management.
**Impact:** Cannot manage a catalog without the ability to add products.
#### Gap 2: No image/media management
The `ProductDraft` model has an `imageUrl` field but it is read-only. No upload, selection, or gallery management exists. The `WooCommerceApiClient` has no media endpoints. Product images cannot be added or changed.
**Impact:** Catalog products without images have severely reduced sales conversion.
#### Gap 3: Orders are read-only with fake data
The `OrdersRepository` contract has only `getOrders()`. There is no `createOrder()`, `updateOrderStatus()`, or any write operation. The `Order` domain model lacks custom order fields (notes, customizations, deposit tracking). The repository has no real backend — only `FakeOrdersRepository`.
**Impact:** Cannot take custom orders or sync orders from WooCommerce.
#### Gap 4: Inventory is read-only with fake data
The `InventoryRepository` contract has only `getInventoryItems()`. No write operations (adjust quantity, add item, record usage). No concept of raw materials vs. finished goods. No real backend. No WooCommerce stock sync.
**Impact:** Cannot track supplies or update inventory levels. No stock accuracy across channels.
#### Gap 5: Square integration is completely absent
No Square API client, no Square SDK references, no Square-related domain models, no payment processing, no POS capability. The `integrations` package is an empty stub containing only a placeholder `Calculator` class.
**Impact:** Cannot process payments at markets. Cannot sync catalog or inventory with Square.
#### Gap 6: No multi-channel sync engine
No mechanism to synchronize products, inventory, or orders between WooCommerce and Square. No unified product ID mapping, no conflict resolution strategy, no webhook handling for external change detection.
**Impact:** Running two sales channels without sync leads to overselling and data inconsistency.
#### Gap 7: No authentication
The `auth` package is a stub. All access is uncontrolled. No user identity, no role-based access, no secure credential storage beyond `--dart-define`.
**Impact:** Cannot deploy to multiple users or secure API credentials on-device.
#### Gap 8: No offline capability
The mobile app requires network connectivity for all operations. Markets frequently have poor or no cellular coverage. No local data caching, no offline queue, no sync-on-reconnect.
**Impact:** Mobile app is unusable at markets without reliable network.
#### Gap 9: WooCommerce integration is products-only
The `WooCommerceApiClient` supports only the Products endpoint (`GET /products`, `PUT /products/{id}`). Missing: Orders API, Customers API, Categories API (CRUD), Product Images/Media API, Stock/Inventory API, Webhooks, Coupons.
**Impact:** Cannot pull orders from the online store or sync inventory levels with WooCommerce.
#### Gap 10: No financial/reporting capability
`feature_finance` is a stub. No sales reports, inventory valuation, cost tracking, or revenue analytics. No data aggregation from orders across channels.
**Impact:** No business intelligence for decision-making.
### Audit recommendations
The gaps above are addressed in the revised development roadmap below (Stages 816). The roadmap is ordered to deliver the highest-value capabilities first while respecting architectural dependencies.
--- ---
@ -150,9 +250,11 @@ These must be preserved in all future work:
- No credentials hardcoded or committed. - No credentials hardcoded or committed.
- Do **not** broaden into generic product editing prematurely. - Do **not** broaden into generic product editing prematurely.
- Keep WooCommerce details inside the WP repository layer. - Keep WooCommerce details inside the WP repository layer.
- Keep Square details inside a dedicated Square integration layer.
- Keep diffs small and reviewable. - Keep diffs small and reviewable.
- Prefer inspection-first development. - Prefer inspection-first development.
- Android support should reuse shared feature/domain/application layers rather than fork logic. - Android support should reuse shared feature/domain/application layers rather than fork logic.
- Multi-channel sync must be abstraction-first: define channel-agnostic contracts before platform-specific implementations.
--- ---
@ -310,17 +412,10 @@ Harden Android UX after the core feature surface works.
> Merged `feat/android-feedback-polish``main` (2026-05-29). > Merged `feat/android-feedback-polish``main` (2026-05-29).
> Converted `MobileProductDetailPage` to StatefulWidget with its own controller listener for local SnackBar feedback. Added confirmation dialogs for publish/move-to-draft status changes. Added haptic feedback (mediumImpact for status, lightImpact for field edits) on successful actions. Guarded list page SnackBars with `_detailPageActive` flag to prevent invisible behind-route feedback. 4 new tests added (14 total kell_mobile tests passing). > Converted `MobileProductDetailPage` to StatefulWidget with its own controller listener for local SnackBar feedback. Added confirmation dialogs for publish/move-to-draft status changes. Added haptic feedback (mediumImpact for status, lightImpact for field edits) on successful actions. Guarded list page SnackBars with `_detailPageActive` flag to prevent invisible behind-route feedback. 4 new tests added (14 total kell_mobile tests passing).
#### Stage 6B — Android mobile workflow hardening #### ~~Stage 6B — Android mobile workflow hardening~~ ✅ COMPLETE
##### Goal > Merged `feat/android-mobile-ux-hardening``main` (2026-05-30).
> Restored Material Design 48×48dp minimum touch targets on 12 `IconButton`s in `ProductPreviewPanel` by removing `constraints: BoxConstraints()` and `padding: EdgeInsets.zero` overrides. Replaced fixed-width `SizedBox(width: 80)` on price edit `TextField` with `Expanded` for flexible layout on narrow mobile screens. Added `tooltip: 'Edit price'` for consistency. Wrapped `ProductPreviewPanel` in `SafeArea` in `MobileProductDetailPage` to prevent content clipping under system UI on devices with notches/gesture bars. 6 new touch target rendered-size tests added (300 total `feature_wordpress` tests passing, 14 `kell_mobile`, 24 `kell_web`, 41 `design_system` — 379 total). Stage 6 complete.
Improve ergonomics on smaller screens.
##### Requirements
- verify scrolling/selection/edit flows
- ensure touch targets and inline edit behavior are mobile-friendly
- refine layout without changing shared domain/application contracts
--- ---
@ -356,6 +451,588 @@ Do not begin until:
--- ---
### Stage 8 — Infrastructure package activation
#### Objective
Activate the three infrastructure stub packages (`auth`, `data`, `integrations`) to provide shared foundations required by all subsequent feature work — especially multi-channel commerce, custom orders, and inventory management.
#### Key principle
These packages define **contracts and abstractions** first, with concrete implementations added incrementally. Do not build full implementations in one pass.
#### Branches
- `feat/integrations-contracts`
- `feat/data-layer-contracts`
- `feat/auth-foundation`
#### Stage 8A — Integration abstractions (`integrations` package)
##### Goal
Replace the placeholder `Calculator` class in `integrations` with shared integration contracts that formalize the API client pattern already proven in `feature_wordpress`.
##### Requirements
- Define `ApiClient` abstract class (lifecycle, auth, error handling, dispose)
- Define `ApiException` base class with status code, message, body
- Define `RetryPolicy` and `RateLimiter` abstractions
- Define `IntegrationHealthCheck` contract
- Define `WebhookHandler` contract for incoming webhook processing
- Define `ChannelAdapter` contract — the abstraction for a sales channel (WooCommerce, Square, future channels) with standard operations: products, orders, inventory, customers
- Refactor `WooCommerceApiClient` and `WooCommerceApiException` to implement the shared contracts
- Tests for all contracts
#### Stage 8B — Shared data layer (`data` package)
##### Goal
Provide shared data abstractions for local caching, offline storage, and cross-feature data patterns.
##### Requirements
- Define `LocalCache<T>` contract for in-memory and persistent caching
- Define `SyncQueue` abstraction for offline operation queuing
- Define `ConflictResolver<T>` contract for multi-channel sync conflicts
- Define `ChangeTracker<T>` for tracking local modifications pending sync
- Define `DataMapper<TLocal, TRemote>` for local ↔ remote model mapping
- Implement SQLite-backed persistent cache (using `sqflite` or `drift`)
- Tests for all abstractions
#### Stage 8C — Authentication foundation (`auth` package)
##### Goal
Provide basic authentication and secure credential storage so the app can be deployed beyond a single operator.
##### Requirements
- Define `AuthService` contract (login, logout, current user, token refresh)
- Define `CredentialStore` contract for secure storage of API keys (WooCommerce, Square)
- Implement `SecureCredentialStore` using `flutter_secure_storage`
- Define `User` and `AuthState` domain models
- Add `KC_ENV` extension: support `SQUARE` mode alongside `FAKE` and `WP`
- Update `KcAppEnvironment` enum and `KcBootstrap` to support the new mode
- Tests for auth contracts and credential store
---
### Stage 9 — WooCommerce integration expansion
#### Objective
Extend the WooCommerce integration from products-only to full commerce coverage: orders, inventory/stock, categories, images, and customers.
#### Key principle
Each API surface is added as a separate sub-stage with its own repository contract, implementation, and tests. The `WooCommerceApiClient` gains new endpoint methods; feature packages gain new repository methods.
#### Branches
- `feat/wc-orders-integration`
- `feat/wc-inventory-sync`
- `feat/wc-catalog-expansion`
#### Stage 9A — WooCommerce Orders API integration
##### Goal
Connect the orders feature to WooCommerce so orders placed online appear in the app and order status can be updated.
##### Requirements
- Add `getOrders()`, `getOrder(id)`, `updateOrderStatus()`, `createOrder()` to `WooCommerceApiClient`
- Extend `Order` domain model: add `orderNotes`, `paymentMethod`, `source` (online vs. market), `customFields` map for custom order specifications
- Extend `OrderItem` domain model: add `productId`, `variationId`, `customizations` (map for custom order specs like color, size, material)
- Add `OrderStatus` values: `onHold`, `refunded`, `failed` (WooCommerce standard statuses)
- Extend `OrdersRepository` contract: `getOrders()`, `getOrder(id)`, `createOrder()`, `updateOrderStatus()`, `addOrderNote()`
- Implement `WooCommerceOrdersRepository`
- Update `FakeOrdersRepository` with matching methods
- Add order mapper for WooCommerce JSON ↔ domain
- Tests for repository, mapper, and API client extensions
#### Stage 9B — WooCommerce stock/inventory sync
##### Goal
Enable bidirectional stock quantity synchronization between the app's inventory and WooCommerce product stock levels.
##### Requirements
- Add `getProductStock(id)`, `updateProductStock(id, quantity)`, `batchUpdateStock(updates)` to `WooCommerceApiClient`
- Extend `InventoryItem` domain model: add `wooCommerceProductId` (nullable — not all inventory items are WooCommerce products), `lastSyncedAt`, `syncStatus`
- Extend `InventoryRepository` contract: `updateQuantity()`, `adjustQuantity()`, `getItemBySku()`, `syncWithRemote()`
- Add `InventorySyncService` in application layer — orchestrates comparing local vs. remote stock and resolving differences
- Implement `WooCommerceInventoryRepository`
- Update `FakeInventoryRepository` with matching write methods
- Tests for sync service, repository, and stock update flows
#### Stage 9C — WooCommerce catalog expansion
##### Goal
Add product creation, image management, and category CRUD to complete full catalog management via WooCommerce.
##### Requirements
- Add `createProduct(data)`, `deleteProduct(id)` to `WooCommerceApiClient`
- Add `getCategories()`, `createCategory()`, `updateCategory()`, `deleteCategory()` to `WooCommerceApiClient`
- Add `uploadMedia(file)`, `getMedia(productId)`, `setProductImages(productId, mediaIds)` to `WooCommerceApiClient`
- Extend `ProductPublishingRepository`: `createProduct()`, `deleteProduct()`, `updateProductImages()`, `getCategories()`, `createCategory()`
- Add `ProductCategory` domain model (id, name, slug, parent, count)
- Add `ProductImage` domain model (id, url, alt, position)
- Add product creation UI — form with name, description, price, category, SKU, images
- Add image picker/upload UI for mobile and web
- Add category management page
- Tests for all new repository methods, use cases, and UI
---
### Stage 10 — Square integration foundation
#### Objective
Introduce Square as a second sales channel, enabling in-person payment processing and catalog/inventory synchronization.
#### Key principle
Square integration must follow the same architectural pattern as WooCommerce: API client in a data layer, repository contracts in domain layer, and runtime selection via `--dart-define`. The `integrations` package `ChannelAdapter` contract (Stage 8A) provides the abstraction both channels implement.
#### Branches
- `feat/square-api-client`
- `feat/square-catalog-sync`
- `feat/square-payments`
#### Stage 10A — Square API client and authentication
##### Goal
Create the foundational Square API client with OAuth 2.0 authentication.
##### Requirements
- Add `KC_SQUARE_ACCESS_TOKEN`, `KC_SQUARE_LOCATION_ID`, `KC_SQUARE_ENVIRONMENT` to `--dart-define` keys
- Create `SquareApiClient` implementing shared `ApiClient` contract from `integrations` package
- Implement Square OAuth 2.0 flow (or access token injection for initial development)
- Implement Square Catalog API: `listCatalogItems()`, `upsertCatalogItem()`, `deleteCatalogItem()`
- Implement Square Inventory API: `getInventoryCount()`, `batchChangeInventory()`
- Implement Square Orders API: `createOrder()`, `listOrders()`, `getOrder()`
- Implement Square Payments API: `createPayment()`, `getPayment()`
- Add `SquareApiException` extending shared `ApiException`
- Update `KcAppEnvironment` to support `square` and `multi` (both channels) modes
- Tests for API client, auth flow, and all endpoints
#### Stage 10B — Square catalog and inventory sync
##### Goal
Synchronize the product catalog and inventory levels between the app and Square.
##### Requirements
- Create `SquareProductMapper` for Square CatalogObject ↔ `ProductDraft` mapping
- Create `SquareInventoryMapper` for Square InventoryCount ↔ `InventoryItem` mapping
- Create `SquareCatalogRepository` implementing `ProductPublishingRepository` (or a shared catalog contract)
- Create `SquareInventoryRepository` implementing `InventoryRepository`
- Create `ProductIdMapping` domain model — maps product IDs across WooCommerce and Square
- Create `ProductIdMappingRepository` for persisting cross-channel ID mappings
- Update `FakeProductPublishingRepository` and `FakeInventoryRepository` with matching operations
- Tests for mappers, repositories, and ID mapping
#### Stage 10C — Square payment processing (mobile POS)
##### Goal
Enable the Android app to accept payments via Square at craft markets.
##### Requirements
- Integrate Square Mobile Payments SDK (or Square Reader SDK) for Android
- Create `PaymentService` abstraction in application layer
- Create `PaymentResult` domain model (amount, method, transactionId, receiptUrl)
- Create `MobileCheckoutPage` — product selection → cart → payment flow
- Support cash recording (manual entry, no Square hardware needed)
- Support card payment via Square Reader
- Generate digital receipt (or link to Square receipt)
- After payment: create order record, deduct inventory
- Add `Sales` tab to mobile navigation (replaces or supplements current Products tab for market mode)
- Tests for payment service, checkout flow, and order creation
---
### Stage 11 — Custom orders workflow
#### Objective
Enable operators to accept custom/made-to-order requests from both online and in-person channels, track specifications, manage deposits, and fulfill orders.
#### Key principle
Custom orders extend the existing `Order` domain model with additional fields rather than creating a parallel system. Custom orders flow through the same order management pipeline.
#### Branches
- `feat/custom-order-domain`
- `feat/custom-order-ui`
- `feat/custom-order-tracking`
#### Stage 11A — Custom order domain and data
##### Goal
Extend the order domain model and repository to support custom order creation with specifications.
##### Requirements
- Add `OrderType` enum: `standard`, `custom`
- Add `CustomOrderSpec` value object: material, color, dimensions, special instructions, reference images, estimated completion date
- Add `DepositInfo` value object: depositAmount, depositPaid, depositDate, balanceDue
- Extend `Order` model: `orderType`, `customSpec` (nullable), `depositInfo` (nullable), `estimatedCompletionDate`, `internalNotes`
- Extend `OrdersRepository`: `createCustomOrder()`, `updateCustomOrderSpec()`, `recordDeposit()`, `updateEstimatedCompletion()`
- Implement in both `FakeOrdersRepository` and `WooCommerceOrdersRepository` (WooCommerce custom orders use order meta fields)
- Tests for domain models and repository methods
#### Stage 11B — Custom order creation UI
##### Goal
Provide mobile and web interfaces for creating custom orders, especially at markets.
##### Requirements
- Create `CustomOrderFormPage` (shared or platform-specific presentation)
- Customer information capture (name, email, phone)
- Product base selection (from catalog) or free-text description
- Customization fields: material, color, size/dimensions, special instructions
- Reference image capture (camera on mobile, file picker on web)
- Pricing: estimated price, deposit amount, deposit payment method
- Estimated completion date picker
- Create `CustomOrderConfirmationPage` — summary with digital receipt for deposit
- Wire into mobile shell navigation and web routing
- Tests for form validation, order creation flow
#### Stage 11C — Custom order tracking and fulfillment
##### Goal
Provide order tracking dashboard and status management for custom orders.
##### Requirements
- Create `CustomOrderTrackingPage` — filtered view of custom orders by status
- Add status progression: `quoted``depositReceived``inProduction``qualityCheck``readyForPickup``completed`
- Add notification triggers (future: email/SMS when status changes)
- Add order history/timeline view showing all status changes with timestamps
- Link custom order completion to inventory deduction (materials used)
- Tests for tracking UI and status progression
---
### Stage 12 — Inventory and supplies management
#### Objective
Transform the inventory feature from a read-only fake-data display into a full supplies and inventory management system with write operations, supply tracking, and multi-channel stock sync.
#### Key principle
Distinguish between **raw materials/supplies** (inputs to production) and **finished goods** (products for sale). Both are tracked in the inventory system but have different lifecycles.
#### Branches
- `feat/inventory-write-ops`
- `feat/supplies-tracking`
- `feat/inventory-sync-engine`
#### Stage 12A — Inventory write operations and domain expansion
##### Goal
Add write operations to inventory and expand the domain model for real-world tracking.
##### Requirements
- Add `ItemType` enum: `rawMaterial`, `finishedGood`, `packaging`, `tool`
- Extend `InventoryItem`: `itemType`, `reorderPoint`, `reorderQuantity`, `supplier`, `costPerUnit`, `location`, `lastCountDate`, `notes`
- Add `InventoryAdjustment` value object: `itemId`, `adjustmentType` (received, used, damaged, returned, counted), `quantity`, `reason`, `timestamp`, `operator`
- Extend `InventoryRepository`: `addItem()`, `updateItem()`, `adjustQuantity()`, `getAdjustmentHistory()`, `getLowStockItems()`, `getItemsByType()`
- Implement write operations in `FakeInventoryRepository`
- Add `InventoryController` (ChangeNotifier) for state management — mirrors the pattern from `ProductPublishingController`
- Tests for all new domain models, repository methods, and controller
#### Stage 12B — Supplies tracking
##### Goal
Add supply-specific workflows: receiving shipments, recording material usage in production, and reorder alerts.
##### Requirements
- Add `Supplier` domain model: name, contact, leadTime, minimumOrder
- Add `PurchaseOrder` domain model: supplier, items, orderDate, expectedDelivery, status, totalCost
- Add `SupplierRepository` contract and fake implementation
- Add `PurchaseOrderRepository` contract and fake implementation
- Create supply receiving workflow: record incoming shipments → auto-adjust inventory
- Create material usage workflow: when fulfilling orders (standard or custom) → deduct raw materials
- Add low-stock alert system: surface items below reorder point on dashboard
- Create `SuppliesPage` UI (list, filter by type, quick-adjust, reorder alerts)
- Tests for supply workflows, reorder logic, and UI
#### Stage 12C — Multi-channel inventory sync engine
##### Goal
Keep inventory quantities consistent across the local app, WooCommerce, and Square.
##### Requirements
- Create `InventorySyncEngine` in application layer:
- Poll or webhook-triggered sync from WooCommerce and Square
- Conflict resolution: last-write-wins with operator override option
- Sync status tracking per item (synced, pending, conflict, error)
- Batch sync support for efficiency
- Create `ChannelStockMapping` — maps inventory item IDs to WooCommerce product IDs and Square catalog item IDs
- Implement `WooCommerceStockSync` using Stage 9B foundations
- Implement `SquareStockSync` using Stage 10B foundations
- Add sync status indicators to inventory UI
- Add manual sync trigger and conflict resolution UI
- Tests for sync engine, conflict resolution, and channel adapters
---
### Stage 13 — Multi-channel sync and unified operations
#### Objective
Provide a unified view across WooCommerce and Square as two sales channels, with consistent product catalog, pricing, and order visibility.
#### Key principle
The sync engine is event-driven where possible (webhooks) and poll-based as fallback. The app is the source of truth for catalog data; channels are synchronized outward.
#### Branches
- `feat/unified-catalog-sync`
- `feat/unified-order-view`
- `feat/webhook-integration`
#### Stage 13A — Unified catalog sync
##### Goal
Synchronize the product catalog bidirectionally between WooCommerce and Square, with the app as the authoritative source.
##### Requirements
- Create `CatalogSyncService` orchestrating WooCommerce ↔ app ↔ Square product sync
- Product changes in the app push to both channels
- Product changes detected from channels pull into the app (with conflict detection)
- Handle field mapping differences between platforms (e.g., WooCommerce categories vs. Square categories)
- Handle price sync across channels (same price or channel-specific pricing)
- Create `SyncDashboard` widget showing last sync time, pending changes, errors per channel
- Tests for sync service, conflict detection, and field mapping
#### Stage 13B — Unified order view
##### Goal
Aggregate orders from all channels (WooCommerce online, Square in-person, custom orders) into a single order management interface.
##### Requirements
- Add `OrderChannel` enum: `woocommerce`, `square`, `manual`
- Extend `Order` model with `channel` field
- Create `UnifiedOrdersController` that merges orders from all repositories
- Create unified orders page showing all orders with channel indicator and filters
- Order detail page shows channel-specific information
- Tests for unified controller and merged order display
#### Stage 13C — Webhook integration (stretch)
##### Goal
Set up real-time change detection from WooCommerce and Square via webhooks.
##### Requirements
- Define `WebhookHandler` endpoints for WooCommerce webhooks (order created, product updated, stock changed)
- Define `WebhookHandler` endpoints for Square webhooks (payment completed, inventory changed)
- Requires a lightweight backend/proxy (n8n workflow or serverless function) to receive webhooks and forward to the app
- Alternatively: implement polling with configurable intervals as interim solution
- Tests for webhook payload parsing and event routing
---
### Stage 14 — Offline capability and field operations
#### Objective
Make the mobile app usable at markets with poor or no network connectivity.
#### Key principle
Offline-first for critical market operations (viewing catalog, recording sales, capturing custom orders). Sync when connectivity is available.
#### Branches
- `feat/offline-data-cache`
- `feat/offline-sales-queue`
- `feat/barcode-scanning`
#### Stage 14A — Offline data cache
##### Goal
Cache product catalog and inventory data locally so the mobile app can display products and check stock without network.
##### Requirements
- Implement `LocalCache<T>` from `data` package (Stage 8B) using SQLite
- Cache product catalog, inventory items, and categories on successful fetch
- Detect offline state and serve from cache with "offline" indicator
- Cache invalidation strategy: time-based + manual refresh
- Pre-market sync: "Prepare for Market" action that forces a full cache refresh
- Tests for cache storage, retrieval, offline detection, and invalidation
#### Stage 14B — Offline sales queue
##### Goal
Queue sales transactions made offline and sync them when connectivity returns.
##### Requirements
- Implement `SyncQueue` from `data` package (Stage 8B)
- Queue offline sales with all order details (items, quantities, payment method, customer)
- Queue offline inventory adjustments
- Queue offline custom order captures
- Auto-sync on connectivity restoration with progress indicator
- Handle sync conflicts (e.g., item sold out in both channels during offline period)
- Manual sync trigger with error reporting
- Tests for queue operations, sync flow, and conflict handling
#### Stage 14C — Barcode/SKU scanning (stretch)
##### Goal
Enable quick product lookup via barcode scanning on the mobile app.
##### Requirements
- Integrate camera-based barcode scanning (using `mobile_scanner` or similar package)
- Scan → product lookup by SKU → add to cart or view details
- Support standard product barcodes and QR codes
- SKU label printing integration (stretch)
- Tests for scan → lookup flow
---
### Stage 15 — Reporting and business intelligence
#### Objective
Activate the `feature_finance` package to provide sales reporting, inventory valuation, and basic business analytics.
#### Key principle
Reports aggregate data from orders, inventory, and product repositories across all channels. Start with simple aggregations before adding charts or advanced analytics.
#### Branches
- `feat/sales-reporting`
- `feat/inventory-reporting`
- `feat/financial-dashboard`
#### Stage 15A — Sales reporting
##### Goal
Provide daily, weekly, and monthly sales summaries across all channels.
##### Requirements
- Sales by channel (WooCommerce, Square, custom orders)
- Sales by product/category
- Sales by time period with comparison to previous period
- Top-selling products
- Average order value
- Revenue tracking
- Exportable reports (CSV)
- Tests for aggregation logic
#### Stage 15B — Inventory valuation and reporting
##### Goal
Provide inventory value, turnover, and supply usage reports.
##### Requirements
- Current inventory value (cost and retail)
- Inventory turnover rate
- Low stock alerts summary
- Supply usage history
- Reorder recommendations based on usage trends
- Tests for valuation calculations
#### Stage 15C — Financial dashboard
##### Goal
Create a unified financial dashboard combining sales, costs, and margins.
##### Requirements
- Revenue vs. cost of goods
- Gross margin by product and category
- Market event profitability (sales at specific markets)
- Period-over-period trends
- Wire into existing dashboard page as additional summary cards
- Tests for dashboard aggregations
---
### Stage 16 — Production readiness
#### Objective
Harden the platform for production deployment beyond a single operator.
#### Branches
- `feat/error-tracking`
- `feat/secure-deployment`
#### Stage 16A — Error tracking and observability
##### Goal
Add crash reporting, error tracking, and basic usage analytics.
##### Requirements
- Integrate lightweight crash reporting (e.g., Sentry)
- Add structured logging for API calls, sync operations, and errors
- Add basic usage analytics (feature usage frequency, sync success rates)
- Add health check dashboard showing integration status per channel
#### Stage 16B — Secure deployment configuration
##### Goal
Move beyond `--dart-define` for credential management in production.
##### Requirements
- Secure credential storage on device (via `auth` package Stage 8C)
- First-run setup wizard for entering API credentials
- Connection testing UI (verify WooCommerce and Square connectivity)
- Environment-specific configuration (dev/staging/production)
---
## Suggested improvements (identified from codebase analysis) ## Suggested improvements (identified from codebase analysis)
This section captures structural improvements, coverage gaps, and maturity enhancements identified through codebase analysis against the architecture documentation and current implementation state. Items here are candidates for future stages or can be woven into existing stages as appropriate. This section captures structural improvements, coverage gaps, and maturity enhancements identified through codebase analysis against the architecture documentation and current implementation state. Items here are candidates for future stages or can be woven into existing stages as appropriate.
@ -373,7 +1050,7 @@ The following packages exist as scaffolded stubs with no implementation. Each ne
| `feature_mrp` | Craft Manufacturing / MRP Components | Low | Architecture docs exist but no implementation. Defer until inventory workflows mature. | | `feature_mrp` | Craft Manufacturing / MRP Components | Low | Architecture docs exist but no implementation. Defer until inventory workflows mature. |
| `feature_social` | Social Media Management Components | Low | Architecture docs exist but no implementation. Defer until core platform stabilizes. | | `feature_social` | Social Media Management Components | Low | Architecture docs exist but no implementation. Defer until core platform stabilizes. |
**Recommendation:** Add a **Stage 8 — Infrastructure package activation** to the roadmap covering `auth`, `data`, and `integrations` before expanding into new feature domains. **Recommendation:** Addressed in **Stage 8 — Infrastructure package activation**. Auth, data, and integrations are activated before expanding into new feature domains.
### ~~2. Design system expansion~~ ✅ RESOLVED ### ~~2. Design system expansion~~ ✅ RESOLVED
@ -413,7 +1090,7 @@ The following packages exist as scaffolded stubs with no implementation. Each ne
Architecture documentation (`traceability-index.md` coverage gaps) identifies "Product image / media synchronization workflow" as a gap. The current publishing workflow handles text fields only. Architecture documentation (`traceability-index.md` coverage gaps) identifies "Product image / media synchronization workflow" as a gap. The current publishing workflow handles text fields only.
**Recommendation:** Plan a future stage for image/media management once existing narrow edits are stable on both platforms. This aligns with the architecture gap analysis. **Recommendation:** Addressed in **Stage 9C — WooCommerce catalog expansion** which includes image upload, gallery management, and product image assignment via the WooCommerce Media API.
### 8. Error tracking and observability ### 8. Error tracking and observability
@ -423,7 +1100,7 @@ No monitoring, error tracking, or analytics strategy exists for the Flutter appl
- No crash reporting - No crash reporting
- No usage analytics to guide prioritization - No usage analytics to guide prioritization
**Recommendation:** Add lightweight error reporting and observability as a future stage, particularly important before any production WP-mode deployment beyond a single operator. **Recommendation:** Addressed in **Stage 16A — Error tracking and observability**.
### 9. ~~Empty tools directory~~ ✅ RESOLVED ### 9. ~~Empty tools directory~~ ✅ RESOLVED
@ -475,6 +1152,34 @@ The Architecture Decision Record (ADR) index exists but contained no entries. Se
**Resolution:** ✅ Populated 2026-05-22 during full project analysis. Seven candidate ADRs documented with status, context, decision, and consequences. **Resolution:** ✅ Populated 2026-05-22 during full project analysis. Seven candidate ADRs documented with status, context, decision, and consequences.
### 17. Square integration not represented in architecture
The architecture documentation (system landscape, integration architecture, deployment architecture) makes no mention of Square. Square needs to be added as an external system in the system landscape and as an integration target in the enterprise integration architecture.
**Recommendation:** Update architecture diagrams before or during Stage 10 (Square integration foundation):
- Add Square to `system-landscape.puml` as an external system
- Add Square to `enterprise-integration-orchestration-architecture.puml`
- Add Square to `context-kellcreations-platform.puml`
- Create a new dynamic workflow: `dynamic-market-sale-checkout.puml`
### 18. No multi-channel data model in architecture
The current architecture documents describe a single-channel model (WordPress/WooCommerce). The multi-channel reality (WooCommerce + Square) requires:
- A unified product ID mapping concept
- Cross-channel inventory reconciliation
- Multi-source order aggregation
- Channel-specific vs. channel-agnostic data separation
**Recommendation:** Add `enterprise-multi-channel-architecture.puml` capturing the channel adapter pattern, product ID mapping, sync engine, and conflict resolution strategy. This should be created during Stage 13 planning.
### 19. `feature_mrp` activation alignment
The `feature_mrp` (Craft Manufacturing/MRP) package is architecturally described but stubbed. Custom orders (Stage 11) and supplies tracking (Stage 12B) together provide the foundation for basic manufacturing planning. The MRP package should be evaluated for activation after Stage 12 to determine if its scope is already covered by custom orders + inventory or if it adds distinct value (e.g., production scheduling, work orders, bill of materials).
**Recommendation:** Re-evaluate `feature_mrp` scope after Stage 12 completion. It may be partially or fully absorbed by custom order fulfillment and supplies tracking.
--- ---
## Required development workflow for every slice ## Required development workflow for every slice
@ -512,6 +1217,8 @@ Constraints:
- No credentials hardcoded or committed. - No credentials hardcoded or committed.
- Do not broaden into generic product editing prematurely. - Do not broaden into generic product editing prematurely.
- Keep WooCommerce details inside the WP repository layer. - Keep WooCommerce details inside the WP repository layer.
- Keep Square details inside a dedicated Square integration layer.
- Multi-channel sync contracts before implementations.
- Use small, reviewable steps only. - Use small, reviewable steps only.
- Work is being driven through Cline in Windows 11 VS Code. - Work is being driven through Cline in Windows 11 VS Code.
@ -528,13 +1235,13 @@ Working rules:
--- ---
## Appendix: Feature maturity matrix ## Appendix A: Feature maturity matrix
| Package | Domain Layer | Application Layer | Data (Fake) | Data (Real) | Presentation | Tests | Maturity | | Package | Domain Layer | Application Layer | Data (Fake) | Data (Real) | Presentation | Tests | Maturity |
| ------------------- | ------------ | ----------------- | ----------- | -------------- | -------------------------------------------------- | ------- | -------------------- | | ------------------- | ------------ | ----------------- | ------------ | -------------- | -------------------------------------------------- | ------- | -------------------- |
| `feature_wordpress` | ✅ Complete | ✅ Complete | ✅ Complete | ✅ WooCommerce | ✅ Complete | 294 | **Production-ready** | | `feature_wordpress` | ✅ Complete | ✅ Complete | ✅ Complete | ✅ WooCommerce | ✅ Complete | 294 | **Production-ready** |
| `feature_inventory` | ✅ Complete | ✅ Complete | ✅ Complete | ❌ None | ✅ Complete | Minimal | **Fake-only MVP** | | `feature_inventory` | ⚠️ Read-only | ⚠️ Read-only | ⚠️ Read-only | ❌ None | ⚠️ Read-only | Minimal | **Fake-only MVP** |
| `feature_orders` | ✅ Complete | ✅ Complete | ✅ Complete | ❌ None | ✅ Complete | Some | **Fake-only MVP** | | `feature_orders` | ⚠️ Read-only | ⚠️ Read-only | ⚠️ Read-only | ❌ None | ⚠️ Read-only | Some | **Fake-only MVP** |
| `feature_policy` | ✅ Complete | ✅ Complete | ✅ Complete | ❌ None | ✅ Complete | Minimal | **Fake-only MVP** | | `feature_policy` | ✅ Complete | ✅ Complete | ✅ Complete | ❌ None | ✅ Complete | Minimal | **Fake-only MVP** |
| `feature_finance` | ❌ Stub | ❌ Stub | ❌ Stub | ❌ None | ❌ Stub | None | **Scaffolded only** | | `feature_finance` | ❌ Stub | ❌ Stub | ❌ Stub | ❌ None | ❌ Stub | None | **Scaffolded only** |
| `feature_mrp` | ❌ Stub | ❌ Stub | ❌ Stub | ❌ None | ❌ Stub | None | **Scaffolded only** | | `feature_mrp` | ❌ Stub | ❌ Stub | ❌ Stub | ❌ None | ❌ Stub | None | **Scaffolded only** |
@ -544,3 +1251,91 @@ Working rules:
| `integrations` | ❌ Stub | N/A | N/A | ❌ None | N/A | None | **Scaffolded only** | | `integrations` | ❌ Stub | N/A | N/A | ❌ None | N/A | None | **Scaffolded only** |
| `design_system` | N/A | N/A | N/A | N/A | ✅ Expanded (theme, typography, layout, 7 widgets) | 41 | **Foundation ready** | | `design_system` | N/A | N/A | N/A | N/A | ✅ Expanded (theme, typography, layout, 7 widgets) | 41 | **Foundation ready** |
| `core` | ✅ Partial | ✅ Composition | N/A | N/A | N/A | 20 | **Foundation ready** | | `core` | ✅ Partial | ✅ Composition | N/A | N/A | N/A | 20 | **Foundation ready** |
### Key changes from previous matrix
- `feature_inventory` and `feature_orders` downgraded from "Complete" to "⚠️ Read-only" in domain/application/data/presentation to reflect that they have no write operations — a critical gap for the target platform vision.
- Feature maturity labels clarified: "Fake-only MVP" packages that are also read-only now carry the ⚠️ indicator.
---
## Appendix B: Integration landscape
| Integration | Package/Location | Protocol | Current Status | Target Stage |
| -------------------- | ------------------- | ------------- | ------------------- | ------------ |
| WooCommerce Products | `feature_wordpress` | REST API v3 | ✅ Production-ready | — |
| WooCommerce Orders | `feature_wordpress` | REST API v3 | ❌ Not implemented | Stage 9A |
| WooCommerce Stock | `feature_wordpress` | REST API v3 | ❌ Not implemented | Stage 9B |
| WooCommerce Catalog | `feature_wordpress` | REST API v3 | ⚠️ Read + edit only | Stage 9C |
| WooCommerce Media | `feature_wordpress` | REST API v3 | ❌ Not implemented | Stage 9C |
| Square Catalog | (new) | Square API v2 | ❌ Not implemented | Stage 10A/B |
| Square Inventory | (new) | Square API v2 | ❌ Not implemented | Stage 10B |
| Square Payments | (new) | Mobile SDK | ❌ Not implemented | Stage 10C |
| Square Orders | (new) | Square API v2 | ❌ Not implemented | Stage 10A |
| Facebook API | `feature_social` | Graph API | ❌ Stub only | Deferred |
| Instagram API | `feature_social` | Graph API | ❌ Stub only | Deferred |
| X (Twitter) API | `feature_social` | REST API v2 | ❌ Stub only | Deferred |
| n8n Workflows | (external) | Webhooks/REST | ❌ Not implemented | Stage 13C |
| Mail Server | (external) | SMTP | ❌ Not implemented | Deferred |
---
## Appendix C: Runtime configuration keys
### Current
| Key | Description | Default | Used By |
| ----------------------- | ------------------------------------ | ------- | ------- |
| `KC_ENV` | `fake`, `wordpress` | `fake` | All |
| `KC_WC_SITE_URL` | WordPress site URL | (empty) | WP mode |
| `KC_WC_CONSUMER_KEY` | WooCommerce REST API consumer key | (empty) | WP mode |
| `KC_WC_CONSUMER_SECRET` | WooCommerce REST API consumer secret | (empty) | WP mode |
### Planned (Stage 8+)
| Key | Description | Default | Target Stage |
| ------------------------ | ------------------------- | --------- | ------------ |
| `KC_SQUARE_ACCESS_TOKEN` | Square API access token | (empty) | Stage 10A |
| `KC_SQUARE_LOCATION_ID` | Square location ID | (empty) | Stage 10A |
| `KC_SQUARE_ENVIRONMENT` | `sandbox` or `production` | `sandbox` | Stage 10A |
### Planned `KC_ENV` values
| Value | Description | Target Stage |
| ----------- | -------------------------------- | ------------ |
| `fake` | All fake repositories (current) | — |
| `wordpress` | WooCommerce real + others fake | — |
| `square` | Square real + others fake | Stage 10A |
| `multi` | Both WooCommerce and Square real | Stage 13A |
---
## Appendix D: Stage dependency graph
```
Stages 16 (COMPLETE/IN PROGRESS) — Foundation
├── Stage 7 — Bulk actions (independent, can proceed in parallel)
└── Stage 8 — Infrastructure activation (PREREQUISITE for 916)
├── Stage 9 — WooCommerce expansion
│ │
│ ├── Stage 11 — Custom orders (depends on 9A orders API)
│ │
│ └── Stage 12 — Inventory management (depends on 9B stock sync)
│ │
│ └── Stage 12C — Multi-channel inventory sync (depends on 10B)
├── Stage 10 — Square integration
│ │
│ └── Stage 10C — Mobile POS (depends on 10A + 10B)
├── Stage 13 — Multi-channel sync (depends on 9 + 10 + 12C)
├── Stage 14 — Offline capability (depends on 8B data layer)
├── Stage 15 — Reporting (depends on 9A + 10 + 12)
└── Stage 16 — Production readiness (depends on 8C auth)
```

View File

@ -155,7 +155,8 @@ class _MobileProductDetailPageState extends State<MobileProductDetailPage> {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(draft.name)), appBar: AppBar(title: Text(draft.name)),
body: Padding( body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: ProductPreviewPanel( child: ProductPreviewPanel(
draft: draft, draft: draft,
@ -168,6 +169,7 @@ class _MobileProductDetailPageState extends State<MobileProductDetailPage> {
onCategoryChanged: (cat) => _controller.updateCategory(draft.id, cat), onCategoryChanged: (cat) => _controller.updateCategory(draft.id, cat),
), ),
), ),
),
); );
}, },
); );

View File

@ -1 +0,0 @@
{"version":2,"entries":[{"package":"design_system","rootUri":"../../design_system/","packageUri":"lib/"},{"package":"feature_orders","rootUri":"../","packageUri":"lib/"}]}

View File

@ -1,178 +0,0 @@
{
"configVersion": 2,
"packages": [
{
"name": "async",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/async-2.13.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "boolean_selector",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "characters",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/characters-1.4.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "clock",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/clock-1.1.2",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "collection",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/collection-1.19.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "design_system",
"rootUri": "../../design_system",
"packageUri": "lib/",
"languageVersion": "3.11"
},
{
"name": "fake_async",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/fake_async-1.3.3",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "flutter",
"rootUri": "file:///D:/develop/flutter/packages/flutter",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "flutter_lints",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_lints-6.0.0",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "flutter_test",
"rootUri": "file:///D:/develop/flutter/packages/flutter_test",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "leak_tracker",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker-11.0.2",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "leak_tracker_flutter_testing",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "leak_tracker_testing",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_testing-3.0.2",
"packageUri": "lib/",
"languageVersion": "3.2"
},
{
"name": "lints",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/lints-6.1.0",
"packageUri": "lib/",
"languageVersion": "3.8"
},
{
"name": "matcher",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/matcher-0.12.19",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "material_color_utilities",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/material_color_utilities-0.13.0",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "meta",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/meta-1.17.0",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "path",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/path-1.9.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "sky_engine",
"rootUri": "file:///D:/develop/flutter/bin/cache/pkg/sky_engine",
"packageUri": "lib/",
"languageVersion": "3.9"
},
{
"name": "source_span",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/source_span-1.10.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "stack_trace",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/stack_trace-1.12.1",
"packageUri": "lib/",
"languageVersion": "3.4"
},
{
"name": "stream_channel",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/stream_channel-2.1.4",
"packageUri": "lib/",
"languageVersion": "3.3"
},
{
"name": "string_scanner",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/string_scanner-1.4.1",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "term_glyph",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/term_glyph-1.2.2",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "test_api",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/test_api-0.7.10",
"packageUri": "lib/",
"languageVersion": "3.7"
},
{
"name": "vector_math",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/vector_math-2.2.0",
"packageUri": "lib/",
"languageVersion": "3.1"
},
{
"name": "vm_service",
"rootUri": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache/hosted/pub.dev/vm_service-15.0.2",
"packageUri": "lib/",
"languageVersion": "3.5"
},
{
"name": "feature_orders",
"rootUri": "../",
"packageUri": "lib/",
"languageVersion": "3.11"
}
],
"generator": "pub",
"generatorVersion": "3.11.4",
"flutterRoot": "file:///D:/develop/flutter",
"flutterVersion": "3.41.6",
"pubCache": "file:///C:/Users/mtkel/AppData/Local/Pub/Cache"
}

View File

@ -1,232 +0,0 @@
{
"roots": [
"feature_orders"
],
"packages": [
{
"name": "feature_orders",
"version": "0.0.1",
"dependencies": [
"design_system",
"flutter"
],
"devDependencies": [
"flutter_lints",
"flutter_test"
]
},
{
"name": "flutter_lints",
"version": "6.0.0",
"dependencies": [
"lints"
]
},
{
"name": "flutter_test",
"version": "0.0.0",
"dependencies": [
"clock",
"collection",
"fake_async",
"flutter",
"leak_tracker_flutter_testing",
"matcher",
"meta",
"path",
"stack_trace",
"stream_channel",
"test_api",
"vector_math"
]
},
{
"name": "design_system",
"version": "0.0.1",
"dependencies": [
"flutter"
]
},
{
"name": "flutter",
"version": "0.0.0",
"dependencies": [
"characters",
"collection",
"material_color_utilities",
"meta",
"sky_engine",
"vector_math"
]
},
{
"name": "lints",
"version": "6.1.0",
"dependencies": []
},
{
"name": "stream_channel",
"version": "2.1.4",
"dependencies": [
"async"
]
},
{
"name": "meta",
"version": "1.17.0",
"dependencies": []
},
{
"name": "collection",
"version": "1.19.1",
"dependencies": []
},
{
"name": "leak_tracker_flutter_testing",
"version": "3.0.10",
"dependencies": [
"flutter",
"leak_tracker",
"leak_tracker_testing",
"matcher",
"meta"
]
},
{
"name": "vector_math",
"version": "2.2.0",
"dependencies": []
},
{
"name": "stack_trace",
"version": "1.12.1",
"dependencies": [
"path"
]
},
{
"name": "clock",
"version": "1.1.2",
"dependencies": []
},
{
"name": "fake_async",
"version": "1.3.3",
"dependencies": [
"clock",
"collection"
]
},
{
"name": "path",
"version": "1.9.1",
"dependencies": []
},
{
"name": "matcher",
"version": "0.12.19",
"dependencies": [
"async",
"meta",
"stack_trace",
"term_glyph",
"test_api"
]
},
{
"name": "test_api",
"version": "0.7.10",
"dependencies": [
"async",
"boolean_selector",
"collection",
"meta",
"source_span",
"stack_trace",
"stream_channel",
"string_scanner",
"term_glyph"
]
},
{
"name": "sky_engine",
"version": "0.0.0",
"dependencies": []
},
{
"name": "material_color_utilities",
"version": "0.13.0",
"dependencies": [
"collection"
]
},
{
"name": "characters",
"version": "1.4.1",
"dependencies": []
},
{
"name": "async",
"version": "2.13.1",
"dependencies": [
"collection",
"meta"
]
},
{
"name": "leak_tracker_testing",
"version": "3.0.2",
"dependencies": [
"leak_tracker",
"matcher",
"meta"
]
},
{
"name": "leak_tracker",
"version": "11.0.2",
"dependencies": [
"clock",
"collection",
"meta",
"path",
"vm_service"
]
},
{
"name": "term_glyph",
"version": "1.2.2",
"dependencies": []
},
{
"name": "string_scanner",
"version": "1.4.1",
"dependencies": [
"source_span"
]
},
{
"name": "source_span",
"version": "1.10.2",
"dependencies": [
"collection",
"path",
"term_glyph"
]
},
{
"name": "boolean_selector",
"version": "2.1.2",
"dependencies": [
"source_span",
"string_scanner"
]
},
{
"name": "vm_service",
"version": "15.0.2",
"dependencies": []
}
],
"configVersion": 1
}

View File

@ -0,0 +1,31 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins-dependencies
/build/
/coverage/

View File

@ -1,212 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev"
source: hosted
version: "2.13.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.1"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
design_system:
dependency: "direct main"
description:
path: "../design_system"
relative: true
source: path
version: "0.0.1"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
lints:
dependency: transitive
description:
name: lints
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
url: "https://pub.dev"
source: hosted
version: "1.10.2"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
url: "https://pub.dev"
source: hosted
version: "15.0.2"
sdks:
dart: ">=3.11.4 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

View File

@ -310,16 +310,14 @@ class _ProductPreviewPanelState extends State<ProductPreviewPanel> {
icon: const Icon(Icons.check, size: 20), icon: const Icon(Icons.check, size: 20),
onPressed: widget.isUpdating ? null : _submitName, onPressed: widget.isUpdating ? null : _submitName,
tooltip: 'Save name', tooltip: 'Save name',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
const SizedBox(width: KcSpacing.xs), const SizedBox(width: KcSpacing.xs),
IconButton( IconButton(
icon: const Icon(Icons.close, size: 20), icon: const Icon(Icons.close, size: 20),
onPressed: widget.isUpdating ? null : _cancelNameEdit, onPressed: widget.isUpdating ? null : _cancelNameEdit,
tooltip: 'Cancel', tooltip: 'Cancel',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
], ],
), ),
@ -336,8 +334,7 @@ class _ProductPreviewPanelState extends State<ProductPreviewPanel> {
icon: const Icon(Icons.edit, size: 18), icon: const Icon(Icons.edit, size: 18),
onPressed: () => setState(() => _editingName = true), onPressed: () => setState(() => _editingName = true),
tooltip: 'Edit name', tooltip: 'Edit name',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
], ],
const SizedBox(width: KcSpacing.sm), const SizedBox(width: KcSpacing.sm),
@ -370,8 +367,7 @@ class _ProductPreviewPanelState extends State<ProductPreviewPanel> {
), ),
), ),
), ),
SizedBox( Expanded(
width: 100,
child: TextField( child: TextField(
controller: _priceController, controller: _priceController,
enabled: !widget.isUpdating, enabled: !widget.isUpdating,
@ -392,16 +388,14 @@ class _ProductPreviewPanelState extends State<ProductPreviewPanel> {
icon: const Icon(Icons.check, size: 20), icon: const Icon(Icons.check, size: 20),
onPressed: widget.isUpdating ? null : _submitPrice, onPressed: widget.isUpdating ? null : _submitPrice,
tooltip: 'Save price', tooltip: 'Save price',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
const SizedBox(width: KcSpacing.xs), const SizedBox(width: KcSpacing.xs),
IconButton( IconButton(
icon: const Icon(Icons.close, size: 20), icon: const Icon(Icons.close, size: 20),
onPressed: widget.isUpdating ? null : _cancelEdit, onPressed: widget.isUpdating ? null : _cancelEdit,
tooltip: 'Cancel', tooltip: 'Cancel',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
], ],
), ),
@ -435,8 +429,7 @@ class _ProductPreviewPanelState extends State<ProductPreviewPanel> {
icon: const Icon(Icons.edit, size: 18), icon: const Icon(Icons.edit, size: 18),
onPressed: () => setState(() => _editingPrice = true), onPressed: () => setState(() => _editingPrice = true),
tooltip: 'Edit price', tooltip: 'Edit price',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
], ],
), ),
@ -482,16 +475,14 @@ class _ProductPreviewPanelState extends State<ProductPreviewPanel> {
icon: const Icon(Icons.check, size: 20), icon: const Icon(Icons.check, size: 20),
onPressed: widget.isUpdating ? null : _submitCategory, onPressed: widget.isUpdating ? null : _submitCategory,
tooltip: 'Save category', tooltip: 'Save category',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
const SizedBox(width: KcSpacing.xs), const SizedBox(width: KcSpacing.xs),
IconButton( IconButton(
icon: const Icon(Icons.close, size: 20), icon: const Icon(Icons.close, size: 20),
onPressed: widget.isUpdating ? null : _cancelCategoryEdit, onPressed: widget.isUpdating ? null : _cancelCategoryEdit,
tooltip: 'Cancel', tooltip: 'Cancel',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
], ],
), ),
@ -522,8 +513,7 @@ class _ProductPreviewPanelState extends State<ProductPreviewPanel> {
icon: const Icon(Icons.edit, size: 18), icon: const Icon(Icons.edit, size: 18),
onPressed: () => setState(() => _editingCategory = true), onPressed: () => setState(() => _editingCategory = true),
tooltip: 'Edit category', tooltip: 'Edit category',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
], ],
), ),
@ -547,16 +537,14 @@ class _ProductPreviewPanelState extends State<ProductPreviewPanel> {
icon: const Icon(Icons.check, size: 20), icon: const Icon(Icons.check, size: 20),
onPressed: widget.isUpdating ? null : _submitDescription, onPressed: widget.isUpdating ? null : _submitDescription,
tooltip: 'Save description', tooltip: 'Save description',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
const SizedBox(width: KcSpacing.xs), const SizedBox(width: KcSpacing.xs),
IconButton( IconButton(
icon: const Icon(Icons.close, size: 20), icon: const Icon(Icons.close, size: 20),
onPressed: widget.isUpdating ? null : _cancelDescriptionEdit, onPressed: widget.isUpdating ? null : _cancelDescriptionEdit,
tooltip: 'Cancel description edit', tooltip: 'Cancel description edit',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
], ],
), ),
@ -591,8 +579,7 @@ class _ProductPreviewPanelState extends State<ProductPreviewPanel> {
icon: const Icon(Icons.edit, size: 18), icon: const Icon(Icons.edit, size: 18),
onPressed: () => setState(() => _editingDescription = true), onPressed: () => setState(() => _editingDescription = true),
tooltip: 'Edit description', tooltip: 'Edit description',
padding: EdgeInsets.zero, padding: const EdgeInsets.all(8),
constraints: const BoxConstraints(),
), ),
], ],
], ],

View File

@ -709,6 +709,68 @@ void main() {
}); });
}); });
// Touch target sizing (Stage 6B)
group('touch target sizing', () {
// Material Design minimum touch target is 48×48 dp.
// We verify the rendered size of each IconButton meets this.
const minTarget = 48.0;
Finder iconButtonAncestor(String tooltip) =>
find.ancestor(of: find.byTooltip(tooltip), matching: find.byType(IconButton));
testWidgets('edit name button has adequate touch target', (tester) async {
await tester.pumpWidget(buildTestWidget(sampleDraft, onNameChanged: (_) {}));
final size = tester.getSize(iconButtonAncestor('Edit name'));
expect(size.width, greaterThanOrEqualTo(minTarget));
expect(size.height, greaterThanOrEqualTo(minTarget));
});
testWidgets('edit price button has adequate touch target', (tester) async {
await tester.pumpWidget(buildTestWidget(sampleDraft, onPriceChanged: (_) {}));
final size = tester.getSize(iconButtonAncestor('Edit price'));
expect(size.width, greaterThanOrEqualTo(minTarget));
expect(size.height, greaterThanOrEqualTo(minTarget));
});
testWidgets('edit category button has adequate touch target', (tester) async {
await tester.pumpWidget(buildTestWidget(sampleDraft, onCategoryChanged: (_) {}));
final size = tester.getSize(iconButtonAncestor('Edit category'));
expect(size.width, greaterThanOrEqualTo(minTarget));
expect(size.height, greaterThanOrEqualTo(minTarget));
});
testWidgets('edit description button has adequate touch target', (tester) async {
await tester.pumpWidget(buildTestWidget(sampleDraft, onDescriptionChanged: (_) {}));
final size = tester.getSize(iconButtonAncestor('Edit description'));
expect(size.width, greaterThanOrEqualTo(minTarget));
expect(size.height, greaterThanOrEqualTo(minTarget));
});
testWidgets('save/cancel name buttons have adequate touch targets', (tester) async {
await tester.pumpWidget(buildTestWidget(sampleDraft, onNameChanged: (_) {}));
await tester.tap(find.byTooltip('Edit name'));
await tester.pump();
final saveSize = tester.getSize(iconButtonAncestor('Save name'));
final cancelSize = tester.getSize(iconButtonAncestor('Cancel'));
expect(saveSize.width, greaterThanOrEqualTo(minTarget));
expect(saveSize.height, greaterThanOrEqualTo(minTarget));
expect(cancelSize.width, greaterThanOrEqualTo(minTarget));
expect(cancelSize.height, greaterThanOrEqualTo(minTarget));
});
testWidgets('price edit row uses flexible width for narrow screens', (tester) async {
await tester.pumpWidget(buildTestWidget(sampleDraft, onPriceChanged: (_) {}));
await tester.tap(find.byIcon(Icons.edit));
await tester.pump();
// The TextField should be wrapped in Expanded, not a fixed-width SizedBox.
// Verify the TextField exists and the edit row renders without overflow.
expect(find.byType(TextField), findsOneWidget);
});
});
// Disabled state during isUpdating (Stage 2B) // Disabled state during isUpdating (Stage 2B)
group('edit fields disabled during isUpdating', () { group('edit fields disabled during isUpdating', () {