From 519e1ec33a3c9c57822f82081f8a4ae8b59523bb Mon Sep 17 00:00:00 2001 From: Erik Shafer Date: Tue, 16 Jun 2026 15:50:52 -0500 Subject: [PATCH] =?UTF-8?q?design:=20unpark=20frontend=20architecture=20?= =?UTF-8?q?=E2=80=94=20vision=20v0.6=20+=20ADR-016=20(Frontend=20Live-Upda?= =?UTF-8?q?te=20Transport)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Discharges the vision's explicit frontend-architecture unpark trigger ("CritterBids lands on a stable live-update pattern"), confirmed by the sibling-repo frontend survey (docs/research/frontend-survey-sibling-repos.md, committed in prior session). ADR-016 (Accepted): SignalR (`@microsoft/signalr` v10) as the browser-client live-update transport for rider, driver, and ops frontend surfaces. Architecture is the transport-agnostic push→Query-cache-bridge pattern from CritterBids' `createSignalRProvider`: component contract (useListen / useConnectionState / applyMessage) is decoupled from transport at the createConnection seam. gRPC-web ruled out as niche and experimental; gRPC thesis demonstrated by the backend service mesh. Cross-references ADR-005 (browser-client push is the fourth transport category not covered by service-to-service ADR-005) and ADR-006 (accessTokenFactory token acquisition for authed hub connections). Vision v0.6: adopted convergent house stack (React 19 / Vite 8 / TS 6 / Tailwind v4 / TanStack Query / shadcn/ui / Zod) and audience-SPA monorepo shape (rider / driver / operations / shared / e2e) as working direction in §Tentative Technology Stack. Removed "Frontend architecture" and "Map library for frontend" from §Explicitly Parked. Added three new §Open Questions entries: map library (candidates: MapLibre GL / Leaflet / deck.gl; couples to Telemetry BC geospatial representation), contracts-package shape (one @crittercab/shared vs. per-BC packages), and frontend monorepo vs. separate repos. Two successor sessions named: map-library spike (after Telemetry geospatial repr. is settled) and contracts/monorepo session (at first frontend implementation). Null narrative/workshop spec delta — vision/ADR-layer design-return per ADR-004. --- .../016-frontend-live-update-transport.md | 69 ++++++++++++++++++ docs/decisions/README.md | 1 + docs/retrospectives/README.md | 1 + .../frontend-architecture-unpark.md | 70 +++++++++++++++++++ docs/vision/README.md | 38 ++++++++-- 5 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 docs/decisions/016-frontend-live-update-transport.md create mode 100644 docs/retrospectives/frontend-architecture-unpark.md diff --git a/docs/decisions/016-frontend-live-update-transport.md b/docs/decisions/016-frontend-live-update-transport.md new file mode 100644 index 0000000..096840f --- /dev/null +++ b/docs/decisions/016-frontend-live-update-transport.md @@ -0,0 +1,69 @@ +# ADR-016: Frontend Live-Update Transport + +**Status:** Accepted +**Date:** 2026-06-16 + +## Context + +CritterCab's three audience-facing frontend surfaces — the rider app, the driver app, and the ops dashboard — require real-time push updates from the backend: + +- The driver app receives offer deliveries and trip-state changes as they occur. +- The rider app watches trip progress during an active ride. +- The ops dashboard renders a live map of active drivers and trips. + +None of these surfaces are satisfied by request/response polling: the latency is too high for offer delivery, and polling a live map at the required refresh rate is wasteful. A server-push transport is required. + +The choice of browser-side push transport is governed by a different set of constraints than the service-to-service choices in ADR-005. ADR-005 covers gRPC (service-to-service calls and streaming), Kafka (high-volume telemetry), and Azure Service Bus (business events). Browser-to-backend push is a distinct fourth category: a client that cannot run a native gRPC stack, connecting over HTTP/HTTPS to a server that must push updates as they occur. ADR-005's "transport split by flow type" principle applies — the question is which transport fits the browser-client push shape — but the options differ. + +The [sibling-repo frontend survey](../research/frontend-survey-sibling-repos.md) identified a reusable architectural pattern from CritterBids: a transport-agnostic push→Query-cache-bridge where the component contract (`useListen`, `useConnectionState`, `applyMessage`) is decoupled from the underlying transport. The transport is plugged in at the `createConnection` seam; components do not know whether the bytes arrived over SignalR or any other mechanism. This architecture is the primary transferable asset from the survey — the transport slot is a separate decision. + +The survey also named a tension: CritterBids uses SignalR (proven), while CritterCab's primary goal of showcasing Wolverine's gRPC feature set (vision §Goals Primary #1) raised gRPC-web streaming as the on-thesis alternative. Both were evaluated. + +## Options Considered + +### Option A — SignalR (`@microsoft/signalr` v10) + +SignalR provides a managed WebSocket hub abstraction over ASP.NET Core. It handles connection negotiation, reconnect lifecycle, and hub method invocation. For authenticated surfaces, it accepts a bearer token via `accessTokenFactory`, which maps to CritterCab's Entra-issued token acquisition (ADR-006) without additional ceremony. + +CritterBids already ships a generic `createSignalRProvider` that returns a `{ Provider, useHub, useListen, useConnectionState }` quad shared across three SPAs. The pattern is proven, actively maintained, and part of the existing muscle memory across multiple repositories. + +The cost: SignalR routes the most visible real-time frontend surfaces through a Microsoft-specific WebSocket framework rather than through gRPC. For the browser-client category, this is a deliberate pragmatic departure from the on-thesis path. + +### Option B — gRPC-web streaming + +gRPC-web is a browser-compatible variant of gRPC using HTTP/1.1 framing or HTTP/2 without server push. ASP.NET Core supports it natively via `UseGrpcWeb()`. Choosing gRPC-web would align the browser transport with the backend gRPC surfaces. + +The cost: gRPC-web is niche and experimental in the browser context. Neither sibling repository has shipped it. Ergonomics — tooling maturity, streaming reconnect semantics, React DX with generated TypeScript clients — are unproven in this stack. The on-thesis argument is real but the gRPC thesis is demonstrated convincingly by the backend service mesh; the browser transport does not need to carry the same demonstration burden. + +### Option C — Raw WebSockets or Server-Sent Events + +Direct WebSocket connections or SSE streams without a framework. Lowest ceremony to establish, but requires hand-rolling connection management, reconnect logic, and message dispatch. The ops live map's fan-out requirements and the driver app's reconnect-after-gap semantics make the missing infrastructure costs real, not theoretical. + +## Decision + +**Option A.** CritterCab adopts **SignalR** (`@microsoft/signalr` v10) as the browser-client live-update transport for the rider app, driver app, and ops dashboard. + +The architectural pattern adopted is the transport-agnostic push→Query-cache-bridge from CritterBids' `createSignalRProvider`: + +- The `Provider` is parameterised by behavior — `createConnection`, `parseMessage(payload) → TMessage | null`, and `applyMessage(queryClient, message)` — not by protocol. +- On each inbound message, the bridge writes the result into the TanStack Query cache via `applyMessage`, then fans out to imperative listeners. +- `useConnectionState` surfaces `HubConnectionState` + `lastError` for UI resilience. +- The `onReconnected(connection, queryClient)` seam handles cache invalidation after a reconnect gap — the ride-sharing analogue of state-digest reconciliation when a driver or rider app reconnects after a network drop. + +Connection construction follows CritterBids' anonymous/token split: an anonymous connection (auto-reconnect, default negotiation) for any public surfaces; a token connection (`WebSockets` + `skipNegotiation` + `accessTokenFactory` acquiring Entra-issued tokens per ADR-006) for authenticated rider, driver, and ops surfaces. + +The gRPC thesis (vision §Goals Primary #1) is demonstrated by the backend service mesh — service-to-service calls, server-streaming offer delivery between services, client-streaming GPS ingest. ADR-005's three-transport commitment governs those flows. SignalR governs the distinct browser-client push category without conflicting with ADR-005's scope. + +Consistency with the proven house stack (shared with CritterBids) is an explicit factor: the pattern and its tooling are already exercised across multiple active projects, reducing the marginal integration risk to near zero. + +## Consequences + +The component contract (`useListen`, `useConnectionState`, `applyMessage`) is transport-agnostic. Components in the rider, driver, and ops SPAs do not couple to SignalR directly; they read from the TanStack Query cache and call the hook surface. The `createConnection` seam is explicitly left open: the transport is swappable at that seam without touching component code. + +SignalR hub endpoints live on the ASP.NET Core host of the relevant backend service (or a dedicated BFF layer). They are standard ASP.NET Core SignalR hubs, not Wolverine endpoints. Backend-to-hub fan-out uses Wolverine message handlers or background services publishing to the hub context, keeping the Wolverine handler graph intact on the server side. + +The ops dashboard's live map requires higher-frequency driver location updates than the rider or driver apps. SignalR's WebSocket transport handles this at expected ops-dashboard concurrency (tens of simultaneous ops users). + +The shared contracts package shape — one `@crittercab/shared` or per-BC packages — is explicitly deferred to the first frontend implementation session. See §Open Questions in the vision doc. + +Cross-references: [ADR-005](./005-transport-selection-by-flow-type.md) (transport precedent; browser-client push is the fourth category not covered by ADR-005), [ADR-006](./006-identity-provider-as-swappable-anti-corruption-layer.md) (token acquisition for `accessTokenFactory` on authenticated hub connections). diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 058b253..a947885 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -21,3 +21,4 @@ For the template and guidelines on when to write an ADR, see [ADR-001](./001-rec | [013](./013-shared-cross-bc-identifier.md) | Shared Cross-BC Identifier | Accepted | | [014](./014-asb-topic-naming-convention.md) | Azure Service Bus Topic Naming Convention | Accepted | | [015](./015-driver-app-projection-timing-budget.md) | Driver-App Projection Timing Budget | Accepted | +| [016](./016-frontend-live-update-transport.md) | Frontend Live-Update Transport | Accepted | diff --git a/docs/retrospectives/README.md b/docs/retrospectives/README.md index 89c6f74..f92ab9e 100644 --- a/docs/retrospectives/README.md +++ b/docs/retrospectives/README.md @@ -84,6 +84,7 @@ Subsequent sections are session-specific. Existing retros in this directory serv - [`skills-tidy-ai-skills-sync.md`](./skills-tidy-ai-skills-sync.md) — **First ai-skills upstream-sync session**; lighter-cadence successor to Phase 5's full reconciliation. Sync target was ai-skills @ `b0d0f7d` (HEAD as of 2026-05-22). Eighteen-commit upstream delta categorized as 4 content commits (HTTP strengthen ×2, projections `IncludeType` clarification, migration-skills first cut) and 14 infrastructure / catalog-growth commits (sidebar reconciliation, Buy CTAs, install URL fixes, version bumps, new `added_in` frontmatter convention, three new upstream skills). Verified zero content propagation to both Cab counterparts (`wolverine-http-handlers/SKILL.md` and `marten-projections/SKILL.md`) via re-greps + end-to-end reads — upstream-changed surface lives in fundamentals layers Cab already cross-references rather than duplicates, so Phase 5's trim discipline carried the sync passively. **Closed out Phase 5 retro's metadata gap** (`Status: In progress` → `Complete`; `Outcome` synthesized from the existing `## Phase 5 deliverable summary`; new `## Document history` section) as natural continuation of the reconciliation thread. Introduces a **methodology shift** captured for future syncs: per-upstream-commit lens (this session) is correct for incremental syncs; per-skill audit (Phase 5) is correct for full reconciliations. Recorded sync floor SHA + upstream catalog inventory (75 skills, 7% growth) so the next sync becomes a `git log b0d0f7d..HEAD` content-delta query. **Second prompt-and-retro pair to exercise the spec-delta null-edge case** (first was `housekeeping-delete-may-15-handoff` on 2026-05-19). Triggered by [`prompts/skills-tidy-ai-skills-sync.md`](../prompts/skills-tidy-ai-skills-sync.md). Status: complete (2026-05-23). - [`aspire-reconcile-and-port-band.md`](./aspire-reconcile-and-port-band.md) — **First `tidy: aspire` session.** Reconciled Aspire to `13.4.3` across code and docs and pinned CritterCab's `53xx` local-dev port band (cross-project band registry + `+5` per-service slot convention documented in the `aspire`/`adding-a-service` skills, whose AppHost-location contradiction was also fixed). **Surfaced and fixed a latent build break:** the file-based AppHost did not restore under the repo's Central Package Management (NU1008/NU1009); `#:property ManagePackageVersionsCentrally=false` fixes it and corrects the prompt's "entries are inert" premise (a file-based `dotnet run` generates a synthetic csproj at the repo root that inherits `Directory.Packages.props`). Proved empirically that a file-based program honors an adjacent `Properties/launchSettings.json`. Compile-verified via `dotnet build apphost.cs`; full `aspire run` deferred to a manual check. Six follow-ups queued (Wolverine/Marten/Polecat doc-drift, Grpc/Polecat 5.38-vs-6.8, ServiceDefaults, Swagger title, MessagePack NU1903, JasperFx feed 403). Triggered by [`prompts/aspire-reconcile-and-port-band.md`](../prompts/aspire-reconcile-and-port-band.md). Status: complete (2026-06-13). - [`fix-marten9-projection-source-gen.md`](./fix-marten9-projection-source-gen.md) — **First `fix:` PR.** Declared the three conventional projection subclasses `partial` so Marten 9's source generator emits dispatchers (runtime codegen removed in v9 / JasperFx 2.0, no fallback); fixed pre-existing `main` breakage from the `2f83e66` bump that PR #31's CI surfaced. `dotnet test CritterCab.slnx` 7/8-failing → 8/8-green; analyzer flows transitively (no `.csproj` change); `RideRequest` self-aggregating type left untouched. Confirmed the diagnosis by checking `main`'s CI before assuming the PR caused it. Triggered by [`prompts/fix-marten9-projection-source-gen.md`](../prompts/fix-marten9-projection-source-gen.md). Status: complete (2026-06-13). +- [`frontend-architecture-unpark.md`](./frontend-architecture-unpark.md) — **First frontend design-return.** Unparked the frontend decision parked since vision v0.1, after the [sibling-repo frontend survey](../research/frontend-survey-sibling-repos.md) discharged the "CritterBids lands on a stable live-update pattern" trigger. Five decisions walked: transport recording (Accepted, SignalR — gRPC-web ruled out as niche/experimental before grill reached question 2); stack/monorepo (vision doc only, no ADR); map library (re-filed to open question); gRPC-web spike (moot); contracts-package shape (open question). ADR-016 (Frontend Live-Update Transport) authored and Accepted: SignalR with transport-agnostic push→Query-cache-bridge architecture; component contract decoupled from transport at the `createConnection` seam. Vision bumped to v0.6: frontend stack + audience-SPA monorepo direction adopted; two parked items retired; three new open questions filed (map library, contracts-package shape, monorepo vs. separate repos). Two successor sessions named: map-library spike (after Telemetry geospatial representation is settled); contracts/monorepo session (at first frontend implementation). Null narrative/workshop spec delta — fifth honest-null instance. Triggered by [`prompts/frontend-architecture-unpark.md`](../prompts/frontend-architecture-unpark.md). Status: complete (2026-06-16). Phase 1–3 retrospectives were not authored at the time those phases ran (the retrospective convention solidified during Phase 4). They may be reconstructed from the working transcripts and skill artifacts if needed; otherwise they remain a known gap in the project record. diff --git a/docs/retrospectives/frontend-architecture-unpark.md b/docs/retrospectives/frontend-architecture-unpark.md new file mode 100644 index 0000000..6cee102 --- /dev/null +++ b/docs/retrospectives/frontend-architecture-unpark.md @@ -0,0 +1,70 @@ +# Retrospective — Frontend Architecture Unpark (Vision v0.6 + ADR-016) + +| Field | Value | +|---|---| +| **Triggering prompt** | [`docs/prompts/frontend-architecture-unpark.md`](../prompts/frontend-architecture-unpark.md) | +| **Status** | Complete | +| **Date** | 2026-06-16 | +| **Output artifacts** | [`docs/decisions/016-frontend-live-update-transport.md`](../decisions/016-frontend-live-update-transport.md) (new); [`docs/decisions/README.md`](../decisions/README.md) (ADR-016 row); [`docs/vision/README.md`](../vision/README.md) (v0.6 — stack/monorepo direction, two parked items re-filed, three open questions added, Document History entry); [`docs/retrospectives/README.md`](../retrospectives/README.md) (this retro's index entry) | +| **Outcome** | ADR-016 Accepted (SignalR). Vision bumped to v0.6. Two parked frontend items retired. Three new open questions filed. Two successor sessions named (map-library spike; contracts-package session). | + +--- + +## Framing + +Design-return session per ADR-004, triggered by the sibling-repo frontend survey discharging the vision's explicit "CritterBids lands on a stable live-update pattern" unpark condition. The session's job was judgment, not research: decide what the vision adopts as working direction and record the transport decision in an ADR. + +--- + +## Outcome summary + +| Deliverable | Result | +|---|---| +| ADR-016 — Frontend Live-Update Transport | Authored and Accepted. SignalR (`@microsoft/signalr` v10) with transport-agnostic push→Query-cache-bridge architecture. | +| Vision v0.6 | Bumped. Frontend stack + audience-SPA monorepo adopted as working direction in §Tentative Technology Stack. | +| "Frontend architecture" — re-filed | Removed from §Explicitly Parked. Discharge cited in v0.6 Document History. | +| "Map library for frontend" — re-filed | Removed from §Explicitly Parked. Added to §Open Questions with candidates (MapLibre GL, Leaflet, deck.gl) and backend-coupling note. | +| §Open Questions — three new entries | Map library; contracts-package shape (one `@crittercab/shared` vs. per-BC); monorepo vs. separate repos. | +| Successor sessions named | Map-library spike; contracts-package/monorepo-shape session at first frontend implementation. | + +--- + +## What worked + +**grill-with-docs surfaced the load-bearing question early.** The first grill question — "does ADR-005 already place browser-client streaming in the gRPC column?" — immediately exposed the most important distinction the ADR had to make: ADR-005 governs service-to-service transport; browser-to-backend push is a distinct fourth category. Without that clarification, ADR-016 would have been written with the wrong framing. + +**The transport decision resolved cleanly and fast.** The prompt's lean was "Accepted with gRPC-web as default, SignalR as fallback, pending spike." The user rejected gRPC-web as too niche and experimental before the grill reached question 2, collapsing four grilling questions to one answer: SignalR, Accepted, no spike, no gate. The decision is cleaner than the lean anticipated. + +**Decision-at-a-time sign-off kept each call crisp.** Five decisions, each pre-authored with a lean and a citation, walked sequentially. No batching, no ambiguity about what was being decided. The two moot decisions (gRPC-web spike; fallback alignment with ADR-005) were dropped cleanly rather than argued through. + +**The transport-agnostic bridge architecture survived the transport flip.** The core architectural pattern from the survey (push→Query-cache-bridge with the transport parameterised at `createConnection`) transferred intact regardless of which transport filled the slot. Committing to the architecture separately from the transport was the right structure. + +--- + +## What was harder than expected + +**The prompt's lean on flag #1 was overthought.** The "Accepted-with-default-gRPC-web-pending-spike" shape never came up in practice — the user resolved the transport question categorically before the grill reached it. The lean anticipated a tension that the user had already resolved. Useful to surface, but the sign-off pattern worked better than the multi-step fallback structure suggested. + +**The vision doc header was stale.** The version header said "v0.4" when v0.5 had already been applied (a missed header update from the W003 session). Fixed incidentally during the v0.6 bump. + +--- + +## Methodology refinements that emerged + +**The grill-with-docs "first question" carries disproportionate leverage.** In this session the first question — scope of ADR-005 — collapsed the rest of the grilling tree. Investing in identifying the most foundational dependency-breaking question before starting the grill is worth explicit effort. + +**Prompt leans should acknowledge that the user may have already resolved the tension.** The flag #1 lean spent significant prose on a three-option fallback structure for a decision the user had already made. A shorter lean — "our read is SignalR-or-gRPC-web; here's the tension; your call" — would have been more efficient. + +--- + +## Outstanding items / next-session inputs + +- **Map-library spike** — named successor session. Trigger: when Telemetry BC's geospatial representation (H3 cells vs. GeoJSON) is decided; spike is a throwaway implementation, not a research doc. +- **Contracts-package + monorepo shape** — named successor session at first frontend implementation. Trigger: when the first SPA package is bootstrapped. Two open questions (contracts-package shape; monorepo vs. separate repos) are filed in the vision doc and will be decided in that session. +- **SignalR hub design** — not started. Which services own hub endpoints, how Wolverine message handlers fan out to hub contexts, and whether a BFF layer is needed are all deferred to the first frontend implementation session. + +--- + +## Spec delta — landed? + +Null narrative/workshop spec delta, as planned and honestly named in the prompt. This is a vision-level design-return; no narrative or workshop spec was amended. Deltas land at the vision/decision layer: vision doc gains §Tentative Technology Stack frontend section and loses two §Explicitly Parked entries; decision record gains ADR-016. The null-delta pattern is established precedent (fifth instance: housekeeping-delete-may-15-handoff; skills-tidy-ai-skills-sync; workshops/003 retro; aspire-reconcile-and-port-band; fix-marten9-projection-source-gen). diff --git a/docs/vision/README.md b/docs/vision/README.md index 8a8d9c1..3055d5c 100644 --- a/docs/vision/README.md +++ b/docs/vision/README.md @@ -6,7 +6,7 @@ CritterCab is a reference architecture for a ride-sharing platform, built on the Where CritterSupply explores a modular monolith for e-commerce and CritterBids explores saga-driven auction orchestration, CritterCab focuses on distributed, event-driven services communicating over gRPC. The project exists in large part to showcase Wolverine's gRPC feature set, which shipped in Wolverine 5.32. The ride-sharing domain was chosen because its natural shape (driver location streaming, rider-driver matching, trip lifecycle) exercises every mode of gRPC communication while providing room for event sourcing, high-volume telemetry, and multi-transport messaging. -This document captures the current state of our thinking as **version 0.4**. Bounded contexts, technology choices, and design principles will shift as Event Modeling and real implementation pressure the design. When they do, this document gets updated, and significant decisions get recorded as ADRs in [docs/decisions](../decisions). +This document captures the current state of our thinking as **version 0.6**. Bounded contexts, technology choices, and design principles will shift as Event Modeling and real implementation pressure the design. When they do, this document gets updated, and significant decisions get recorded as ADRs in [docs/decisions](../decisions). ## Goals @@ -146,6 +146,34 @@ The resulting document layers, each with a clear job: Azure (ADR-007). The specific hosting model — Container Apps, App Service, or AKS — is deferred until the first cross-service integration is demonstrable end-to-end. Actually-deployed is a stated goal. Conference demos include a reachable URL. +### Frontend + +The following working direction was adopted in v0.6 based on the [sibling-repo frontend survey](../research/frontend-survey-sibling-repos.md) and ADR-016. It is working direction, not a final commitment — revisions are expected as frontend implementation begins. + +**Stack (convergent with CritterBids and mmo-reconnect):** + +- React 19 / Vite 8 / TypeScript 6 +- Tailwind v4 (`@tailwindcss/vite`, CSS-variable theme, no `tailwind.config.js`) +- TanStack Query v5 (server-state cache) +- TanStack Router (client routing) +- shadcn/ui + `class-variance-authority` + `clsx` + `tailwind-merge` (component layer) +- Zod schemas in the shared package (wire-contract types — the frontend analogue of the backend Contracts assembly) +- react-hook-form + `@hookform/resolvers/zod` (forms with validation) +- Vitest 4 + Testing Library + jsdom (unit/component tests) +- Playwright (end-to-end, in the `e2e` workspace) +- ESLint flat config (`typescript-eslint`) +- Node `>=22` (engine floor) + +**Live-update transport:** SignalR (`@microsoft/signalr` v10) via the transport-agnostic push→Query-cache-bridge pattern from CritterBids. Components call `useListen`/`useConnectionState` and read from the TanStack Query cache; the SignalR transport is plugged in at the `createConnection` seam and is not visible to component code. See ADR-016. + +**App structure:** audience-SPA monorepo with three independently-buildable SPAs sharing a contracts/theme/transport core: + +- `rider/` — rider-facing app (trip booking, live trip tracking) +- `driver/` — driver-facing app (offer receipt, trip mode, GPS context) +- `operations/` — internal ops dashboard (live map, manual admin commands) +- `shared/` — shared contracts package, SignalR provider factory, Tailwind v4 theme (see §Open Questions for package-shape decision) +- `e2e/` — Playwright harness + ### Deliberately Not Using - **RabbitMQ.** Not because it is inappropriate for the domain, but because the showcase goals favor breadth of transport experience; Kafka and ASB earn the slots. @@ -176,14 +204,10 @@ The following principles guide decisions when they come up. They are descriptive The following decisions are intentionally deferred. Revisiting them too early risks premature commitment; revisiting them too late risks drift. Each has a trigger that indicates the right time to pick it up. -**Frontend architecture.** Live-update transport (SignalR, gRPC-web streaming, or WebSockets), component library, and app structure are deferred pending lessons from the CritterBids frontend work. The trigger is: CritterBids lands on a stable live-update pattern, or CritterCab reaches the point where a driver or rider UI is needed to make further backend progress meaningful. - **Microsoft Graph integration depth.** Current plan covers sign-up events only. Full user-lifecycle integration (password resets, MFA enrollment, account blocks) is deferred until the Identity BC is actively being worked. **Operations tenant.** Real workforce Entra tenant setup is deferred. For now, the Operations side is stubbed. The trigger is: Operations BC is actively being built, and the auth story for ops users becomes load-bearing. -**Map library for frontend.** Parked alongside the rest of the frontend. - **Rust as a second polyglot language.** Go is the first polyglot service. Rust remains a candidate for a second service if and when one is motivated by actual need. ## Open Questions @@ -197,6 +221,9 @@ Questions that are currently unresolved and that will resolve through Event Mode - Final form of the conference-demo scenario: pre-seeded simulated drivers only, audience-member drivers in addition, or both? - Final narrative format: freeform markdown with structured headers, a Gherkin-flavored template, or a stricter schema-backed format? The NDD-informed direction is committed; the specific template evolves as we write the first narratives. - **Suspension / reinstatement / deactivation BC placement.** Vision-doc v0.1 places these under Onboarding's lifecycle scope; [Workshop 003](../workshops/003-onboarding-domain-story.md) §5.2 finding B6 surfaced the alternative that the post-approval suspension lifecycle may belong to a Trust & Safety or Operations BC rather than Onboarding (the vetting lifecycle and the disqualification lifecycle share an actor but differ in trigger and reviewer). **Trigger to resolve:** when the suspension lifecycle is first modeled (likely a future workshop after Onboarding's W004). +- **Map library for the frontend.** Ride-sharing is map-centric: live driver positions, pickup/dropoff pins, route overlays, ops live map. Candidates include MapLibre GL, Leaflet, and deck.gl. The choice couples to the backend geospatial representation (H3 cells vs. GeoJSON — see `docs/research/ride-sharing-lessons-learned.md` Lesson 4) and deserves its own evaluation once that representation is settled. Neither sibling repository (mmo-reconnect, CritterBids) has a geospatial UI, so no tested choice can be inherited. **Trigger to resolve:** a dedicated map-library spike session, after the Telemetry BC's geospatial representation is decided. +- **Shared contracts package shape: one `@crittercab/shared` or per-BC packages?** CritterBids uses a single shared package. CritterCab's separately-deployed-services stance and BC-owned-language convention may argue for per-published-language packages instead, embodying in TypeScript what the context map says in DDD vocabulary. This question only becomes load-bearing when the first frontend package is bootstrapped. **Trigger to resolve:** the first frontend implementation session. +- **Frontend monorepo or separate frontend repos?** The `rider/driver/operations` SPA structure adopted in v0.6 co-locates three SPAs in one npm-workspaces monorepo (mirroring CritterBids). CritterCab's "separately deployable per BC" ethos raises the question of whether the frontends should likewise be separable — and whether a shared package across repo boundaries is worth the tooling cost. **Trigger to resolve:** the first frontend implementation session, once the deployment model for the frontend surfaces is clearer. ## Related Documents @@ -220,3 +247,4 @@ Cross-cutting: - **v0.3** (2026-04-23): Cross-referenced ADRs 001–009 throughout. Committed Azure as the deployment platform (ADR-007) and Azure Service Bus as a planned transport (ADR-005). Removed resolved items from Open Questions and Explicitly Parked. Added ADR references to Design Principles. - **v0.4** (2026-05-19): Added cross-reference to the new [`docs/context-map/README.md`](../context-map/README.md) foundation artifact from §Methodology's DDD strategic-design bullet. Closes the "first-class context map" methodology commitment that has been open since v0.1; the artifact rolls up cross-BC relationships from ADRs 006, 013, 014 and Workshops 001 and 002 into a single named place using DDD strategic-design vocabulary. - **v0.5** (2026-05-26): Marked Domain Storytelling as **Exercised** per [Workshop 003 — Onboarding Domain Story](../workshops/003-onboarding-domain-story.md), closing the "Pilot Domain Storytelling" methodology commitment that has been open since v0.1. DS committed as a permanent design-phase technique alongside Event Modeling. Adds one new open question (suspension / reinstatement / deactivation BC placement) surfaced by W003 §5.2 finding B6. +- **v0.6** (2026-06-16): Unparked the frontend architecture. The vision's explicit unpark trigger — "CritterBids lands on a stable live-update pattern" — was discharged by the [sibling-repo frontend survey](../research/frontend-survey-sibling-repos.md) (2026-06-16), which confirmed that `CritterBids/client/shared/src/signalr/provider.tsx` is a generic, packaged `createSignalRProvider` shared across three SPAs. Adopted the convergent house stack (React 19 / Vite 8 / TS 6 / Tailwind v4 / TanStack Query) and the audience-SPA monorepo shape (`rider/driver/operations`) as working direction in §Tentative Technology Stack. Added ADR-016 (Frontend Live-Update Transport) — SignalR as the browser-client push transport, transport-agnostic push→Query-cache-bridge architecture. Removed "Frontend architecture" and "Map library for frontend" from §Explicitly Parked; added three new §Open Questions entries (map library, contracts-package shape, monorepo vs. separate repos). Drove by [`docs/prompts/frontend-architecture-unpark.md`](../prompts/frontend-architecture-unpark.md).