Part 5 of 7 · Menu sync series ~5 min read

How a rejected update gets fixed

A flag lands in the owner’s Slack at 11:04am. The delivery app rejected the short rib’s new price. There’s a Fix button. What happens when they tap it? The honest answer is “it depends on why the channel said no.” This post walks through the three things the system can do on a flagged rejection — retry, edit, or skip — and how the master menu, the channel state, and the audit trail all stay in sync.

Key takeaways

  • Three actions per flag: retry (resend after a fix), edit (correct the value and resend), skip (accept the channel can’t take it).
  • Each action updates the channel state via the adapter and writes an audit row.
  • A successful retry clears the flag; the channel is back in sync with the master menu.
  • Skip is bounded — a long-skipped item keeps showing on the weekly out-of-step report so it isn’t forgotten.
  • The Fix button is a Slack interactive message backed by a Function URL.

Three actions on a flag

Three actions on a flagged rejection A diagram showing one input on the left flowing through a small interactive Slack panel, then branching into three action paths. Far left: a "Flag in Slack DM" box showing a typical rejection message — item name, channel, the value that was rejected, and the channel's reason — with three Slack-button placeholders below: Retry, Edit, Skip. The owner taps one button. The middle column shows the three branches. Branch one, Retry: used when the channel problem has been fixed outside the system, for example a re-authorized token or a platform outage that has passed; on tap, a Function URL Lambda re-runs the same push through the adapter, and on success updates the channel state in the ms-state DynamoDB table, clears the flag, and writes a retried event to the audit trail. Branch two, Edit: opens a Slack modal pre-filled with the rejected value so the owner can correct it — for example fixing a price entered in the wrong units or shortening a name the channel found too long; on save, the Function URL Lambda writes the corrected value to the master menu via the Sheets API and resends through the adapter. Branch three, Skip: accepts that this channel can't take this item right now; the flag is cleared so it stops nagging, but the item-and-channel is recorded as deliberately out of step and keeps appearing on the weekly out-of-step report. The right side shows the convergence: every action writes a row to the ms-audit DynamoDB table with timestamp, item id, channel, action, by-user, and notes. A note at the bottom: Skip doesn't pretend the channel is in sync — it just stops the noise. The weekly report still lists it until it's truly fixed. Flag in Slack DM item, channel, reason [Retry] [Edit] [Skip] Action 1 Retry • Channel issue fixed outside the system • Re-runs the same push • On success, clears flag Action 2 Edit • Modal: correct the value (units, length) • Sheets API updates row then resends Action 3 Skip • Channel can't take it for now • Clears flag, but stays — on the weekly report Audit trail DynamoDB ms-audit timestamp · item_id channel · action · by-user notes Skip doesn’t pretend the channel is in sync — it stops the noise. The weekly report still lists it.
Fig 5. Three actions per flag, three different effects. Retry resends after a fix. Edit corrects the value and resends. Skip accepts the channel can’t take it for now. Every action writes to the audit trail.

Action 1: retry (the “it’s fixed now”)

Most rejections aren’t about the value at all — they’re about the channel. A delivery platform’s access token expired, so every push bounced. The platform had a short outage during lunch. A network hiccup dropped the call. In all of these, the value the system tried to send was perfectly correct; the channel just wasn’t ready to take it.

When the owner has fixed the underlying thing — re-authorized the token, waited out the outage — they tap Retry. The Fix button submits to a Function URL Lambda, which re-runs the exact same push through the adapter, carrying the same idempotency key from Part 4. Three things happen, in order. First, the adapter applies the change and reports success. Second, the ms-state row for that item-and-channel is updated to the new accepted value and the flag is cleared. Third, an action: retried row is written to ms-audit with the user, the timestamp, and the result. The channel is now back in sync with the master menu, and the next run sees it as in sync.

Action 2: edit (the value was wrong)

Sometimes the channel was right to say no. The price went out in dollars where that platform’s API wanted cents. A new dish name ran past the channel’s length cap. A category name didn’t match anything the platform supports. These are real problems with the value, and a plain retry would just bounce again.

Edit opens a small Slack modal pre-filled with the rejected value and the channel’s reason, so the owner can see exactly what to fix. They correct it — usually a one-character change — and hit Save. On save, the Function URL Lambda writes the corrected value to the master menu via the Sheets API (so the fix lives in the source of truth, not just on one channel), then resends through the adapter with a fresh idempotency key. If the correction belongs only on that one channel — a shorter name just for the app that caps length — the edit updates that channel’s formatting template in the voice doc instead, so future changes are formatted right automatically. Either way, the flag clears on the successful resend and an action: edited row lands in the audit trail.

Action 3: skip (the channel just can’t take it)

Sometimes a channel genuinely can’t hold an item and never will. The delivery platform doesn’t support a “chef’s table” category. A dish is only sold dine-in and shouldn’t be on the app at all. A seasonal special is ending tomorrow and isn’t worth chasing onto every channel. The owner doesn’t want to keep being nagged about a rejection they can’t resolve.

Skip writes a row to ms-state marking the item-and-channel as skipped with the date and the owner who skipped it. The flag clears so it stops pinging. But — and this is the important part — the system doesn’t pretend the channel is in sync. The skipped item still shows on the weekly out-of-step report, with a small note (“skipped by owner on 2026-06-17”), so a genuine drift never silently disappears. If the item is later set to stop publishing to that channel in the master menu, the skip resolves naturally and the report drops it. Skip stops the noise without losing the truth.

Every action is logged, every action is reversible

The ms-audit table records every retry, edit, and skip with the user who took the action, the timestamp, the channel, and a snapshot of the value before and after. If a wrong correction gets entered — a price fixed in the wrong direction, a name shortened too far — the owner can run an “undo last action” through a small admin command that reads the previous-state snapshot and restores both the master menu row and the channel. The undo is itself an audit row, so the trail of edits stays clean.

This kind of reversibility matters because the system touches what guests are charged across several platforms at once. The weekly out-of-step report is the safety net: at any moment, the owner can see exactly which places match the master menu and which don’t — and for the ones that don’t, whether it’s a bug to fix or a skip they chose.

Next post: the cost breakdown. The whole pipeline above runs in coffee-money territory at single-restaurant volume; Part 6 explains exactly where the dollars go and why most of the bill is the work of pushing changes, not thinking about them.

All posts