Adds the operator-facing intake flow for made-to-order commissions on top of
the Stage 11A domain and data layers. The form, validation, and confirmation
receipt all live in feature_orders, so web and mobile host the same widget
rather than forking business logic per platform.
Stage 11C still covers the tracking page, the status-change timeline, and
inventory deduction on completion.
Application:
- CreateCustomOrder and RecordDeposit use cases wrapping the repository
methods added in Stage 11A, so presentation never assembles a custom order
aggregate by hand. RecordDeposit deliberately accepts no payment identifier.
- CustomOrderFormController with field-keyed validation, so each input renders
its own error rather than a single form-level message. Clock and ID
generator are injected, which keeps created-at stamps and order IDs
deterministic under test.
- All money is typed as text and converted to integer cents by
parseMoneyToCents before it reaches DepositInfo, consistent with
PaymentResult.amountCents. Thousands separators, currency symbols, excess
precision, and negatives are rejected or normalised at the boundary. The
only double is the derived OrderItem.unitPrice, computed once from cents.
- Derived balance clamps at zero rather than reporting a credit when a deposit
exceeds the quoted total, matching DepositInfo.balanceDueCents.
Presentation:
- CustomOrderFormPage — sectioned intake (customer, specification, pricing and
deposit, reference images) over the shared design-system widgets.
- CustomOrderConfirmationPage — order summary, spec rendering, and a deposit
receipt showing amount, method, date, and balance only.
- FulfillmentStatusChip over the design-system KcStatusChip, covering all
seven pipeline values.
- showReferenceImageDialog() plus ReferenceImageDialogResult with URL
validation and de-duplication.
- OrdersPage gains an optional onCreateCustomOrder callback. When null the
"New Custom Order" action is not rendered, so the page stays usable in hosts
that have no intake route.
Hosts:
- kell_web: new /orders/custom route rendering CustomOrderFormPage inside
AppShell, with ordersToCustomOrder() and customOrderToOrders() handoffs on
AppNavigation. Confirmation returns to Orders pre-selecting the new order.
- kell_mobile: new MobileCustomOrderPage, a full-screen SafeArea host for the
shared form, reached from an extended FAB on the orders list. Market intake
is the primary custom-order use case, so mobile gets a dedicated route
rather than an inline panel. The list reloads on return.
Privacy:
- The deposit receipt renders amount, method, date, and balance only. The
transaction ID stays on PaymentResult and is asserted absent from the
rendered confirmation by test. The controller holds no payment identifier at
any point.
Deferred to Stage 11C, recorded in the Stage 11B section:
- Product-base selection from the catalogue; the form captures a free-text
specification instead.
- Platform camera and file-picker capture for reference images; the dialog
accepts validated URLs only.
Validation:
- 146 new tests: 82 on CustomOrderFormController (money parsing, setters,
derived balance and clamping, reference image add/remove/dedupe, the full
validation matrix, buildSpec mapping, submission, quoted vs. collected
deposit, repository failure, duplicate ID, reset), 26 on the form page, 24
on the confirmation page including the no-payment-identifier assertion, 3 on
the OrdersPage entry point, 6 on MobileCustomOrderPage, and 5 on the web
route.
- 2,044/2,044 passing across all 11 packages and apps, up from the 1,898
baseline at 0e3987b, with no regression.
- dart analyze --fatal-infos clean across all packages and apps.
- dart format --line-length 100 clean on every file touched by this slice.
Test-harness note: the intake form is taller than the default 800x600 test
view, so the submit-button tests resize tester.view rather than working around
a layout defect. The page scrolls normally on device.
Toolchain note: kell_web/pubspec.lock picked up SDK-pinned transitive bumps on
flutter pub get (matcher 0.12.19 to 0.12.20, meta 1.18.0 to 1.19.0, test_api
0.7.11 to 0.7.12, vector_math 2.2.0 to 2.4.3). Same accepted class as the
kell_mobile bumps recorded under chore/state-reconciliation-flutter-347;
committed so the lock file matches the toolchain in use.
Closes: P2.2 (intake UI portion)
Reconciles repository state after the local toolchain moved to Flutter 3.47.0
/ Dart 3.13.0. No product code changed - no lib/ or test/ file was modified.
Working tree drift:
1. analysis_options.yaml churn (accepted, 11 files). Flutter 3.47 ships
AnalysisOptionsMigration, which unconditionally writes a fixed
analyzer.exclude block into every analysis_options.yaml. It runs from
ensureReadyForPlatformSpecificTooling, so pub get / analyze / run / build
all restore it, and there is no opt-out (flutter#191160, flutter#191131).
Commit 92d83e0 previously reverted this; the revert did not hold - a full
test sweep regenerated the block in 8 more packages. Accepted and committed
rather than fighting the tool. Verified the risky web/** entry drops
nothing: neither kell_web/web/ nor kell_mobile/web/ contains .dart files.
2. kell_mobile web scaffold (reverted). A stray `flutter create
--platforms=web` had added an untracked web/ directory and rewritten
.metadata so the android platform entry was replaced by web. kell_mobile is
the Android target, so .metadata was restored and the scaffold deleted.
3. kell_mobile/pubspec.lock (accepted). Transitive bumps resolved under the
newer SDK: clock, matcher, meta, stack_trace, test_api, vector_math,
vm_service. All SDK-pinned.
Master document reconciliation:
- PROJECT_STATE_AUDIT.md: removed a duplicated "## 5. Summary Scorecard"
section - the scorecard and readiness table were present twice from a bad
append. Re-baselined the remaining §5 against main, added a test-count
column, and corrected §3.4 counts and §3.5 antipattern status. Stages
10C-10F, 11A, and 16A had landed but were still recorded as missing.
- master_development_brief.md: Appendix A maturity matrix refreshed to the
verified counts; new "Toolchain baseline" section documents the Flutter 3.47
behaviour so a future slice does not try to revert it again.
- build_execution_tracker.md: added the reconciliation slice entry and a
toolchain note.
- Fixed a pre-existing MD040 lint error (unlabelled code fence).
Verification:
- dart analyze --fatal-infos: clean across all 11 packages and apps
- tests: 1,898 / 1,898 passing (core 90, design_system 51, auth 42,
integrations 123, data 63, feature_wordpress 545, feature_inventory 167,
feature_orders 657, feature_policy 17, kell_web 62, kell_mobile 81) -
identical to the Stage 11A total, confirming no behaviour change
- markdownlint-cli2: 0 issues
Adds the custom/made-to-order commission workflow to feature_orders at the
domain and data layers. No UI in this slice — Stage 11B/11C cover the
CustomOrderFormPage, confirmation, and tracking surfaces.
Custom orders extend the existing Order aggregate rather than forming a
parallel system, so they flow through the same repository contract and the
same order management pipeline as standard orders.
Domain:
- OrderType enum (standard, custom) with wireValue/fromWire; fromWire defaults
to standard so an order written by an older client is never mis-classified
as a commission.
- CustomOrderSpec value object — material, color, size, dimensions,
unmodifiable reference image URLs, free-text notes, estimated completion.
Immutable with copyWith, list-aware ==/hashCode, and a toString that reports
the reference image count rather than the URLs.
- DepositInfo value object — all money as integer cents, consistent with
PaymentResult.amountCents, so repeated deposit/balance/settlement arithmetic
cannot drift. Derived balanceDueCents clamps at zero rather than reporting a
credit when a deposit exceeds the total.
- FulfillmentStatus pipeline: quoted -> depositReceived -> inProduction ->
qualityCheck -> readyForPickup -> completed, plus cancelled from any
non-terminal stage. FulfillmentTransition enforces legality in the domain —
illegal jumps, same-stage advances, and any move out of a terminal stage are
rejected with an explanatory reason rather than silently applied.
qualityCheck -> inProduction is permitted: failed inspection sending a piece
back to the bench is a real workshop operation.
- Order extended with orderType (defaults to standard), nullable
customOrderSpec, depositInfo, and fulfillmentStatus, plus isCustom,
hasDeposit, and balanceDueCents. Every existing field, default, and copyWith
semantic is preserved.
- OrdersRepository extended with createCustomOrder, updateCustomOrderSpec,
recordDeposit, and advanceFulfillmentStatus.
Data:
- FakeOrdersRepository implements all four methods and seeds five custom
orders (KC-C2001..KC-C2005) covering every non-terminal pipeline stage. The
six standard orders are unchanged.
- WooCommerceOrdersRepository implements all four methods. Custom order state
round-trips through 13 _kc_-prefixed order meta keys; the full mapping table
is documented on WooCommerceOrderMapper. Spec edits and stage advances PUT
only meta, so they can never mutate a price, a line item, or a customer
record. balanceDueCents is derived on read, never persisted, to avoid a
second source of truth.
- WooCommerceApiClient.updateOrder added in feature_wordpress — only
updateOrderStatus existed, which cannot carry meta.
Observability (Stage 16A layer):
- KcLogger injected into both repositories, defaulting to KcLogger.silent().
Domain rejections log at warning; API failures log at error with the status
code and originating exception.
- No silent catch (_) {} blocks — every swallowed error produces both a log
record and a failure result.
- Log context is limited to identifiers, operation names, and status names.
Customer identity, addresses, money amounts, and payment identifiers are
never logged, and all context additionally passes through KcRedactor.
- The deposit transaction ID is deliberately absent from both DepositInfo and
the WooCommerce meta mapping — it lives on PaymentResult alone, because
order meta is readable by any store administrator. Asserted by tests on both
the object and the outgoing request body.
Also closes P3.8: adds .metadata, CHANGELOG.md, LICENSE, and README.md to
feature_orders so it matches every other package (.gitignore already existed).
The README documents the pipeline, the meta mapping, and the privacy rules.
Deviations from the brief, recorded in the Stage 11A section:
- estimatedCompletionDate lives on CustomOrderSpec rather than Order, and
there is no separate updateEstimatedCompletion — a date revision goes
through updateCustomOrderSpec. Deferred for review in Stage 11C.
- internalNotes not added; Order.notes already serves that purpose.
Validation:
- 423 new tests in feature_orders (234 -> 657), including an exhaustive 7x7
legal/illegal transition matrix and coverage of every new repository method
on both implementations.
- 1,898/1,898 passing across all packages; no regression from the 1,475
baseline at 666abd2.
- dart analyze --fatal-infos clean across all 11 packages and apps.
- dart format --line-length 100 clean on every file touched by this slice.
Closes: P2.2 (domain + data portion), P3.8
Closes the two findings the release committee review identified as absolute
gaps: the platform had no structured logging and no crash reporting, so runtime
errors were invisible in every environment and operators had no way to report a
defect with evidence attached.
Addresses PROJECT_STATE_AUDIT.md findings P3.4 (structured logging infrastructure)
and P3.5 (crash reporting integration), plus the "No logging infrastructure" and
"No crash reporting" architectural risks in section 3.4.
core — new observability layer (packages/core/lib/src/observability/):
- KcLogLevel: five ordered severities (debug/info/warning/error/fatal) with
numeric severity, label, isAtLeast() threshold comparison, and fromString().
- KcLogRecord: immutable structured record (level, source, message, timestamp,
unmodifiable context, error, stackTrace) with format(), toJson(), isFailure.
- KcRedactor: credential redaction over 14 sensitive key fragments, recursing
through nested maps and lists. Applied to every logged context map and every
bug report diagnostics map.
- KcLogSink contract plus three implementations: KcConsoleLogSink (silent in
release by default), KcInMemoryLogSink (bounded ring buffer supplying bug
report evidence), KcMultiLogSink (fan-out that tolerates a failing sink).
- KcLogger: the logging facade. Drops sub-threshold records, redacts context,
and swallows sink exceptions so observability can never break a business
operation. Provides forSource() for subsystem-scoped child loggers.
- KcErrorReporter: installs FlutterError.onError and
PlatformDispatcher.instance.onError, and provides runGuarded() to wrap runApp.
restore() resets global state so tests stay isolated.
- KcBugSeverity and KcBugReport: the report model with redacted diagnostics,
hasFailureEvidence, toJson() for transmission, and toMarkdown() for export.
- KcBugReportSink contract plus KcInMemoryBugReportSink.
- KcBugReportService: validates input, attaches the recent log tail as evidence,
logs the submission outcome, and returns a failure result rather than throwing
when the tracker is unreachable.
- KcObservability: assembles logger, evidence buffer, and bug report service
together. Building them independently is the most likely way to produce empty
bug reports, so this bundle guarantees they share one buffer.
design_system:
- KcBugReportDialog and KcBugReportDraft: the shared operator reporting form
with inline validation, severity selection, optional reporter identity, and an
explicit statement that logs are attached and credentials are never included.
Kept free of a core dependency so design_system stays dependency-free.
Application wiring (kell_web and kell_mobile):
- AppServices and MobileAppServices hold a KcObservability; all four factories
(fake/wordpress/square/multi) stamp their environment label so reports are
attributable to the mode that produced them.
- Both main.dart entry points install the global error handlers before the first
frame and wrap runApp in a guarded zone, then emit a startup record capturing
the effective environment and whether a fake-mode credential fallback occurred.
- "Report a problem" action added to the AppShell and MobileShell app bars,
capturing the active screen (and tab index on mobile).
Documentation:
- docs/operations/release-checklist.md (new): per-environment release gates for
Development, Test/QA, Staging, and Production. Each environment inherits the
prior one's gates and adds its own, with build commands, functional and
observability verification, a sign-off record, rollback triggers, and the open
blocker register.
- master_development_brief.md: Stage 16A manifest, corrected baseline, Stage 16C
defined for the deferred scope (retry/rate limiting, remote sink, health
dashboard, analytics). Marked the square()/multi() factory and ID conflation
debt items resolved (fixed in Stage 10E but still listed as open).
- build_execution_tracker.md: Stage 16A slice entry.
- PRD.md: milestone table brought current; observability added to infrastructure
requirements. Added a scoped markdownlint-disable for MD011 because the PRD's
"(text)[cite: n]" citation markers are indistinguishable from reversed links
and were silently corrupted by markdownlint --fix during this review.
- mkdocs.yml and operations/index.md: register the release checklist.
Corrections made during review:
- The documented test total of 1,458 omitted feature_policy's 17 tests. The true
grand total is 1,475; both figures are now documented with the reason.
Validation:
- 85 new tests: 56 core, 10 design_system, 10 kell_web, 9 kell_mobile.
- 1,475/1,475 passing across all 11 packages and apps.
- dart analyze --fatal-infos clean across all 11 packages and apps.
- markdownlint clean on all five modified/created documents.
Known limitations (tracked as Stage 16C):
- Logs and bug reports are held in-process only. There is no remote sink, so
diagnostics are lost on restart and are unavailable off-device.
- Usage analytics and the per-channel integration health dashboard are not
implemented; IntegrationHealthCheck remains a contract with no implementation.
- Add MobileInventoryPage with search bar, horizontal status filter chips
(All / In Stock / Low Stock / Out of Stock / Draft), item count display,
compact _MobileInventoryCard list at 120px height (name, SKU, status chip,
quantity-on-hand), pull-to-refresh, and _detailPageActive guard to suppress
behind-route SnackBars
- Add MobileInventoryDetailPage wrapping shared InventoryDetailPanel; local
controller listener for SnackBar feedback; showAdjustQuantityDialog on
Adjust Quantity tap; haptic feedback (mediumImpact on result, lightImpact
on successful adjust); SafeArea wrapper
- Update MobileShell Inventory tab (case 1) from shared InventoryPage to
MobileInventoryPage; remove unused feature_inventory direct import
- Add 6 Stage 6C widget tests: Inventory tab search bar, status filter chips,
item count, card navigation to detail, adjust quantity dialog, back
navigation returning to list (26/26 kell_mobile total)
All tests passing: 26/26 kell_mobile, 75/75 feature_inventory
dart analyze --fatal-infos: no issues found
Stage 6 complete — Products, Orders, and Inventory all have mobile surfaces
- Add MobileOrdersPage with search bar, horizontal status filter chips
(All/Pending/Processing/Shipped/Delivered/Cancelled), order count
display, OrderCard list at 160px height, pull-to-refresh, and
\_detailPageActive guard to suppress behind-route SnackBars
- Add MobileOrderDetailPage wrapping shared OrderDetailPanel as a
StatefulWidget with local controller listener for SnackBar feedback,
confirmation dialog before status changes, haptic feedback
(mediumImpact for status, lightImpact for note additions), and SafeArea
- Wire Orders tab (case 3) in MobileShell to MobileOrdersPage; remove
unused feature_orders direct import from shell
- Add 6 new Stage 6B widget tests: Orders tab search bar, status filter
chips, order count, order card navigation to detail, confirmation dialog
for status change, back navigation returning to orders list
- Update build_execution_tracker.md and master_development_brief.md
Tests: 20/20 kell_mobile, 115/115 feature_orders — all passing
Analyze: dart analyze --fatal-infos — no issues found
- 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
- Update master_development_brief.md and build_execution_tracker.md
Tests: 300/300 feature_wordpress, 14/14 kell_mobile, 24/24 kell_web,
41/41 design_system — 379 total, all passing.
Stage 6 (Android operational maturity) complete.
Validate Docs / validate-docs (push) Successful in 55sDetails
Flutter Analyze / Dart Analyze (push) Has been cancelledDetails
Flutter Test / Flutter Tests (push) Has been cancelledDetails
- Convert MobileProductDetailPage to StatefulWidget with local controller
listener for SnackBar feedback in detail page context
- Add confirmation dialogs for publish/move-to-draft status changes
- Add haptic feedback (mediumImpact for status, lightImpact for field edits)
on successful actions
- Guard MobilePublishingPage SnackBars with _detailPageActive flag to prevent
invisible behind-route feedback when detail page is pushed
- Add 4 new Stage 6A widget tests (14 total kell_mobile tests passing)
- Update build_execution_tracker.md and master_development_brief.md
Validate Docs / validate-docs (push) Successful in 3m31sDetails
Flutter Analyze / Dart Analyze (push) Has been cancelledDetails
Flutter Test / Flutter Tests (push) Has been cancelledDetails
Replace default Flutter counter template in kell_mobile with a fully
integrated mobile operations platform shell reusing shared packages.
Mobile app shell:
- MobileAppServices extending KcAppServices with fake()/wp() factories
- KellMobileApp with KcAppScope<MobileAppServices>, KcTheme, env badge
- MobileShell with 5-tab NavigationBar (Dashboard, Inventory, Orders,
Publishing, More) using IndexedStack for state preservation
- KcBootstrap entry point with --dart-define environment variables
Dashboard:
- DashboardSummary value object with fromData()/empty() constructors
- GetDashboardSummary use case aggregating inventory, orders, publishing
- DashboardController (ChangeNotifier) with loading/error/summary state
- MobileDashboardPage with GridView summary cards using design system
widgets (KcSectionHeader, KcSummaryCard, KcEmptyState)
Placeholder pages:
- FinancePlaceholderPage, IntegrationsPlaceholderPage for More tab
- Feature tab pages delegate to shared feature presentation layers
Infrastructure:
- pubspec.yaml references all shared packages (core, design_system,
feature_inventory, feature_orders, feature_policy, feature_wordpress)
- SDK constraint corrected from ^3.11.4 to ^3.11.0 across all 14
pubspec.yaml files to match installed Dart SDK 3.11.3
Tests:
- 6 new kell_mobile widget tests: shell loading, summary cards,
environment badge, navigation bar destinations, tab switching, More menu
- All existing tests remain passing (24/24 kell_web, 294/294
feature_wordpress)
Documentation:
- master_development_brief.md: Stage 5A marked complete, next branch
updated to feat/android-publishing-surface (Stage 5B), kell_mobile
platform description updated
- build_execution_tracker.md: Stage 5A entry added with full file list
Publish Docs / publish-docs (push) Successful in 1m5sDetails
- Update current status to Stage 4D complete (Stage 4 complete)
- Add feat/test-coverage-visibility slice entry with coverage baseline
- Set next branch to feat/android-app-shell (Stage 5A)
Validate Docs / validate-docs (push) Successful in 1m4sDetails
Add _refreshSelection() to ProductPublishingController to preserve and refresh selectedDraft by id after all write-triggered reloads. Selection stays on the same product with latest data, or auto-selects first visible item if the original leaves the active filter.
- 11 new post-write consistency tests (234 total)
- dart analyze clean