Skip to content

Release: a restaurant gets a restaurant, and a kitchen can say how long it takes - #786

Merged
sridharkalaibala merged 8 commits into
mainfrom
develop
Sep 15, 2026
Merged

sridharkalaibala merged 8 commits into
mainfrom
develop

Conversation

@sridharkalaibala

@sridharkalaibala sridharkalaibala commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Promotes develop to main. Four merged pull requests now, not one - three landed on develop after this was opened, so the description is refreshed to match what the button would actually ship.

Merge with a merge commit, never a squash. The repository ruleset enforces that, so the button should only offer it.

The online ordering channel

#785 - a restaurant that signs up gets a restaurant. It got a coffee shop: "restaurant" pointed at the cafe pack, so a new restaurant had to delete espresso, a smoothie, a croissant and a litre of milk before typing its first dish.

It also fixes why the whole restaurant feature set has been invisible in the product's own demo data. The installer builds each item from a fixed object literal, and a field the pack carries that the literal does not name is dropped silently - the third time that shape has cost this program a feature. diet, prep_minutes, spice_choice, nutrition, food_tags and menu_marks now travel, cleaned by the same rules the item form uses. And dish-icons, written for the seeder and never wired to it, now gives every photo-less demo item a picture read from its own name.

#788 - how long a section takes. The busy-kitchen notice is live and working on production - checked while writing this: capacity: 13, open: 0, busy: false, the whole chain answering on real data. But that shop has 272 dishes and not one prep time, so if the kitchen did fall behind it could say so only in words and never quote a figure. The field has always been on the item form; it is empty because filling it means opening 272 dishes.

Tools → How long a section takes, with the same scope and check-then-apply as the price, stock and spice tools. Only the empty ones by default, because a shop that has hand-tuned a few dishes has done the most valuable work on this field and a blanket write would erase exactly that.

The print roadmap, from another session

#787 - the kitchen queue watches and prints nothing, the shadow step usually skipped. #789 - an order is never left in silence.

Nothing changes for an existing shop

Demo packs are installed at signup or on an explicit reseed, so no live catalogue is touched by this merge. The two new catalogue tools do nothing until a shop opens them.

Verified before opening

Root suite 2,633 of 2,636 on the develop tip; the three remaining are the release-artifact tests that need frontend/public built, which CI does. API suite 10,741 of 10,741.

It got a coffee shop. "restaurant" pointed at the cafe pack - espresso, a
smoothie, a croissant and a litre of milk - because that was the closest
catalogue that existed, and the note said so in as many words: "Closer
than groceries, which is where it landed before." Closer is not right. A
restaurant had to delete fifteen coffee-shop products before it could
type its first dish.

The pack alone would not have been enough. The installer builds each item
from a fixed object literal, and a field the pack carries that the
literal does not name is dropped on the way in, silently - the third time
this shape has cost this program a feature, after the ordering catalogue
dropped the dish facts and then the market price. So diet, prep time,
nutrition, tags, marks and the spice choice are carried through, cleaned
by the same rules the item form uses so a pack cannot smuggle in a claim.

That matters because a menu is not a price list. Without those fields the
ordering page has nothing to filter by, no prep time to show, no badge to
earn and no spice choice to offer, and the demo demonstrates a column of
names - which is what the restaurant already had on paper.

The dishes carry what a real half-filled menu carries: veg marks on all of
them, prep times on nearly all, nutrition on six, and a spice choice on
seven. Deliberately not on the dal or the biryani, which a kitchen makes
once in one pot and cannot vary by the portion - the menu shows a curry
that cannot be made mild sitting beside ones that can.

Also wires dish-icons into the seeder, which was written for this and
never connected. Every demo item without a photograph now gets an icon
read from its own name, right 22 times out of 22 on this pack and on
every cafe and bakery product too.

The restaurant pack has no photographs. There is no licensed food
photography for these dishes in the manifest and a code change cannot
invent any, so the test that asks for real images now exempts it by name
and says why, rather than quietly passing.
A restaurant that signs up gets a restaurant
Step 1 of Stage 2 in the print roadmap, and the step usually skipped:

  "Write print_jobs rows and drain nothing. Compare, for a week, what the queue
  says should print against what actually printed. Any disagreement is a bug
  found before it can cost anything."

WHY IT IS WORTH A WHOLE CHANGE TO PRINT NOTHING

Ninety shops feed their kitchens through multiKitchenPrint. The failure mode of
a bad cutover is not an error message - it is a dish nobody made, or two dishes
where one was ordered. Owner: "printing dont bring me new issues. keep changes
safely."

So the queue records what it believes should print, the old path keeps printing
exactly as it does, and the two are compared. Nobody can currently say whether
duplicates happen four times a week or forty; this can answer that without
touching a printer.

HOW IT CANNOT PRINT

Rows are written with status `shadow`, which nothing claims - claimPrintJobs
matches `queued` only, and the till asks for bills. A test drives the real claim
and fails if that filter ever widens. They carry no payload either, so there is
nothing to print even by accident.

WHAT IT RECORDS

Tickets named the way the TILL names them, both schemes - a sale with no
print_jobs array is named by the fallback rather than skipped, because skipping
it would make the shadow blind to a whole class of ticket and then report
perfect agreement, which is the worst outcome a measurement can have.

One row per ticket, enforced by a partial unique index rather than by a
read-then-write: two tills polling at once would both read "absent" and both
insert. An amended order is a second row, because it is a second ticket.

CLOSING A ROW, AND THE BUILD THAT DOES NOT KNOW ABOUT IT

The till now sends printedKeys - the names of what actually reached paper, only
when paper actually came out. A failed ticket reported as printed would close a
row that should be showing up as a disagreement.

But ticket names are new on the wire, and closing only by name would leave every
row from every older till open for ever - the first report would say the whole
estate is failing, which is worthless exactly when it is meant to establish a
baseline. So an older till still closes its rows by sale. That is less precise
and understates disagreement rather than inventing it, which is the safe
direction for a number nobody has yet.

It is a bystander throughout: every failure is swallowed inside, and a test
drives it against a database that throws on every call to prove a broken shadow
cannot fail a service.

API 10715 pass, desktop 2541 pass.
…-shadow

The kitchen queue watches, and prints nothing
Counted on the live shop the day this was written: 272 dishes, none with
a prep time. The field has been on the item form all along. It is empty
because filling it means opening 272 dishes, and nobody does that.

Two customer-facing things go quiet without it. The dish sheet cannot say
"takes about 20 minutes" so somebody can decide before ordering, and the
busy-kitchen notice shipped last release uses the shop's median prep time
as its round length - so on every shop in the world it can say the kitchen
is behind and never by how much. That is the weakest sentence it knows and
the only one it can currently say.

A kitchen already thinks in sections: breads come off the tawa in eight
minutes, a biryani is thirty, a papad is three. So the scope is the one
the price, stock and spice tools already use.

Only the empty ones by default. A shop that has hand-tuned a few dishes
has done the most valuable work on this field, and a blanket write would
erase exactly that; overwriting is available and has to be asked for. The
check says out loud how many it is sparing, so a shop can see its own
numbers are safe rather than trusting the wording of a checkbox.

Writing the test found a real one: Number(null) is 0, so a client that
sent no minutes at all - or a form that failed to read its box - would
have arrived as a confident request to set every dish in the shop to zero
minutes, which reads on a menu as "ready instantly". Refused now at both
ends, with the reason written down.
Stage 3 of the print roadmap: "Replace 20 seconds x 15 then silence with:
acknowledge stops it, escalate in reach rather than volume, back off, then the
shop's declared default fires."

WHAT WAS THERE

    const REPEAT_EVERY_MS = 20000;
    const MAX_REPEATS = 15; // five minutes of asking, then it stops nagging

Five minutes, then silence, with the order still unanswered and the customer
still waiting. The comment beside it is honest about why: "An alarm that never
stops is one somebody mutes at the speaker, and then it is gone for every future
order too."

That reasoning is right and the conclusion is wrong. The choice was never
between nagging for ever and giving up; it is between the same volume for ever
and BACKING OFF. An order arriving at 19:05 was never mentioned again, which is
how one sits until closing time - the roadmap names it: "no order sits in
pending overnight. Today they can."

FOUR RULES

It backs off rather than stopping: 20s, a minute, five, then every fifteen for
as long as it takes. A slow heartbeat is still a signal; silence is not. At two
hours it is still speaking.

It escalates in REACH, not volume - till, then handsets, then the owner.
Louder is how an alarm gets muted at the speaker; further is how it gets
answered.

Acknowledging stops the noise, and nothing else does. "I have seen this" is a
different act from "I have accepted it" and a shop mid-rush needs the first
without the second. But acknowledged is NOT answered: the shop's declared
default still fires on time, or acknowledging becomes a way to park an order for
ever by tapping a button - the same bug wearing a different hat.

Then the shop's declared default fires. Owner: "let restaurent owner decide
that. give option auto cancel or auto accept. based ont time he defines it. by
default dont accpept or reject." Absent means nothing happens, and a value the
code does not recognise is treated as no choice - a product that cancels a
customer's order because a shop never opened a settings page has made a decision
that was not its to make.

AND TWO THINGS THAT ARE NOT SETTINGS

An aggregator's clock is not ours. Swiggy and Zomato reject on their own timer
and count it against the shop, so a decision of ours landing after theirs is
worse than none: the order is already gone and we have recorded the opposite. A
shop can only act inside their window.

The customer can always retry or contact the shop. If a shop never sets a timer
the order sits, so the one thing that must always hold is that the person
waiting is not trapped in silence. A shop chooses what IT does; it cannot choose
to leave a customer with no way out.

The deciding is pure and lives in waiting-order-policy.js, so it can be tested
without a clock. The alarm announces a decision on the process bus and never
performs one - a sound module that could cancel a customer's order would be a
surprising place to find that power.

An existing test asserted the opposite ("the alarm gives up rather than becoming
background noise") and now pins the new behaviour, with the reversal written
down rather than quietly edited.

Desktop 2638 pass.
How long a section takes, said once rather than 272 times
@sridharkalaibala sridharkalaibala changed the title Release: a restaurant that signs up gets a restaurant Release: a restaurant gets a restaurant, and a kitchen can say how long it takes Sep 15, 2026
@sridharkalaibala
sridharkalaibala merged commit 4adf73c into main Sep 15, 2026
23 checks passed
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.

1 participant