Skip to content

One tap says how hot, and the ticket prints it the same in every language - #772

Merged
sridharkalaibala merged 1 commit into
developfrom
feat/how-hot-you-want-it
Sep 15, 2026
Merged

sridharkalaibala merged 1 commit into
developfrom
feat/how-hot-you-want-it

Conversation

@sridharkalaibala

@sridharkalaibala sridharkalaibala commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Owner: "when user order if food is speci food. we can have simple option like low, medium high with number chilly image like one, two, three chilli icons user can customize easy. we can add those into kitchen note. what you think?"

I think yes, with three decisions worth defending.

Three levels, and not choosing is one of them

Mild, medium, spicy. There is no fourth button for "however the kitchen makes it": that is the answer most people give and a default that has to be selected turns a tap into a question. Once a level is set, a plain link takes it back off - the only way to undo a radio is to offer the undo. Anybody who wants no chilli at all still has the note, which has not moved.

Per dish, not per menu

A kitchen that batch-cooks its gravy cannot make one portion mild, and a customer who asked for mild and got hot is worse off than one who never asked. So the offer exists only where it can be honoured, and only the kitchen knows where that is: one tick on the dish card, off by default.

A field, not a sentence in the note

The obvious build appends "less spicy" to the kitchen note, and it is wrong twice over. A customer reading a Tamil menu writes Tamil, so the ticket carries prose the kitchen may misread; a level prints identically on every ticket whatever language the order was placed in. And a number can be counted afterwards - a shop can learn that four orders in ten ask for mild, and cook accordingly - which no amount of free text will ever tell it.

CHETTINAD CHICKEN          x1
   SPICE: MEDIUM (2 of 3)
   ** no coriander **

ASCII on the paper, deliberately: escpos-receipt.js puts every character through ascii() and latin1, so an emoji would arrive as a question mark. The chillies are drawn on the phone; the paper gets the word and the count, because a cook who does not read English still reads 2 of 3.

It travels by the road the note already took

The kitchen ticket is not printed from sale.items - the poller builds its print jobs out of changes[].items. That is what made the note bug so hard to find last month: every layer worked and the paper was still wrong. A level stored on the sale and missing from the change record would be the same bug shipped again in the same month, so the test asserts the rule rather than a spelling: every place in sale.repository.js that carries the customer's note carries the level beside it. Eight places; on develop all eight were bare.

It also reaches the approval queue, because that screen is where an order is refused and "we cannot make that one mild" is a reason to refuse it - unusable if the request only appears on paper in the kitchen.

The bug this found, which is not about spice

order/indexedDB.js builds the ordering bundle's entire catalogue in one object literal, and a field it does not name there never reaches the page, however correctly the server sent it.

nutrition, tags, marks and claims have been sent since the dish-facts release. All four stopped at that literal. So on /order the dish sheet drew no numbers, no badges and no marks, and the "Good for" filter group had nothing to offer and hid itself - while /menu, which reads the same endpoint without a local store, showed all of it. Nothing failed, nothing was logged, and every test passed because they all read the source rather than running it.

Verified against develop's tip: dropped on HEAD: nutrition, tags, marks, claims.

The test for it asks the server what a dish carries (Object.keys(dishFacts.factsFor({}))) rather than naming fields here, so a fifth fact added tomorrow fails until the page is told to keep it.

Two things the tests caught that reading would not have

Number(true) is 1. Every path here does Number(value), so a client sending spice_level: true would have had MILD printed on real paper for a request nobody made. All three implementations now refuse anything that is not a number or the text of one.

A code example in a comment became a translation site. The i18n collision scanner reads i18n.t('key', 'English') out of source, and a comment explaining that rule contained a literal example of it. one key, one meaning failed on a key called key.

Two copies of three words, on purpose

src/ cannot require into api/: the API ships outside the asar as extraResources/server.js. Same reason the-ticket-says-where-the-order-came-from.test.js gives for its own duplicated table. So the words are written twice and tests/the-ticket-says-how-hot.test.js runs both implementations over every input - 0 1 2 3 4 -1 '1' '2' '3' '' null undefined NaN true 'hot' {} [] - and refuses a commit where they disagree.

Checks

  • 33 new tests: 9 driving the picker in a real DOM, 9 on the ticket (bytes and the HTML fallback), 9 on the rule itself, 6 on the road through sale.repository.js
  • Root suite 2,550 of 2,553; the three remaining need frontend/public built, which CI does
  • API suite 10,653 of 10,653
  • 6 new lang_ keys translated into all 17 packs; the ordering dictionary's Tamil added and the two byte copies verified identical after the commit hook
  • Looked at in a browser at 390px and 320px: no horizontal overflow, three chillies on one line at the narrowest phone

…uage

Spice is what a restaurant is asked to change more often than anything
else on its menu, and the only way to ask was typing it into the note:
in whatever words, in whatever language, for a kitchen that then has to
read prose off a ticket at speed.

Three levels, one chilli more on each, on the dishes the shop says its
kitchen can really cook to order. Not choosing is an answer and needs no
button. A level prints identically on every ticket whatever language the
order was placed in, and can be counted afterwards, which no amount of
free text will ever allow.

Per dish, because a kitchen that batch-cooks its gravy cannot make one
portion mild, and a customer who asked for mild and got hot is worse off
than one who never asked.

It travels by the road the note already took: sale line, change record,
approval queue, paper. The kitchen ticket is printed from changes[], not
from sale.items, so a field stored on the sale and missing there never
reaches the kitchen. The test asserts the rule rather than the spelling:
every place that carries the note carries the level.

Also fixes a bug found on the way. order/indexedDB.js builds the ordering
bundle's whole catalogue in one object literal, and a field it does not
name there never reaches the page. nutrition, tags, marks and claims have
been sent by the server since the dish-facts release and all four stopped
at that literal, so /order drew no numbers, no badges and no marks and its
"Good for" filter had nothing to offer, while /menu showed all of it. The
test asks the server what a dish carries rather than naming the fields, so
a fifth fact fails until the page is told to keep it.
@sridharkalaibala
sridharkalaibala merged commit a46da7d into develop Sep 15, 2026
10 checks passed
@github-actions github-actions Bot added the ready for QA Merged to develop and live on develop.posnic.io - anyone can test it label Sep 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Merged to develop. Anyone can test this - you do not need write access.

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:3000

When you have tested it, say what you did and what happened, and set
QA passed or QA failed. If you cannot set labels, just comment -
a maintainer will.

Reporting that something is broken is as useful as fixing it. It is
better found here than by a shopkeeper.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for QA Merged to develop and live on develop.posnic.io - anyone can test it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant