Release: the ticket carries the waiter's words, and the ordering pages move to one clock - #878
Merged
Merged
Conversation
Owner: "this is about kitchen play right? not restaurant module?"
He is right, and it took three goes. First the Sale tab of Core
Settings, beside the auto-focus switches, because those are device-local
too. Then the Print tab, because the kitchen ticket is printed. Per-device
was the right instinct and the wrong page both times.
A speaker is a device on ONE COMPUTER, like the cash drawer, the weighing
scale and the kitchen screen. Hardware Manager is where those live, and it
is the window somebody setting up the machine at the pass is already
standing in. So it is a Kitchen Sound tab, next to Kitchen Screen.
Not the Multi KOT Print tab, which would have been the obvious guess: that
one is hidden unless multi-KOT is enabled, and a setting that disappears
depending on another setting is how this feature spent its whole life
switched off and unfindable.
Removed from the settings page rather than left in both, with a note there
saying where it went. Two homes drift apart and then a shop has two
answers to one question.
The switches are set BEFORE anything that can fail and the pickers fill
inside their own guard, which is the fix from the settings page carried
over rather than re-learned: filling the voice picker threw there, the
failure path hid the switches and the Test button, and left three empty
pickers on screen.
tests/settings-windows.test.js caught a real bug in this while I was
writing it: switchTab('sound') had no entry in tabMap, and switchTab hides
every panel before showing the requested one, so clicking the tab would
have blanked the window. Its message says Receipt Printer did exactly that
once. A guard somebody wrote after being bitten, catching the next person.
A cancellation ticket came off the printer reading
CHICKEN BIRYANI 1 HANDI x1
** Chicken Biryani sold as 1 handi, configured for a INR
Restaurant Demo Dataset POS demo. **
Owner: "it supposed print only note right? that too in cancel shit?" and,
seeing the same text beside every line in the KOT cart, "who asked to add
this line item info in the desktop cart? why?"
Nobody asked. Two client-side fallbacks reached for the dish's catalogue
description whenever a line had no note. sales.js addItem STORED
params.description in item_description, so the blurb was saved on the sale
and travelled to the cart, the kitchen ticket and the cancellation; kot.js
printed it.description when the note was empty, catching any line that
escaped the first.
A note on a ticket is an instruction and a cook assumes somebody asked for
it. The server was taught this on 2026-09-15 ("The kitchen reads less
spicy") and the till went on writing the blurb in, which is why it was seen
again after being reported fixed. Both halves have to agree or the paper
shows the wrong one.
The fallback has been in addItem since the first import, so orders already
open still carry the text on their stored lines; a line added from now on
carries only what somebody typed.
…h-the-kitchen The kitchen's speaker lives with the kitchen
A note is the waiter's words, never the menu's
Owner, a third time, with the paper in his hand: "order cancel i see fucking
item description. i dont know why injecting those info. how its useful to
kitch?" It is not useful. A cook reads a ticket to know what to make and what
somebody asked to be different about it; marketing copy is noise at the one
moment noise costs money.
The reason it survived being reported fixed twice is that it was never in one
place. The cart write and the HTML ticket were fixed; the paper did not
change, because kot-manager builds the ESC/POS ticket payload out of the
sale's lines and BOTH of its builders read the sale's `description` - the
catalogue sentence - whenever a line carried no note. Those are the two that
print in a kitchen.
kot-manager.js x2 the ticket payloads now prefer item_note and never
read the catalogue field
sale.repository.js the cancellation's stored change record prefers the
typed note
sale.repository.js the kitchen screen card does too
escpos-kot.js is deliberately untouched: its payload field is NAMED
description and by then it already IS the note, because the builder above put
it there. Changing it broke two ticket tests, which is the contract working.
The new test sweeps every file on the path to a cook rather than checking one
at a time, because checking one at a time is exactly how the two that
mattered were missed.
Owner: "animations not properly organized. you need to see that. i want app like real corporate app, proper mobile app kind of feel in responsive website." He is right, and it was measurable. order.css carried THIRTEEN durations - 70ms, 90ms, 0.12s, 0.15s, 0.2s, 0.22s, 0.25s, 0.28s, 0.3s, 0.32s, 0.4s, 0.5s, 0.9s - written in two notations, and `ease` twenty-five times. The menu page had grown its own copy of the same sprawl in an inline stylesheet, so the two pages of one product disagreed about how long a movement takes. No single value was wrong. Together they are why a page feels assembled rather than made. FOUR DURATIONS AND THREE CURVES. Scaled by DISTANCE, not importance, because that is the only rule the eye actually reads: press 90ms, quick 160ms, move 240ms, sheet 320ms. Enter decelerates, exit accelerates, move holds at both ends, and there is a spring for emphasis. `ease` is the browser default and symmetrical - it accelerates and decelerates equally, which nothing physical does, and it is the single biggest reason web motion reads as web motion. It no longer appears. Looping animations keep their own rhythm and their own linear or ease-in-out: a spinner, a breathing orb and a skeleton pulse never arrive or leave, so they are not on the scale. AND THE TWO LINES THAT MOST DECIDE WHETHER A PAGE READS AS AN APP. A mobile browser paints a grey box over whatever a finger lands on and waits about 300ms before believing a tap was not the start of a zoom. Both are right for a document and wrong for a thing being operated. Pinch to zoom is kept; only double-tap zoom goes. ONE PRESS, EVERYWHERE. Fourteen :active rules had grown up separately, so a chip sank and a card did not, and the two that both sank did it by different amounts over different times. Now every tappable surface answers a finger the same way: scale to 0.97 on the compositor, and instant on release, because a release that eases is a button still busy after the finger has gone. A disabled control does not answer at all, since answering is a promise. ONE DOCUMENTED EXCEPTION. The voice meter animates height, driven from the microphone level. scaleY would keep it off the main thread but needs the bars re-authored and watched while it is done, and a meter smoothed wrongly reads as a microphone that is not listening. I started that change and backed it out: it belongs in a pass where somebody can see it. The reason is now written where the next person will find it. The test is a ratchet. It does not care what the numbers are; it cares that there is one set of them and that nothing writes its own.
Owner: "print bill from desktop not working. thermal printer not taking bills?" It was printing perfectly. His receipt log: 19:25:44 BILL 545 source: Till -> "Posnic Reception" success 19:32:23 BILL 400 source: Floor bill -> POS-80C success 19:36:03 BILL 660 source: Till -> "Posnic Reception" success Every desktop bill went to a printer on the far side of the shop, in 54 ms, while he watched the thermal one. The handset's floor bill, which resolves its printer in the main process, went to the right place. The Receipt Printer setting lives in `preferences`, a file the main process owns, because Hardware Manager runs on another origin with its own localStorage. syncPrinterPreferences mirrors it into the till window and had exactly ONE call site, at startup, while its own comment said it was "called at startup and again after printing". So the till addressed every receipt to the printer it had cached at boot: change the setting, press Print Bill, and the bytes go to the old queue and the log records a success. Nothing anywhere says the setting was ignored. The print path now refreshes the setting before resolving the name, and a failed refresh still prints on the last known printer, because a bill that never comes out is worse than one on yesterday's queue.
Six things the owner asked for on the till's own surfaces. THE MENU. "KOT" told a new user nothing, so it reads "KOT - Kitchen Order" in the sidebar and at the top of the page, which are the same key and have to agree. It moves up under Dashboard, above Invoices: on a restaurant till the tables screen is opened every few minutes and it was sitting four entries down, below Sales History. Online orders travels with it, because orders held back waiting to be accepted are the same job one moment earlier - the decision that produces the ticket. Item List is gone from Home. It was the same route as Inventory > Items, so the shop had one page under two names in two places: "Item List i dont know why its here. its supposed to under inventory." It is already there. The selectors that showed and hid it went with it rather than being left pointing at markup that no longer exists. THE PAYMENT PANEL. It was headed "Add Payment Field", which is a developer's word for it; it now says Add Payment Method, reusing the key the product already had for those exact words rather than giving a second key the same meaning. The method name is typed left to right, so it is no longer right-aligned; amount boxes are left alone, because right-aligned digits are correct for money and that was not the complaint. And it no longer closes itself after Save. A shop entering Cash, UPI, Card and an aggregator in a row paid a reopen for each one. The form clears, the cursor returns to the name box, and closing is the person's choice - the button, or Escape, which these slide-out panels never honoured because they are not modals and the close_on_esc class does nothing for them.
Owner, twice: "when payment done table not cleared from active order ( KOT
page )" and "after taking payment its not going rom screen. table not going.
what issue ?"
It was never the screen. getTablesWithActiveOrders asks for sale_process
'KOT' AND payment_status 'Unpaid', so a table leaves the floor by being
paid. processSale had an override for table orders that ran on EVERY write:
if (saleMethod === 'Table-Order') {
saleProcess = 'KOT';
paymentStatus = 'Unpaid';
paymentPending = finalSaleTotAmount;
paidAmount = 0;
}
including the write the till sends to settle the bill. The service derived
'Paid' from the payment a few lines earlier and this put it straight back to
Unpaid, with nothing paid and the whole total outstanding. So the table
could never clear, no amount of refreshing would help, and the payment
itself was discarded on the way in - which is the worse half of it.
The override has to stay. A table order must read Unpaid however it
arrives: the captain app sends `payment_status: "cash"` and the QR page
sends "Upi", both METHODS, and taking those for a status let a bill close
before anybody handed over money.
So it no longer touches a write that is settling: an EXISTING order being
given a status this service derived from a real payment. A create is always
Unpaid, because the till puts payment_mode 'Cash' on a table order long
before anybody pays, and the test that pins that still passes.
sale_process stays KOT either way. It is still a table order, so history and
the reports that read it are untouched; the STATUS is what clears the floor.
A print failure was announced and a success said nothing, so the only way to know a bill had come out was to walk to the printer. Owner: "when sales done (auto print) or user click print sale bill (if print done show success. hardware level confirmation also good.)" This is the hardware answer rather than a hopeful one. The main process writes the bytes to each queue and reports back per printer; `printed` counts the ones that took them, and the failures name themselves. The printer is named in the message because a till can have several, and a bill landing on the wrong one is exactly the fault that cost an evening - from the counter it looked like nothing had printed at all. A partial success is a warning rather than a tick: one copy out of two is not what somebody asked for, and the message says which printer refused and why.
The board was a fixed number of columns however many orders were on it, so a single ticket sat in a quarter-width card. "Chicken Tikka 1 plate" wrapped onto three lines, the card clips what it cannot fit, and the third dish dropped off the bottom - on an otherwise empty screen. The owner photographed exactly that: "display not showing properly." The fit already computes the MOST columns a screen can carry at a readable size. That is a ceiling, not a quota. With fewer orders than the ceiling the cards now spread into the room they already have, and the names fit on one line. When the kitchen fills up the ceiling applies again, so nothing about the busy case changes.
Two assertions pinned the exact source lines they were written against, so they failed the moment those lines were made STRICTER - the cancel flow and the ticket builder now prefer the note somebody typed and never reach for the catalogue sentence. That is the same brittleness that let the bug run: a test tied to wording fails when its subject improves, and passes while the meaning rots. What has to hold is that a note travels, and that the menu's own words never do. escpos-kot.js is left alone deliberately: by the time a payload reaches the renderer, `description` IS the note, because the builder put it there.
…dering-pages One motion system for the ordering pages
The ticket a kitchen prints carries the note, not the catalogue
TWO FIGURES, IN THE WRONG ORDER. Owner: "take payment cand cash new payment metho sometime it shows only total without tax." Sometimes, which is the shape of a race rather than of arithmetic. `sale_new_tot` is the grand total the sale screen has just worked out - items, discount, tax, round-off, the number printed on the bill. `EditRecentSaleParams.sales_total` is whatever was stored when the sale was last written, which for an order taken on a handset or a QR page was computed elsewhere and can predate a tax change or an edit. The stored one was preferred, and the correction underneath it only fired when the computed total was ABOVE ZERO. Open the tender before this screen has finished adding up - the order is fetched, the rows are drawn, the totals follow - and the computed total is 0, the correction is skipped, and the stale figure stands. That is how a customer was shown a total with no tax in it on some bills and not others. The computed total now wins whenever there is one; the stored figure is the fallback for the moment before the screen has run, rather than the other way round. The test runs those real lines rather than reading them, because the bug is in the ORDER of two fallbacks and reading is what missed it. AND A SAVED PRINTER IS ASKED ABOUT. "Saved" only ever proved this window could write a file: "we need show confirmation its done. its failed or really done. hardware level also good." A shop that picks a printer which is switched off, unplugged or renamed learnt nothing until a customer was waiting. The machine is now asked again, fresh - missing from the list is an error, present but not reporting ready is a warning, and only a printer Windows calls ready gets a tick. No paper is spent; Test print is still the only thing that proves the whole path.
Owner: "username password not saved already. everytime i need to enter." The handset was not forgetting. It stores the credential and stores it correctly. The credential expired. A till token lasting 24 hours is right: a till is a fixed machine behind a counter that somebody signs into at the start of a shift. A handset is not that. It is carried by a part-time waiter who was handed it five minutes ago and does not know the shop's password, so a daily expiry means finding somebody who does and bringing them over, at the start of every service, for every phone. One support call a day per shop, for ever, produced entirely by a number. So the handset gets its own lifetime, thirty days by default, and nothing else changes. signLegacyToken takes it as an optional last argument, so the till and the web sign-ins keep exactly what they had, and a test fails if a second caller ever picks it up by accident. WHY A LONGER ONE IS SAFE HERE AND NOT ON THE TILL. A lost handset is cut off by freeing its slot on the till, which the till already enforces and answers 403 to. That is a revocation a manager can actually perform. An expiry is not: it does nothing about the phone in somebody's pocket today, it only inconveniences the shop tomorrow. Still a number somebody can disagree with: HANDSET_JWT_EXPIRES_IN=24h brings the old behaviour back without a release, and a value somebody typed wrong falls back rather than throwing, because a waiter locked out by a typo in an env file has no way to find out what is wrong.
Adding a course to a table meant typing at least two letters into a search box and then hitting a 13px row in a dropdown. Owner: "add new item like typeahead... coz its not touch friendly. so make it proper. touch friendly can do stuff without type. near some 4 box icon to show the items left side normal sales flow." Somebody holding a tablet at a table is not going to type. Browse, beside the search, opens the branch's most-ordered dishes as tiles 64px tall with the name wrapped rather than cut, and no keyboard at all. The search stays for the long tail, because a shop with three hundred dishes cannot be tapped through. Most-ordered rather than alphabetical: a restaurant's top twenty dishes are most of its covers, so the tap somebody wants is nearly always on the first screen. A shop that has sold nothing says so instead of showing an empty box that reads as broken. A TILE IS PRICED LIKE A SEARCH RESULT, which is the part worth care. The tile knows what the dish last sold for, and that is not what it costs today - a price change, a discount or a tax change all live on the item. So a tap reads the item back and prices it through the same arithmetic the search uses, then adds it through the same door. The sum is lifted into _priceOf for that reason; two prices for one dish is the kind of difference nobody notices until a customer is charged differently on two days. Two further copies of that arithmetic remain in this file, in the view and add-from-view paths. They are named in the comment rather than refactored blind - the anchors in here are subtle and that is worth its own change.
Owner, looking at 267 dishes whose nutrition no customer could see: "no need to worry about correct value. later we can update. now i want all values." dish-facts.js was withholding them on purpose. `nutrition_source: 'estimated'` means a machine guessed from the dish name and nobody has checked, and the rule was that a guess earns nothing. His fill wrote 267 dishes that way, so the whole of it was dark. THE NUMBERS GO OUT NOW, FLAGGED. A number a menu calls an estimate is not a false claim; it is a number with its provenance attached, and a customer can weigh it. The dish sheet says "Estimated, not measured" once, under the figures rather than on each of them, and only when there are figures to say it about. THE BADGES DO NOT. "Heart healthy" and "Diabetic friendly" cannot be labelled as guesses in any way somebody reads carefully: a badge is an assertion, and a machine's guess about a dish name supports nothing. His earlier ruling was that one may "only be shown when the recipe/nutrition actually supports the claim", and that still holds. Claims are still computed from the tags the kitchen ticked itself. The two instructions do not conflict once values and assertions are separated, which is the whole of the change. `nutrition_estimated` had to be NAMED in the ordering bundle's catalogue literal - the fifth field that has. Without it the numbers would arrive and the sentence saying what they are would not, which is worse than not sending them. dish-facts.js ships byte-identical in the api and the desktop frontend so the till's live preview and the customer's menu cannot disagree about what a guess earns. There is now a test for that.
A waiter signs in once, not every day
…y-are-estimates The numbers show, and say they are estimates
fix: bind order decisions to literal tenant values
The tender asks for what the bill says, a saved printer is checked, and a dish can be tapped
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.
Promotes develop to main. Four merged pull requests, one mine and three from other prompts.
Merge with a merge commit, never a squash.
The kitchen ticket, in two goes
#875 and #876 are one problem finished properly. A KOT line was carrying the catalogue's sentence whenever the waiter had not typed a note, so a kitchen was reading
Chicken Biryani sold as 1 handi, configured for a INR Restaurant Demo Dataset POS demooff the paper.#875 fixed the cart write and the HTML ticket. The paper did not change, because neither of those is what prints in a kitchen:
kot-manager.jsbuilds the ESC/POS payload from the sale's lines, and both of its builders fell back to the catalogue description. #876 is the one that reaches the printer.Worth noting for whoever reads this later: the first fix looked complete and was verifiable as complete on screen. It took the owner holding the paper to find that the screen and the printer are two different code paths.
#874 puts the kitchen speaker's switch on the Sale tab of Core Settings, beside the other device-local switches, rather than under the restaurant module where it had been filed.
One motion system for the ordering pages
#877, mine.
order.csscarried thirteen durations in two notations andeasetwenty-five times, and the/menupage had grown its own copy of the same sprawl in an inline stylesheet, so the two pages of one product disagreed about how long a movement takes.Now four durations scaled by distance, three curves, and no
ease- it is the browser default, it is symmetrical, and nothing physical moves that way.It also adds the two lines that most decide whether a page reads as an app rather than a document: no grey flash on tap, and no 300ms wait before a tap is believed. Pinch to zoom is kept. And one press response everywhere, replacing fourteen
:activerules that had grown up separately.The tests are a ratchet rather than a snapshot: they do not care what the numbers are, they care that there is one set and that nothing writes its own.
What to watch
The frontend deploy carries the motion work and the ordering pages. The desktop side is where the ticket fix lands, and the only real proof of that one is paper coming out of a printer.
Verified before opening
Each branch was green before merge;
scripts/check-locally.jspasses all seven ondevelop.