A menu can be filled in without three hundred presses - #745
Merged
sridharkalaibala merged 4 commits intoSep 15, 2026
Merged
Conversation
Nutrition shipped last night and is invisible on every shop in the estate, because the way to fill it in is eight numbers per dish and the assistant does one dish per press. Azure Costal Kitchen has 272 dishes. Nobody was ever going to press that button 272 times, so the panel stays empty everywhere and the feature may as well not exist. So there is a pass over the whole menu, in Tools beside the other catalogue levers. And the moment that exists, the rule the whole feature was built on comes under a new kind of pressure. A GUESS MAY BE STORED. IT MAY NOT BE PUBLISHED. Until now the question could not arise: the only way numbers reached a dish was a person typing them, or pressing Estimate and then Save. Either way a person put them there, so every stored figure was somebody's word. A pass that walks three hundred dishes unattended breaks that, and an unchecked guess would start earning "Heart healthy" and "Diabetic friendly" on a live menu - the same harm as a tick box for those words, arriving by a door that is much harder to see, and one I would have walked through without noticing. So nutrition now records WHO SAID SO, and dish-facts publishes nothing derived from an estimate: no badges, and not even the calorie figure, because a number on a menu is itself a claim. The shop sees them on its own screen, where they are a draft. Confirming is opening the dish and saving it, which is one deliberate act by somebody accountable. Empty means kitchen, deliberately: every number stored before this field existed got there because a person typed it and saved, so reading the absence as "unverified" would strip the badges off every dish already done. The server sets that word, never the caller. A client that could send 'kitchen' alongside its estimate would publish the guess and nothing anywhere would say so. THE OTHER THREE THINGS THE SCREEN OWES THE SHOP. What it costs: one call to their own AI provider per dish, so the count is read first and put in the button. "Run this over your menu" with no number in front of it is not a choice anybody can make. That it can be stopped: the browser walks the list, one dish at a time, so Stop means stop and closing the window means stop. A job on the server could do neither, and would have to invent its own progress reporting besides. That a refusal ends it: 400 from that route means the AI is refusing - no key, provider down, cap spent - and every remaining dish would fail identically. Asking three hundred times in a row is how a rate limit becomes a ban. The write is four fields by name, not a replay of the item form's upsert - that writes every field on the document, and anything the caller did not send comes back as a default. Survivable when a person is looking at a form; not when a loop is walking a menu unattended. It skips any dish a person has already answered, so the pass is safe to run twice. The entry explains itself instead of vanishing. The single-dish button hides when there is no AI key, but the only place that asks about availability is the item FORM and this lives on the item LIST - a gate written the same way would have been dead code and the entry hidden on every shop forever. It says where to paste the key instead, which nobody finds on their own. No local t() wrapper in the new file. That is how the requests dock ended up with its entire key set in no language pack: the scanner reads literal PosnicPro.i18n.t calls and a helper hides every one of them. All 24 keys are spelled out, translated into all seventeen packs, and coverage held at 93%.
Nothing disagreed. This branch appended the nutrition-pass strings and develop appended the bill-copies strings, both after the last existing key in all seventeen packs, so every pack conflicted on the line that had to grow a trailing comma. Resolved as JSON rather than as text: each pack is this branch's keys in its own order, then any key develop added that was not already there. Checked first that no key had its value changed on both sides and none was deleted on one side only - that would have been a real disagreement, not a collision of two appends, and is not something to settle mechanically. check-translations.js says All good and coverage is unchanged at 93%.
Owner: "whenever you show filter, no item in the list then dont show that filter in menu. example heart healthy food not in our menu then dont show the filter itself." The sort-and-filter sheet was built that way already - every option is counted against the menu first and only offered if a dish carries it. Two older chips were not, because each was gated on a question slightly different from the one its own filter asks. VEG ONLY was gated on "does any dish have a diet mark at all". A steakhouse marks every dish non_veg, so the mark is present on all of them, the chip appears, and tapping it empties the menu and says "Nothing on the menu is marked vegetarian." The gate now asks exactly what the filter asks - veg or vegan - with unmarked still never assumed either way, because a shop that never filled the field has promised nothing. AVAILABLE NOW, on the public menu, was shown to every restaurant. It is hidden in both directions now: nothing available means it empties the menu, and everything available means it changes nothing at all. A control that cannot alter what you are looking at is the same annoyance as one that empties it. Checked against the real production menu rather than assumed. Azure Costal Kitchen: 29 sections, 272 dishes, ZERO diet marks, 272 of 272 available, none carrying a health claim. So the veg chip is already correctly hidden there and the health filters never appear - but "Available now" is on that page right now with nothing it could possibly do, which is the complaint exactly. Driving the live page confirmed it: vegChip hidden, availChip SHOWN, 272 dishes.
…ithub.com:Posnic/POS into feat/fill-in-a-menu-without-three-hundred-presses
Contributor
|
Merged to Try it at https://develop.posnic.io, or run it yourself: git fetch origin develop && git checkout develop
npm install && npm --prefix api install
npm run dev # then http://localhost:3000When you have tested it, say what you did and what happened, and set Reporting that something is broken is as useful as fixing it. It is |
This was referenced Sep 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nutrition shipped last night and is invisible on every shop in the estate, because the way to fill it in is eight numbers per dish and the assistant does one dish per press. Azure Costal Kitchen has 272 dishes. Nobody was ever going to press that button 272 times.
So: a pass over the whole menu, in Tools beside the other catalogue levers.
The thing that had to be settled first
The moment a pass can walk three hundred dishes unattended, the rule this whole feature was built on comes under a new kind of pressure.
Until now the question could not arise. The only way numbers reached a dish was a person typing them, or pressing Estimate and then Save - either way a person put them there, so every stored figure was somebody's word. A bulk pass breaks that, and an unchecked guess would start earning "Heart healthy" and "Diabetic friendly" on a live menu.
That is the same harm as a tick box for those words, arriving by a door that is much harder to see - and one I would have walked straight through.
A guess may be stored. It may not be published. Nutrition now records who said so, and
dish-factspublishes nothing derived from an estimate: no badges, and not even the calorie figure, because a number on a menu is itself a claim. The shop sees them on its own screen, where they are a draft. Confirming is opening the dish and saving it - one deliberate act by somebody accountable.Empty means kitchen, deliberately: every number stored before this field existed got there because a person typed it and saved, so reading the absence as "unverified" would strip the badges off every dish already done.
The server sets that word, never the caller. A client that could send
'kitchen'alongside its estimate would publish the guess with nothing anywhere to say so.What the screen owes the shop
What it costs. One call to their own AI provider per dish, so the count is read first and put in the button: "Estimate 214 dishes". "Run this over your menu" with no number in front of it is not a choice anybody can make.
That it can be stopped. The browser walks the list one dish at a time, so Stop means stop and closing the window means stop. A server job could do neither, and would have to invent its own progress reporting besides.
That a refusal ends it. 400 from that route means the AI is refusing - no key, provider down, cap spent - and every remaining dish would fail identically. Asking three hundred times in a row is how a rate limit becomes a ban.
Two things that would have gone wrong quietly
The write is four fields by name, not a replay of the item form's upsert. That writes every field on the document, and anything the caller did not send comes back as a default - survivable when a person is looking at a form, not when a loop is walking a menu unattended. It skips any dish a person has already answered, so the pass is safe to run twice.
The entry explains itself instead of vanishing. The single-dish button hides when there is no AI key, but the only place that asks about availability is the item form, and this lives on the item list. A gate written the same way would have been dead code and the entry hidden on every shop forever. It names where to paste the key instead.
Checks
t()wrapper in the new file - that is how the requests dock ended up with its entire key set in no language pack. All 24 keys spelled out, translated into all 17 packs, coverage held at 93%BaseModel.license, not.licenseId- the latter does not exist, so the filter would simply have had no licence clause