Skip to content

V3 Vision

V3 ideas captured during the requirements session. None of these block V2; the V2 data model is shaped to accommodate them when they land. Cross-cuts: V2 scope (Could list).

A separate, light client-side surface where a player — using only the email captured on their Player record — can:

  • Receive a magic-link login (no password, no signup form).
  • View their own stringing history: list of past orders, dates, racket, strings, total.
  • Re-download any receipt PDF for an order they own.
  • See a "your racket is ready" status when the stringer has marked it Strung.
  • Update their notification preferences (email channel opt-in/out, future SMS).

Read-only on order data. The stringer remains the single writer.

C2. Notification dispatch (auto pickup-ready)

The data model for notifications is captured in V2 (see data model — Player notification_prefs, Stringer notification template, per-client overrides). What V3 adds is the actual send path:

  • Trigger: auto-fire on Strung AND manual "notify" button — both available.
  • Channels: email default; SMS only if free / near-free integration exists.
  • Content: stringer-level template (editable), per-client override, includes the receipt.
  • Per-client preferences: opt-in/out per channel, stored on the Player.

V2 already exposes the manual flow via the Strung event + emailed receipt (M14). V3 turns that into a configurable, automatable notification pipeline.

C3. Bulk historical-data import

Today (V2): a new stringer onboarded by Stefan starts with an empty slate. There is no import path on signup.

V3 adds bulk import for stringers arriving with their own history (XLSX / CSV / JSON). The data shape Stefan migrates from his own XLSX in V2 (M15) becomes the reference template — once the V2 migration tooling is stable, generalising it for arbitrary stringers is the V3 step.

C4. PWA / offline order entry

Online-only at the stringing table is acceptable for V2. V3 candidate: a Progressive Web App shell with offline order entry + deferred sync, useful for courtside data capture in spotty-connectivity venues.

Out of V2 and V3 — see V2 scope, Won't

Online payments, native mobile apps, and a partial-return / warranty workflow are explicitly out of both V2 and V3 scope.


V3 sign-off workflow & channel exploration — scoped, answers captured

Captured 2026-05-01 from Stefan; open questions answered the same day. Still deferred to the V3 milestone — not part of the 2026-04-27 V2 sign-off, and no V2 schema/UX ships from this section. But the design is no longer hand-wavy: the decisions below are the acceptance criteria V3 issues will reference.

V2 hooks to consider landing now

To avoid a V3 migration, the V2 schema/UX should reserve a few fields even if behavior is V3. Theo / Atlas to react:

  • Player.notification_channel_pref — already implied by notification_prefs (email/SMS); explicitly name and document the channel set as extensible (email | sms | whatsapp | telegram | …) so adding a channel in V3 is data-only, not schema-change.
  • Player.signoff_pref — client-side preference: require | skip (default require per Stefan's locked answer Q4 — "default every order, client can opt out". V2 ships the column with require even though no V2 UI surfaces it; the OR-logic in V3 will then trigger sign-off for every player by default once C1 lights up).
  • Stringer.signoff_default — stringer-level default: require | skip. Default require in V2 (matches Q4).
  • Stringer per-client signoff_override — per-Player override of the stringer default. Could live as a column on Player owned by the stringer side (e.g. stringer_signoff_override nullable). Distinct from signoff_pref which is the client's own preference.
  • Order.signed_off_at + Order.signoff_required (snapshot at order creation) — even if V2 never sets signed_off_at, having the columns reserved means V3 can light up the workflow without touching existing rows.

If these don't land in V2, V3 forces a schema migration on a live multi-stringer dataset. Cheap to add now, expensive to add later.

B1. Sign-off workflow (extends C1 + C2)

When a stringer enters an order, the client receives a notification (via their preferred channel — see B2) and signs off on the order: confirms racket, strings, tension, and price. Either party can opt out of the workflow.

Default = sign-off required for every order (Stefan, Q4). Either side can opt out; mutual opt-out is what suppresses sign-off. Because Player records exist per-stringer, the client opt-out is per-stringer by construction (the "trust this stringer" toggle is the same control).

Two settings, OR-logic:

Setting Owner Where Default Meaning
Player.signoff_pref Client V3 client portal (C1) — "Require sign-off on my orders" toggle require Client-expressed preference. Per-stringer (Player rows are per-stringer).
Player.stringer_signoff_override Stringer Stringer's per-client view in main app nullable (= use stringer default) Stringer's per-client override.
Stringer.signoff_default Stringer Stringer settings require Falls through when override is null.

Decision rule (load-bearing — keep this exact):

Sign-off is required iff client_pref == require OR stringer_effective == require. Mutual opt-out is the only state that skips it.

Either side opting in wins. This lets cautious clients force sign-off even if their stringer wouldn't bother, and lets cautious stringers force sign-off even on clients who don't care.

Effective stringer setting = stringer_signoff_override if set on the Player row, else Stringer.signoff_default.

Sign-off artifact: client confirms in the portal (C1) → Order.signed_off_at is stamped.

Decisions locked 2026-05-01 (Stefan):

# Decision Resolution
Q1 Block effect Advisory only. Sign-off does not block the Strung or Paid transitions. UI must clearly highlight unsigned-off orders so the stringer sees the state.
Q2 Timeout Never expires. If the stringer marks the order Strung anyway, status is Strung but an indicator on the order shows it was never signed off.
Q3 Mutability after sign-off Editable. Once the stringer confirms a new setup, the prior sign-off is revoked and re-triggered — client must sign off again on the changed order.
Q4 Default scope Sign-off required for every order by default. Either side can opt out (per-stringer for clients, since Player rows are per-stringer).
Q5 What client sees Racket, Strings, Tension, Price — same fields as the receipt. Client action is approve / reject (read-only on the line items). Reject path: notifies stringer, order parks pending stringer edit (which re-triggers sign-off per Q3).
Q6 Default direction Resolved by Q4 (default = require). The original Q6 phrasing (opt-in vs opt-out asymmetry) is dropped.
Q11 Sign-off action surface C1 portal only for the action itself. Notifications go via the client's preferred channel, but the sign-off click happens in the portal (deep-link). "Reply YES via SMS/WhatsApp" is nice-to-have within V3 if not too complex (Q10) — not a must.
Q12 V3 launch backfill Existing V2 orders are implicitly approved at V3 launch: signed_off_at = strung_at for all already-Strung orders. Forward-only sign-off from launch onwards for new/in-flight orders.

B2. Channel exploration (extends C2)

Currently captured for V3:

  • Email — primary, NFR-2 covers it (Resend free tier — 100/day, 3,000/month per keystone ADR-0005; DKIM/SPF/DMARC handled by Atlas).
  • SMS — S1 in V2 scope; "only if cheap" — cost and provider still pending pricing call. S1 disposition (V2 if cheap, else V3) unchanged by this update.

V3 channel decisions (Stefan, 2026-05-01):

Channel Status Notes
Email In V3 (baseline). Resend per NFR-2 / keystone ADR-0005. Universal fallback target — see Q9 below.
SMS Still pending pricing call. S1 from V2 should-list stays "V2 if cheap, else V3". Twilio / Swiss gateway cost per message at our volume not yet known.
WhatsApp Out of V3 scope. May revisit. Stefan, Q7: cost of WhatsApp Business API not yet decided, "probably won't go that route for now". Struck from V3 channel candidates.
Telegram In V3. Architecture (Q8): one application-wide bot by default; stringer can override and set their own bot for branding. Free Bot API; client must /start the bot once (deep-link from C1 portal).
Signal Out. No public Bot API; no programmatic send path.
Push notifications (PWA) Tied to C4. Defer until PWA path is decided.
In-app inbox (in C1 portal) In V3 (alongside C1). Always-available baseline; no external dependency.
iMessage / RCS Out. No open business API in CH.

Cross-channel decisions:

# Decision Resolution
Q9 Channel fallback On delivery failure of the preferred channel, fall back to email. Email is the universal target; failure of email itself is surfaced as an operational alert (Resend bounce/complaint webhooks per ADR-0005).
Q10 Reply YES via SMS / WhatsApp for sign-off Nice-to-have within V3, not a must. Build only if cheap; otherwise the C1 portal deep-link is the sole sign-off action surface (per B1 / Q11).
Q12-arch Inbound replies / webhook receiver Outbound only — no inbound webhook receiver in V3. Significantly simplifies architecture (no per-channel inbound parsing, no provider webhook surface to operate). Theo flagged this; recorded here as a locked decision. Q10's "reply YES" — if pursued — must be implementable without a general inbound receiver, or it falls out of scope.

Open questions

All 12 open questions from the 2026-05-01 brainstorm are resolved (see B1 and B2 above). No outstanding asks for Stefan in this scope. New asks that surface during V3 design go through normal change-control.