Skip to content

Nutrition / Communication / Coach Review

Meal swap requests let clients ask for plan changes without auto-prescribing replacements.

The V1 baseline supports a client-initiated request from an active meal plan, optional reason and note, optional client-proposed food, coach review from the task inbox or client nutrition context, and resolution by approval, rejection, message, or future plan update. Approved requests can become a logged swap for the affected meal, but the system does not automatically suggest a replacement or mutate the published plan.

Page status

  • Nutrition
  • Client request
  • Coach review
  • Task inbox
  • Audit trail

Boundary decision

Boundary decision: Nutrition owns the request record, plan/log references, approval state, macro comparison snapshot, and accepted-food handoff. Communication owns conversation messages and attachments. CoachWorkflow owns task inbox projections. DailyExecution consumes resolved state for Today and quick-log behavior.

Product decision

Product decision: meal swap requests are not automated nutrition recommendations in Public V1. The client can request, explain, and optionally propose; the coach decides whether to approve, reject, message, or update a future meal plan version.

Meal Swap Flows

Requests are lightweight for clients, explicit for coaches, and traceable for nutrition history.

Client Requests A Swap

  1. Client opens a meal from Today or the active meal plan and taps request swap for a meal slot or planned food portion.
  2. Client selects a reason such as unavailable, disliked, allergy concern, travel, schedule issue, cost, or other.
  3. Client may add a note, propose a replacement food or restaurant item, attach a photo later, and mark urgency for today only when the workspace allows it.
  4. Mobile stores the request locally with a stable client request id, visible plan snapshot, selected planned portion ids, and device timestamp.
  5. Backend accepts the request idempotently, attaches it to the relationship and plan version, and returns the canonical request state.

Coach Reviews Request

  1. Coach sees the request in task inbox, client timeline, nutrition review, and active meal plan context.
  2. Review shows original meal, requested change, client note, recent adherence, target impact estimate, allergies/restrictions, and whether the request is for today or future planning.
  3. Coach can approve a concrete replacement, ask a question, reject with a client-visible reason, mark solved by message, or start a meal-plan draft from the active version.
  4. Approval records coach-selected food, serving, nutrient snapshot, visibility note, and whether it may be reused as a future approved alternative.
  5. Resolution emits events that update the client mobile request state, task inbox, daily log context, and communication thread when a message is sent.

Approved Swap Becomes A Log

  1. If the request targets today's pending meal, the client can accept the coach-approved swap and log the meal from the same screen.
  2. The meal intake line stores the original planned portion id, approved replacement snapshot, swap request id, quantity, and nutrient snapshot used for calculations.
  3. If the client already logged the meal, coach approval is informational unless the coach or client creates an explicit correction inside the allowed window.
  4. Daily aggregates rebuild from the accepted food intake lines; the swap request itself is not counted as intake until a log or correction references it.
  5. Historical plan versions remain immutable. Repeated requests can inform a future draft but do not rewrite the old plan.

Request Expires Or Is Closed

  1. Same-day requests move to overdue after the meal window or local day grace period if the coach has not resolved them.
  2. Client can cancel a pending request while it has no coach decision, leaving a canceled audit state and clearing task inbox projections.
  3. Coach can close duplicates by linking them to the original request or marking no action needed with a reason.
  4. When a plan version is superseded, open requests remain readable but require coach confirmation before applying to the new plan.
  5. Closed relationships follow retention and anonymization rules shared with nutrition logs and communication data.

Domain Model

Meal swap requests are reviewable workflow records connected to immutable plan snapshots and optional daily log facts.

Aggregates & Entities

  • MealSwapRequest: aggregate root for client request identity, relationship scope, plan references, status, reason, notes, urgency, and resolution.
  • MealSwapTarget: value-bearing child describing the meal slot, planned portion ids, original food snapshots, target local date, and plan version.
  • MealSwapProposal: optional client-proposed replacement food, manual entry, barcode, restaurant text, or photo attachment reference.
  • MealSwapCoachDecision: approval, rejection, question, solved, duplicate, or plan-update decision with actor, timestamp, note, and visibility.
  • ApprovedSwapReplacement: coach-selected replacement food or recipe snapshot, serving quantity, nutrient snapshot, source confidence, and reuse policy.
  • MealSwapTaskProjection: rebuildable task inbox row for pending, urgent, overdue, and needs-response requests.
  • MealSwapAuditEntry: append-only state transitions for support, privacy exports, and dispute review.

Value Objects & Rules

  • MealSwapStatus: PENDING, QUESTION_SENT, APPROVED, REJECTED, SOLVED, CANCELED, DUPLICATE, EXPIRED.
  • MealSwapReason: unavailable, disliked, allergy concern, dietary restriction, schedule, travel, cost, appetite, restaurant, or other.
  • SwapScope: planned food portion, entire meal slot, today only, date range, or future plan suggestion.
  • MacroImpactSnapshot: comparison of original and replacement energy, protein, carbohydrate, fat, and coach-selected nutrients when enough data exists.
  • Only pending or question-sent requests can be canceled by the client.
  • A coach approval must include a concrete replacement snapshot before it can be used by a food intake line.
  • Request resolution does not mutate NutritionPlanVersion, MealSlotTemplate, PlannedFoodPortion, or MealAlternative.
  • Repeated requests can create coach review signals, but recommendations and future plan changes remain coach-authored.
ModelOwned ByImportant MethodsEmits
MealSwapRequestNutritioncreate(), cancel(), askQuestion(), approve(), reject(), markSolved(), expire()MealSwapRequested, MealSwapResolved
MealSwapTargetNutritionfromPlanSnapshot(), assertStillVisible(), describeOriginalFoods()None
ApprovedSwapReplacementNutritionfromCoachSelection(), compareToOriginal(), toFoodIntakeLine(), toMealAlternativeDraft()MealSwapReplacementApproved
MealSwapTaskProjectionCoachWorkflow projectionupsertPending(), markResolved(), markOverdue(), rebuildForRelationship()CoachTaskProjectionChanged

Table Structure

Store requests separately from published plan alternatives and accepted food intake lines.

TablePurposeImportant ColumnsConstraints & Indexes
meal_swap_requestsClient-created request workflow root.id, client_request_id, workspace_id, relationship_id, client_user_id, plan_version_id, daily_nutrition_log_id, local_date, timezone, scope, reason, status, urgency, client_note, created_at, resolved_at, canceled_atUnique (relationship_id, client_request_id); index pending by (workspace_id, status, created_at); index client history by relationship and date desc.
meal_swap_targetsOriginal meal or food-line snapshot the client wants changed.id, request_id, meal_slot_template_id, meal_slot_key, planned_food_portion_id, target_kind, original_snapshot jsonb, sort_orderAt least one meal slot or planned portion reference; retain snapshots even when plan is superseded.
meal_swap_proposalsClient-proposed replacement or context.id, request_id, proposal_type, food_item_id, serving_unit_id, quantity, free_text, barcode, attachment_id, selection_snapshot jsonb, nutrient_estimate jsonb, source_confidenceOptional per request; food references are suggestions until coach approval creates a replacement snapshot.
meal_swap_decisionsCoach decision history for the request.id, request_id, decision, coach_user_id, message_id, client_visible_note, internal_note, duplicate_of_request_id, created_atAppend-only; latest decision drives request status; support access to internal notes is audited.
meal_swap_replacementsCoach-approved replacement snapshot usable by meal logging.id, request_id, decision_id, food_item_id, recipe_id, serving_unit_id, quantity, gram_weight_snapshot, selection_snapshot jsonb, nutrient_snapshot jsonb, macro_impact_snapshot jsonb, reuse_policyRequired when decision is approved for logging; immutable after creation.
meal_swap_audit_entriesAppend-only audit of state changes and actor actions.id, request_id, actor_user_id, actor_kind, action, from_status, to_status, metadata jsonb, created_atRetain with nutrition audit policy; redact notes during privacy purge according to retention rules.
food_intake_linesExisting Nutrition accepted intake table gains swap references.Add nullable meal_swap_request_id, meal_swap_replacement_id, and swap_decision_id when a logged line comes from an approved request.Use nullable UUID references across the Nutrition context; daily aggregates rebuild from intake lines, not request rows.
mobile_nutrition_sync_receiptsExisting receipt table for replay-safe request operations.relationship_id, device_id, client_operation_id, operation_type, canonical_record_id, accepted_revision, payload_checksum, accepted_atUse operation_type = MEAL_SWAP_REQUEST and MEAL_SWAP_CANCEL; unique (relationship_id, device_id, client_operation_id).

Note

Client SQLite mirrors active meal swap requests, pending local operations, coach decisions visible to the client, approved replacement snapshots, and sync receipts. It should not mirror internal coach notes, support audit metadata, or task inbox scoring details.

Lifecycle Policy

The request lifecycle is separate from plan versioning and from accepted daily intake facts.

Request States

  • PENDING: accepted by the server and waiting for coach action.
  • QUESTION_SENT: coach asked for clarification through Communication; request remains open.
  • APPROVED: coach created an approved replacement snapshot; client may log it if the target meal is still mutable.
  • REJECTED: coach declined with an optional client-visible reason.
  • SOLVED: coach handled outside approval, usually by message or future plan update.
  • CANCELED, DUPLICATE, and EXPIRED: terminal non-approval states retained for history and analytics.

Versioning And History

  • Requests reference the plan version and meal snapshot visible when the client created the request.
  • Published plan versions stay immutable. A request may start a new draft, but draft publication is a separate meal-plan builder workflow.
  • Approved replacement snapshots are immutable and can be used only by authorized meal logs or corrections.
  • If the client logs before coach response, the request can still be resolved for audit or future planning without changing accepted intake.
  • Corrections create normal nutrition log corrections; the swap request record remains the decision trail.

Concurrency And Offline

  • Client creation and cancellation use stable operation ids and mobile sync receipts.
  • Coach decisions lock the request row and reject stale status transitions with the latest request summary.
  • A request created offline against a superseded plan can be accepted as historical context, but applying it to the current plan requires coach confirmation.
  • Duplicate pending requests for the same meal and date return the existing request unless the client explicitly submits a new note after a cooldown.
  • Urgent same-day requests have a configurable local-day expiry and overdue task-inbox state.

Retention

  • Request rows are soft-hidden only through terminal states, not deleted from normal history while related logs or messages exist.
  • Attachments follow Communication and media retention rules; request records store attachment ids rather than binary data.
  • Client-visible notes remain exportable with nutrition history. Internal notes are restricted and audited.
  • Privacy purge anonymizes notes, proposal text, and audit actor ids after the workspace retention window while preserving aggregate counts.

API Contracts

Client endpoints create and track requests; coach endpoints resolve them and optionally convert approval into log-ready snapshots.

EndpointPurposeRequestResponse
POST /api/v1/client/nutrition/meal-swap-requestsCreate a request from an active meal plan or Today meal.Device id, client request id, plan version id, local date, meal slot key, planned portion ids, reason, urgency, note, optional proposal.Canonical request dto, sync receipt, task state, duplicate hint when applicable.
GET /api/v1/client/nutrition/meal-swap-requests?status=openClient reads open and recent request history.Status filter, date range, cursor, optional include resolved.Request list with visible coach decisions, approved replacement snapshots, and sync state.
POST /api/v1/client/nutrition/meal-swap-requests/{requestId}/cancelClient cancels a pending or question-sent request.Device id, client operation id, reason note, expected status.Updated request dto, sync receipt, task inbox clear hint.
GET /api/v1/coach/relationships/{relationshipId}/nutrition/meal-swap-requestsCoach reviews request queue for one client.Status, local date range, urgency, plan version, cursor.Requests with original meal snapshots, proposal summaries, macro estimates, and latest task state.
GET /api/v1/coach/nutrition/meal-swap-requests/inboxCoach task inbox list across authorized relationships.Status, overdue flag, assigned coach, workspace, cursor.Prioritized request cards, client summary, target meal, age, and quick actions.
POST /api/v1/coach/nutrition/meal-swap-requests/{requestId}/approveCoach approves a replacement.Replacement food or recipe, serving quantity, client-visible note, reuse policy, optional message flag, expected status.Approved request dto, replacement snapshot, macro impact, Today/logging refresh hints.
POST /api/v1/coach/nutrition/meal-swap-requests/{requestId}/rejectCoach rejects a request.Client-visible reason, optional internal note, optional message flag, expected status.Rejected request dto, task inbox completion, notification hint.
POST /api/v1/coach/nutrition/meal-swap-requests/{requestId}/questionCoach asks the client for clarification.Message body, optional attachment ids, expected status.Question-sent request dto, conversation message id, notification hint.
POST /api/v1/coach/nutrition/meal-swap-requests/{requestId}/solveCoach closes without explicit approval or rejection.Resolution reason, client-visible note, optional linked plan draft id, expected status.Solved request dto and task inbox completion.
POST /api/v1/client/nutrition/logs/{logId}/meals/{mealLogId}/apply-approved-swapClient logs a coach-approved replacement.Device id, client operation id, request id, replacement id, quantity confirmation, expected revision.Accepted meal log, food intake lines, recalculated daily totals, sync receipt.

Key Code Snippets

Implementation sketches show the request schema, creation use case, coach approval use case, and policy boundary.

Drizzle schema shape

apps/api/src/nutrition/db/meal-swap-requests.schema.ts

ts
export const mealSwapRequests = pgTable("meal_swap_requests", {
  id: uuid("id").primaryKey().defaultRandom(),
  clientRequestId: uuid("client_request_id").notNull(),
  workspaceId: uuid("workspace_id").notNull(),
  relationshipId: uuid("relationship_id").notNull(),
  clientUserId: uuid("client_user_id").notNull(),
  planVersionId: uuid("plan_version_id").notNull(),
  dailyNutritionLogId: uuid("daily_nutrition_log_id"),
  localDate: date("local_date").notNull(),
  timezone: text("timezone").notNull(),
  scope: text("scope").$type<SwapScope>().notNull(),
  reason: text("reason").$type<MealSwapReason>().notNull(),
  status: text("status").$type<MealSwapStatus>().notNull().default("PENDING"),
  urgency: text("urgency").$type<MealSwapUrgency>().notNull().default("NORMAL"),
  clientNote: text("client_note"),
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
  resolvedAt: timestamp("resolved_at", { withTimezone: true }),
  canceledAt: timestamp("canceled_at", { withTimezone: true }),
}, (table) => ({
  clientRequestUnique: uniqueIndex("meal_swap_requests_client_request_unique")
    .on(table.relationshipId, table.clientRequestId),
  pendingInboxIdx: index("meal_swap_requests_pending_inbox_idx")
    .on(table.workspaceId, table.status, table.createdAt),
  relationshipHistoryIdx: index("meal_swap_requests_relationship_history_idx")
    .on(table.relationshipId, table.localDate),
}));

export const mealSwapReplacements = pgTable("meal_swap_replacements", {
  id: uuid("id").primaryKey().defaultRandom(),
  requestId: uuid("request_id").notNull(),
  decisionId: uuid("decision_id").notNull(),
  foodItemId: uuid("food_item_id"),
  recipeId: uuid("recipe_id"),
  servingUnitId: uuid("serving_unit_id").notNull(),
  quantity: numeric("quantity", { precision: 12, scale: 4 }).notNull(),
  gramWeightSnapshot: numeric("gram_weight_snapshot", { precision: 12, scale: 4 }),
  selectionSnapshot: jsonb("selection_snapshot").$type<FoodSelectionSnapshot>().notNull(),
  nutrientSnapshot: jsonb("nutrient_snapshot").$type<NutrientSnapshot>().notNull(),
  macroImpactSnapshot: jsonb("macro_impact_snapshot").$type<MacroImpactSnapshot>().notNull(),
  reusePolicy: text("reuse_policy").$type<SwapReusePolicy>().notNull(),
});

Create request use case

apps/api/src/nutrition/use-cases/create-meal-swap-request.ts

ts
@Injectable()
export class CreateMealSwapRequestUseCase {
  constructor(
    private readonly requests: MealSwapRequestRepository,
    private readonly plans: NutritionPlanVersionRepository,
    private readonly receipts: NutritionSyncReceiptRepository,
    private readonly policy: MealSwapPolicy,
    private readonly tx: TransactionRunner,
    private readonly outbox: OutboxPort,
  ) {}

  async execute(command: CreateMealSwapRequestCommand, actor: Actor): Promise<MealSwapRequestDto> {
    return this.tx.run(async () => {
      const duplicate = await this.receipts.findAccepted(command.idempotencyKey());
      if (duplicate) return this.requests.getDto(duplicate.canonicalRecordId);

      const plan = await this.plans.getPublishedSnapshot(command.planVersionId);
      this.policy.assertClientCanRequestSwap(actor, plan, command);

      const request = MealSwapRequest.create({
        relationshipId: plan.relationshipId,
        clientUserId: actor.userId,
        target: MealSwapTarget.fromPlanSnapshot(plan, command.target),
        reason: command.reason,
        urgency: command.urgency,
        clientNote: command.clientNote,
        proposal: command.proposal,
      });

      await this.requests.save(request);
      await this.receipts.accept(command.toReceipt(request.id, request.revision));
      await this.outbox.publish(request.pullDomainEvents());

      return MealSwapRequestDto.fromDomain(request);
    });
  }
}

Coach approval use case

apps/api/src/nutrition/use-cases/approve-meal-swap-request.ts

ts
@Injectable()
export class ApproveMealSwapRequestUseCase {
  constructor(
    private readonly requests: MealSwapRequestRepository,
    private readonly foodSelection: FoodSelectionSnapshotPort,
    private readonly macroImpact: MealSwapImpactService,
    private readonly policy: MealSwapPolicy,
    private readonly tx: TransactionRunner,
    private readonly outbox: OutboxPort,
  ) {}

  async execute(command: ApproveMealSwapCommand, actor: Actor): Promise<MealSwapRequestDto> {
    return this.tx.run(async () => {
      const request = await this.requests.getForUpdate(command.requestId);
      this.policy.assertCoachCanResolveSwap(actor, request);
      request.assertStatus(command.expectedStatus);

      const replacement = await this.foodSelection.createSnapshot(command.replacement, actor);
      const impact = this.macroImpact.compare(request.originalSnapshots(), replacement);

      request.approve({
        coachUserId: actor.userId,
        replacement,
        macroImpact: impact,
        clientVisibleNote: command.clientVisibleNote,
        reusePolicy: command.reusePolicy,
      });

      await this.requests.save(request);
      await this.outbox.publish(request.pullDomainEvents());

      return MealSwapRequestDto.fromDomain(request);
    });
  }
}

Access policy

apps/api/src/nutrition/policies/meal-swap.policy.ts

ts
export class MealSwapPolicy {
  assertClientCanRequestSwap(actor: Actor, plan: NutritionPlanVersion, command: CreateMealSwapRequestCommand): void {
    actor.requireRole("CLIENT");
    actor.requireRelationshipParticipant(plan.relationshipId);
    plan.assertVisibleToClient();
    plan.assertContainsTarget(command.target);
  }

  assertClientCanCancel(actor: Actor, request: MealSwapRequest): void {
    actor.requireRole("CLIENT");
    actor.requireRelationshipParticipant(request.relationshipId);
    request.assertCancelableByClient(actor.userId);
  }

  assertCoachCanResolveSwap(actor: Actor, request: MealSwapRequest): void {
    actor.requireRole("COACH");
    actor.requireCoachRelationshipAccess(request.relationshipId);
    actor.requirePermission("nutrition.swap.resolve");
    request.assertOpenForCoachDecision();
  }

  assertClientCanApplyApprovedSwap(actor: Actor, request: MealSwapRequest, log: DailyNutritionLog): void {
    actor.requireRole("CLIENT");
    actor.requireRelationshipParticipant(request.relationshipId);
    request.assertApproved();
    log.assertClientMutable();
    log.assertSameRelationship(request.relationshipId);
  }
}

Events & Background Jobs

Meal swap events keep task inbox, mobile sync, communication, and nutrition logs coordinated without coupling their aggregates.

Emitted Events

  • MealSwapRequested: creates task inbox projection, notifies assigned coach when policy allows, and updates client request list.
  • MealSwapQuestionSent: links a Communication message and keeps the request open.
  • MealSwapReplacementApproved: updates client mobile request state and enables apply-approved-swap action for mutable meals.
  • MealSwapRejected: closes coach task projection and sends client-visible rejection note when configured.
  • MealSwapResolved: closes inbox item, records audit entry, and updates client history.
  • ApprovedMealSwapAppliedToLog: rebuilds daily nutrition aggregates and marks request as used for that log.

Jobs And Projectors

  • Task inbox projector prioritizes pending, urgent, overdue, and repeated swap requests per coach relationship.
  • Same-day expiry worker marks unresolved today-only requests expired after meal window and grace period.
  • Repeated-request detector summarizes foods or meal slots frequently requested for swap to inform future plan reviews.
  • Mobile notification worker sends request decision updates without exposing internal coach notes.
  • Plan supersession watcher flags open requests attached to old plan versions so coaches can re-evaluate before applying.
  • Retention job redacts old notes and proposal free text according to workspace privacy policy.

Permissions & Access Rules

Meal swap requests expose food preferences, restrictions, and coaching decisions, so reads and writes stay relationship-scoped.

Client

  • Client can create requests only for visible active or historically visible plan snapshots in their own relationship.
  • Client can cancel only pending or question-sent requests they created.
  • Client can read request status, client-visible coach notes, approved replacement details, and their own proposal history.
  • Client cannot approve their own swap, see internal coach notes, access task inbox priority, or apply a replacement after the meal/log correction window closes.
  • Offline operations must include device id, stable client operation id, plan version id, target ids, and expected request status when mutating an existing request.

Coach And Support

  • Coach can resolve requests only for relationships they manage and workspaces where they have nutrition.swap.resolve.
  • Coach can approve a replacement only after choosing or entering a concrete food or recipe snapshot.
  • Coach cannot rewrite the client's original note or silently mutate accepted meal logs through request resolution.
  • Coach mobile may resolve straightforward requests, while complex plan changes can deep link into meal plan builder drafts.
  • Support/admin access to internal notes, audit trail, deleted attachments, or sync payloads requires explicit permission and audited reason.

Web, Coach Mobile, Client Mobile

Client mobile focuses on quick request capture; coach surfaces focus on context, decision quality, and follow-up.

Coach Web

  • Task inbox cards show client, meal, request age, reason, urgency, original meal, proposal, and recent adherence context.
  • Nutrition review can approve with food picker, compare macro impact, send a message, reject, close duplicate, or start a plan draft.
  • Client profile context surfaces allergies, dietary restrictions, disliked foods, and previous swap patterns during review.
  • Plan builder can optionally import approved replacement as a future alternative only through an explicit draft change.

Coach Mobile

  • Reads pending request cards, client note, target meal, and quick macro estimate.
  • Supports approve with a simple saved food or text replacement, reject, ask question, message, and mark solved.
  • Defers complex food search, recipe selection, and future plan redesign to web when the mobile flow cannot validate cleanly.
  • Push notifications deep link to the request and refresh stale status before allowing a decision.

Client Mobile

  • Request swap entry point appears in meal detail for active planned foods and meal slots.
  • Form captures reason, optional note, optional proposed replacement, and attachment placeholder without implying auto-approval.
  • Open request state is visible from Today and nutrition history: pending, question sent, approved, rejected, solved, canceled, expired.
  • Approved swaps show clear action to log replacement when still allowed; otherwise they remain visible as coach guidance.
  • Arabic/English copy, RTL ordering, dynamic type, long food names, dark mode, and screen-reader status labels are required.

Test Scenarios & V1 Baseline Decisions

Meal swap requests cross mobile sync, coach workflow, nutrition logging, and messaging, so tests need to cover state transitions and stale plans.

Tests

  • Domain unit tests: valid status transitions, cancellation rules, approval requirements, duplicate linking, expiry, and immutable replacement snapshots.
  • Plan reference tests: request targets a visible plan portion, survives plan supersession, and cannot mutate published plan rows.
  • API integration tests: client creates request, retries idempotently, coach approves with replacement, client applies approved swap, and daily aggregates rebuild once.
  • Offline sync tests: queued create and cancel survive restart, duplicate operation ids dedupe, stale expected status rejects safely, and accepted receipts reconcile local state.
  • Task inbox tests: pending request creates a coach task, question keeps it open, resolution closes it, same-day expiry marks overdue then expired.
  • Communication tests: ask-question creates a linked message, message permissions follow relationship access, and internal notes never reach client payloads.
  • Access tests: client cannot request for another relationship, coach cannot resolve outside workspace, support raw payload reads are audited.
  • Mobile E2E tests: client requests a same-day swap offline, reconnects, coach approves, client logs approved replacement, and coach summary shows swapped meal.
  • Localization and accessibility tests: Arabic/English reasons, RTL request form, dynamic type, screen-reader labels for pending/approved/rejected, and dark mode contrast.

Resolved V1 Baseline Decisions

  • Seeded request reasons are unavailable, disliked, allergy concern, dietary restriction, schedule, travel, cost, appetite, restaurant, and other. Workspaces can hide or reorder seeded reasons in Public V1, but custom canonical reasons are post-V1; OTHER captures free-text context.
  • Same-day urgent requests create a coach inbox item and one immediate rate-limited push when the assigned coach has the category enabled and quiet hours allow delivery. Non-urgent requests and quiet-hour-suppressed urgent requests remain in the inbox and digest flow.
  • Public V1 client proposals support free text and attachment/photo placeholders only. Barcode and provider-food lookup are disabled until external food provider integration ships, while nullable proposal columns remain for the future contract.
  • Macro-impact warnings are advisory rather than blockers: missing or low-confidence nutrients, more than 20% calorie or macro difference, or more than 10 g protein difference require coach acknowledgement and a client-visible note. Approval is still blocked for allergy/restriction conflicts, invalid servings, or missing replacement snapshots.
  • Approved replacements can become future approved alternatives only through an explicit meal-plan draft change. They are never inserted automatically and never mutate the published plan version that produced the request.
  • Same-day unanswered requests become overdue at the target meal window end, or local day end when no window exists, and expire at 06:00 the next local day or immediately when the meal log is reviewed or locked. Expired requests remain readable history but cannot be applied to Today or meal-log actions.

CoachMe internal planning documentation.