Skip to content

Release: the nutrition chain works end to end - #766

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

sridharkalaibala merged 13 commits into
mainfrom
develop

Conversation

@sridharkalaibala

Copy link
Copy Markdown
Contributor

Promotes develop to main. Six merged pull requests since the last release.

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

Why this one is worth doing promptly

The last release shipped the nutrition pass - a shop can estimate a whole menu in one go - but not the screen that confirms it. Confirming is the only thing that publishes a badge, and on production today the only way to confirm is opening a dish and saving it, one at a time.

So a shop that runs the pass on 272 dishes right now ends up with 272 estimates it has no reasonable way to publish. This release closes that: #765 adds the review screen, where each row leads with the badges confirming it would publish, and confirming many dishes is one action.

Confirming still writes no number - only who stands behind the ones already there - so there remains exactly one way for a figure to reach a dish.

What else reaches production

#760 - the requests dock can speak Tamil. Eleven of its fourteen words were in no language pack at all. The coverage scanner collects keys by matching i18n.t(...), and that file had a helper called t(), so every call was invisible: never gathered, never translated, falling back to English in every language. Which renders perfectly, which is why nothing ever failed. Tamil shops have been reading "Requests", "Accept", "Refuse" and every "5 minutes ago" in English for the life of that panel.

From other sessions: an order says where it came from (#763), the kitchen screen is chosen not assumed (#762), a screen is measured across and down (#764), and the checks run locally before a push (#761).

Verified before opening

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

Two failures that were red on develop and are not mine were fixed on the way: .husky/pre-push and scripts/check-locally.js carried a shebang and were committed non-executable, and the README endpoint badge was two routes behind.

After the merge

The nutrition chain is complete end to end for the first time - estimate a menu, read what each dish would claim, confirm, and the badges appear. It is still invisible on every shop until somebody with an AI key actually runs it, because nothing is ever claimed from missing data.

Eleven of the dock's fourteen keys were in NO language pack at all. I noticed
it twice while translating other work, flagged it twice, and did not fix it.

The coverage scanner collects keys by matching `i18n.t('lang_x', 'X')` in these
files. This one had a bare `t()` helper, so every call was invisible: the keys
were never gathered, never translated, and fell back to English in every
language - which renders perfectly, which is why nothing ever failed and why
the only way to notice was to go looking. A Tamil shop read this panel in
English for the life of the feature, including "Requests", "Accept", "Refuse"
and every "5 minutes ago" on a card.

The helper is an object called `i18n` now. That is the whole fix: the scanner
sees `i18n.t(...)` and the guard inside it survives. The guard is not
decoration - this file is in the dashboard bundle and paints from a poll, so
it can run before PosnicPro.i18n is built, and a panel that throws while
somebody is answering a cancellation is worse than one that says "Requests" in
English.

Inlining PosnicPro.i18n.t directly was the obvious fix and the wrong one: it
made the keys visible and took the guard away with them, and eleven dock tests
went red at once.

One call passes VARIABLES rather than a literal key - the word for what a card
is asking - and a regex over literals skipped it, so the file threw "t is not
defined" at the first card. Caught by the tests, which is what they are for.

Eleven keys translated into all seventeen packs. A new test pins the shape:
no bare t() in this file, and every key it names is in the English map, so a
future helper cannot quietly hide them again.
Owner: "better run all ci stuff inside commit. github bill came around 200
usd", and on scope: "deploy stuff is okay. other check only i said."

`npm run check` runs the CHECK half of ci.yml against the tree in front of you:
the attribution guard, ESLint, Prettier, the API docs comparison, the three
packaging checks and the desktop suite. About twenty-five seconds. The deploys,
the releases and the installer packaging stay where they are - they need
secrets, signing keys and three operating systems.

The slow ones are opt-in with --all: the API unit suite, the REST suite against
a real database, and the scan of every blob ever committed. A check nobody
waits for is a check nobody runs.

PRE-PUSH, NOT PRE-COMMIT. A commit is how you save your place - twenty an hour,
half of them on a branch nobody will see. A minute of checks in front of each
is how a hook ends up permanently disabled with -n, and then it protects
nothing. A push is when the work leaves.

A missing prerequisite SKIPS rather than fails. Three desktop tests need the
pages CI builds first, which a working copy does not have; failing on that
every time would make this hook the first thing anybody bypasses.

This does not replace the checks on the pull request, and is not meant to. They
run on a clean machine with a clean checkout, which is the only thing that
catches a file never committed or a stale build left in a folder.
…amil

The requests dock can speak Tamil now
The checks can run here, before a push
Owner: "as of now one screen. but we have option multiple screen is good. we
should able get control of it ans show it." And: "make everthing configurable
pleaes... dont make everthing fixed."

So the till drives as many screens as the machine has, each configured on its
own, and every number on them is a setting.

THE PART THAT MATTERS IS THE ARITHMETIC

Asked how far the cook stands from the screen, the owner said "around 5 meter".
The research this was built from assumed 2.5, and five is not a bit further, it
is a different machine:

  distance   font     fits on a 43" 1080p
  2.5 m      42 px    15 orders
  5.0 m      84 px     2

Text has to be a certain size ON THE EYE, so doubling the distance doubles the
letter height, and that costs room across and down. Double the distance,
quarter the orders. Nobody guesses this right, which is why kitchen-screen-fit
computes it and Hardware Manager says the answer in ORDERS rather than pixels -
before a television is bought, not after the mount is paid for.

It also answers the thing everyone reaches for first: a 4K panel the same size
has the same letter height. Sharper pixels, same letters. The test for that
exists because the instinct is so strong.

WHAT IS CONFIGURABLE, WHICH IS EVERYTHING

Viewing distance, panel size, the visual angle to aim for, the edge margin
televisions crop, which facts a card carries, dishes per card, the amber and
red thresholds, page dwell, when it greys out, and the theme. Per display, so a
hot-kitchen screen and a pass screen can differ.

THE FOUR RULES IT MUST NOT BREAK

The till keeps selling: startup is wrapped and the module loads with no Electron
at all. It never steals focus: showInactive, focusable false, and nothing on the
page to click or type into. Absent means off, so a shop with a projector does
not suddenly show kitchen tickets on it. And it comes back by itself, on restart
and when a display reappears, because a screen needing a click after every power
cut is dark by Thursday.

Item notes are never dropped to save room, and age is carried by border weight
and a bar as well as by hue, because roughly one man in twelve cannot separate
red from green and a kitchen is exactly that audience.

NOT IN THIS CHANGE: live orders. multiKitchenPrint marks tickets printed, so a
display must never read it - that needs its own read-only feed and is the next
piece. Until then the screen shows a sample service for placing it, which is the
only way to answer "can the cook read this?" - by standing where the cook stands.

Desktop 2501 pass, API 10630 pass.
…-not-assumed

The kitchen screen is chosen, not assumed
Owner: "every order should have some details. example what mobile, user agent,
ip address, mobile type or user account whatever infromation app can know do
it."

The customer storefront has recorded this since it was built. The door every
handset uses recorded nothing at all - which is the one that matters most,
because a shop has four identical phones and six waiters, and an order that
goes wrong is a question about which of each.

So qrOrder now collects what the request itself proves: the address it came
from, what the caller calls itself, and - from the session, never the body -
the member of staff signed in. A phone describes its own hardware; it does not
get to name its own address, and it certainly does not get to put somebody
else's name on an order it placed. The body is spread first and ours after, so
ours wins.

_clientFacts learns the fields a handset can answer: the app, its version, the
phone's model, and which door it used. Everything stays whitelisted and cut to
a length, so a crafted payload cannot turn a sale into storage of its own.

None of it reaches a customer: customerOrderView is built from a different list
and a test says so.
Asked for a screen size, the owner answered "28 inch x 15.5 inch". That is what
a tape measure gives you. The field asked for a diagonal, so somebody typing 28
into it would have been told a 28 inch screen fits less than their 32 inch one
does, and would have believed it - the whole point of this panel is that the
number it reports can be trusted.

It now takes either. Type width and height and the diagonal fills in while you
watch, so it can be checked against the box the monitor came in.

And the answer for that monitor, which is why it matters:

  28 x 15.5 inch = 32 inch diagonal

  at 5.0 m    113px    1 x 1  =  1 order    unusable
  at 2.5 m     56px    3 x 2  =  6 orders   tight
  at 2.0 m     45px    4 x 3  = 12 orders   comfortable

One order is not a kitchen display. A 32 inch panel is a desk monitor and wants
to be about two metres away; at five it would need to be roughly three times the
size. Better to know that before it is on the wall.

Also adds a test that runs the panel's own markup generator and checks the
result - every quote balanced, every handler addressed to the right display, the
verdict and the advice actually present. Written because a patch ate an escaped
quote in exactly that markup an hour ago, and nothing would have noticed until
somebody opened the tab.
.husky/pre-push and scripts/check-locally.js arrived committed 100644. Both
start with a shebang, which is a statement that they are meant to be executed,
and neither could be.

On Windows it happens to work, because git marks files executable locally and
sh does not care. On Linux - CI, and any contributor - the hook is skipped in
silence. A check that is skipped in silence is worse than no check, because
everybody believes it ran.

tests/executable-scripts.test.js exists for exactly this and has been failing on
develop since they landed. Mode only; not a byte of either file changes.
The pass made estimating a whole menu cheap and left CONFIRMING it at one dish
at a time - open the item, save it - which on 272 dishes is the same 272
presses, moved one step along. A feature that stops there publishes nothing,
because confirming is the only thing that lets a badge out. This is the other
half, and I should have built it with the first.

WHAT THE ROWS SHOW IS THE POINT. A shop scanning calorie figures is being
asked to check arithmetic it has no way to check. A shop reading "Grilled
Chicken - High protein, Heart healthy" is being asked the question it can
actually answer: is that sentence true of my food? Those badges are exactly
what confirming publishes, so the row leads with them and the numbers sit
behind.

They are DERIVED on the read, by the same claimsFor the customer menu uses, so
what a shop approves is what a customer will see rather than a second opinion
computed somewhere else.

CONFIRMING CHANGES NO NUMBER. Not one figure is written: only who stands
behind the ones already there. A confirm that also edited would be a second
way for values to reach a dish, and the whole feature rests on there being
exactly one - the item form, where a person types them. A test asserts the
write touches nothing but the source fields.

Only rows that are CURRENTLY estimated, enforced in the filter rather than
trusted from the page: somebody may have answered a dish by hand between the
screen reading the list and the button being pressed, and that answer is
already the shop's word.

After a confirm the screen reads back rather than striking rows out, because
the server may have skipped some and the page should show what is actually
left rather than what it assumed happened.

fillCategories took no argument and hard-wired the pass's picker. A second
screen calling it would have filled that one and left its own showing nothing
but "The whole menu" - correct-looking, and silently narrowing nothing.

Also fixed, both pre-existing on develop and both red: .husky/pre-push and
scripts/check-locally.js carry a shebang and were committed non-executable,
and the README endpoint badge was two routes behind.

Eight new keys translated into all seventeen packs.
…nd-down

A screen is measured across and down, not corner to corner
…essed

Checking what the machine guessed, before any of it is published
@sridharkalaibala
sridharkalaibala merged commit d54320d into main Sep 15, 2026
22 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