Skip to content

docs: add TRD for order handler boundary enforcement - #659

Merged
mnindrazaka merged 2 commits into
refactor/order-appfrom
claude/relaxed-hawking-ng1h7j
Sep 27, 2026
Merged

mnindrazaka merged 2 commits into
refactor/order-appfrom
claude/relaxed-hawking-ng1h7j

Conversation

@mnindrazaka

Copy link
Copy Markdown
Collaborator

Add a technical requirements document establishing that order app handlers accept only usecases and documented exceptions, never repositories or duplicated usecase state.

Summary

This TRD (docs/trd-order-handler-usecase-boundary.md) audits the current state of libs/ui/src/presentation/handlers/order/ and libs/ui/src/app/order/ composition roots, identifies 16 non-usecase props across 4 handlers (8 of which are repositories), and proposes a phased 4-PR refactor to enforce the boundary already established in docs/trd-order-app-composition-and-ssr.md §3.1 and exemplified by POS handlers.

Key findings

  • MenuListHandler receives 5 non-usecase props: cartRepository, paymentRepository, sessionRepository, tableCode, preparingCount
  • CartHandler receives 7 non-usecase props: paymentRepository, sessionRepository, enabled, enabledMethods, cashierLocation, tableCode, preparingCount
  • OrderStatusHandler receives 3 non-usecase props: paymentRepository, sessionRepository, cashierLocation
  • OrderHistoryHandler receives 1 non-usecase prop: sessionRepository

Root causes: side effects that belong in usecases are written inline in handlers (useEffect calling repositories), usecases are reconstructed reactively in handlers instead of once in composition roots, and config/feature-flag values are passed as bare props instead of through usecase Params.

Proposed solution

Four phased PRs, each independently revertable:

  1. P1: TableResolveUsecase absorbs session/cart table-binding side effects; handlers read tableResolveUsecase.params.code instead of a duplicate tableCode prop
  2. P2: OrderHistoryUsecase/OrderStatusUsecase absorb sessionRepository for synchronous tableCode read in getInitialState() (matching CartUsecase's existing pattern); OrderStatusUsecase absorbs cashierLocation
  3. P3: CheckoutUsecase absorbs enabled, enabledMethods, cashierLocation as Params (matching how customerName/customerWhatsappNumber already work)
  4. P4: New shared usePaymentCancelSyncedTo hook re-syncs PaymentCancelUsecase via dispatched SYNC_PARAMS action; composition roots construct it once; handlers never see paymentRepository

End state: zero repositories in any handler's prop list; preparingCount is the one documented exception (D6) — a read-only SSR-seeded integer with no state machine.

Design decisions

  • D1: TableResolveUsecase takes optional { sessionRepository?, cartRepository? } dependencies and owns both side effects, with de-dupe logic moving from boundTableCodeRef to an instance field
  • D2: OrderHistoryUsecase/OrderStatusUsecase read tableCode synchronously in getInitialState() via optional sessionRepository dependency — same pattern CartUsecase already uses for cartQueryRepository.getSelectedItemId()
  • D3: tableCode stops being a handler prop; handlers read tableResolveUsecase.params.code instead
  • D4: Composition roots construct paymentCancelUsecase once; new hook re-syncs it via SYNC_PARAMS action, replacing three duplicated ~20-line useState/conditional-setState blocks
  • D5: CheckoutUsecase.Params and OrderStatusUsecase.Params gain feature-flag/config fields; CheckoutState.Context carries them through unchanged
  • D6: preparingCount remains a plain prop — it is SSR-seeded, read-only, has no state machine, and wrapping it in a usecase would add abstraction beyond what the task requires

Rollback

Every phase is

https://claude.ai/code/session_01ARwg5gU5WfUmWAPx8xq8XY

Audits libs/ui/src/app/order and presentation/handlers/order for
repositories and raw params leaking into handler props, and lays out
a 4-phase plan to move that logic into the owning usecases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARwg5gU5WfUmWAPx8xq8XY
@vercel

vercel Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

3 Skipped Deployments
Project Deployment Actions Updated
gatherloop-order Ignored Ignored Sep 27, 2026 12:47pm UTC
gatherloop-pos Ignored Ignored Sep 27, 2026 12:47pm UTC
gatherloop-ui-storybook Ignored Ignored Sep 27, 2026 12:47pm UTC

D4 previously always seeded PaymentCancelUsecase with a placeholder
reference/method and relied on SYNC_PARAMS to correct it. OrderStatus's
loader already knows the payment on the common path, so seed from it
there; MenuList/Cart still need the placeholder since the cart is
deliberately never SSR-seeded (D5 of the composition/SSR TRD).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARwg5gU5WfUmWAPx8xq8XY
@mnindrazaka
mnindrazaka changed the base branch from main to refactor/order-app September 27, 2026 15:43
@mnindrazaka
mnindrazaka merged commit f98dcc1 into refactor/order-app Sep 27, 2026
7 checks passed
mnindrazaka added a commit that referenced this pull request Sep 30, 2026
* docs: add TRD for order handler boundary enforcement (#659)

* docs: add TRD for order handler/usecase boundary

Audits libs/ui/src/app/order and presentation/handlers/order for
repositories and raw params leaking into handler props, and lays out
a 4-phase plan to move that logic into the owning usecases.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARwg5gU5WfUmWAPx8xq8XY

* docs: seed paymentCancelUsecase from known payment where available

D4 previously always seeded PaymentCancelUsecase with a placeholder
reference/method and relied on SYNC_PARAMS to correct it. OrderStatus's
loader already knows the payment on the common path, so seed from it
there; MenuList/Cart still need the placeholder since the cart is
deliberately never SSR-seeded (D5 of the composition/SSR TRD).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARwg5gU5WfUmWAPx8xq8XY

---------

Co-authored-by: Claude <noreply@anthropic.com>

* refactor(order): move table-binding side effects into TableResolveUsecase (#660)

Phase 1 of docs/trd-order-handler-usecase-boundary.md (D1, D3). MenuListHandler
and CartHandler no longer receive cartRepository/sessionRepository/tableCode
props: TableResolveUsecase now takes optional sessionRepository/cartRepository
dependencies and owns setTableCode/updateTable as part of its own
onStateChange, with the cart-bind de-dupe moved from a handler useRef to an
instance field. Handlers read tableResolveUsecase.params.code instead of a
duplicated tableCode prop.


Claude-Session: https://claude.ai/code/session_01YWhceHti3NXs7xRt5uw5o3

Co-authored-by: Claude <noreply@anthropic.com>

* refactor(order): move tableCode/cashierLocation into order status/history usecases (#661)

Implements P2 of docs/trd-order-handler-usecase-boundary.md.
OrderHistoryUsecase and OrderStatusUsecase now take an optional
SessionRepository dependency and read tableCode once in
getInitialState(), the same idiom CartUsecase already uses for
cartQueryRepository. OrderStatusUsecase also takes cashierLocation
as a Params field carried through Context, the same shape
CheckoutUsecase already uses for customerName.

OrderHistoryHandler and OrderStatusHandler no longer receive
sessionRepository/cashierLocation as props; both are read off the
usecase's own state instead.


Claude-Session: https://claude.ai/code/session_01Gj1cLXBEeLrUTrhhJ1QeJB

Co-authored-by: Claude <noreply@anthropic.com>

* refactor(order): move checkout feature flags into CheckoutUsecase params (#662)

Per docs/trd-order-handler-usecase-boundary.md phase 3 (D5): enabled,
enabledMethods, and cashierLocation are checkout-flow config values
that belong on CheckoutUsecase.params, the same way customerName
already does, instead of being threaded past CartHandler as bare
props read from process.env in the composition root.

CartHandler now reads these off checkout.state instead of receiving
them as separate props.


Claude-Session: https://claude.ai/code/session_01Ete6mVquK6t44UQsxf8xjG

Co-authored-by: Claude <noreply@anthropic.com>

* refactor(order): construct paymentCancelUsecase in composition roots, not handlers (#663)

Removes the last repository (paymentRepository) from every order handler's
prop list. MenuListHandler, CartHandler, and OrderStatusHandler each
reconstructed a PaymentCancelUsecase inline via useState + a conditional
setState whenever their own pendingPayment/payment changed; that ~20-line
block is now a single new usePaymentCancelSyncedTo hook that re-syncs a
usecase built once by the composition root via a dispatched SYNC_PARAMS
action, matching how PaymentCancelUsecase already supported this. Also
documents the rule in docs/handlers.md so this doesn't regress the next
time an order screen is touched.

Implements Phase 4 of docs/trd-order-handler-usecase-boundary.md.


Claude-Session: https://claude.ai/code/session_01RFonbEaS6P4NfMBuhJWK2p

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants