Release: coupons on the ordering page, and a voice line that hears the customer - #890
Merged
Merged
Conversation
The product has shipped coupons since the loyalty release, and the channel they exist to drive could not take one: every coupon route sits behind an authenticated user, and a customer at a table has no account and never will. A shop could print a code on a flyer and its own QR menu would refuse it. TWO DOORS, and the split between them is the design. The PREVIEW says whether a code is real and what it offers - percent or amount, the minimum spend, the cap - so somebody can see the terms before committing. It never returns a money figure for a basket. Working one out would mean pricing every line a second time away from the order path, and two places that price a basket are two places that will one day disagree about what a customer owes. The ORDER says what it is worth, once, from the shop's own coupon document, through the same service the till uses. The request carries a CODE and nothing about money: a page that could name the value of a coupon could name one nobody published. AN UNKNOWN CODE REFUSES THE ORDER rather than quietly dropping the discount. Somebody who typed a code, saw a price and pressed pay must not be charged more than the number they agreed to. A COUPON CANNOT DODGE A MINIMUM. The delivery charge and the minimum-order check are worked out on the food BEFORE any coupon, so a shop's "we do not send out under 200" still means 200 of food. It cannot make the shop pay the customer either: the discount applies to the food, never the delivery fee, and never below zero. THE REDEMPTION IS RECORDED AFTER THE ORDER EXISTS. A coupon with a usage limit is a promise to everybody who has not used it yet, and counting one against an order that then failed to save would spend somebody else's turn on nothing. `apply` is idempotent per sale, and a failure to record is logged rather than fatal - the customer's order is worth more than the shop's note about it. One shape of refusal on the public door: a code that does not exist and one that has run out answer the same way, so it cannot be used to read a shop's coupon list one guess at a time. Behind the same flood limiter as the placed-order routes, and declared in the anonymous-route allow-list with its reason. Nine new strings with Tamil in both copies of the dictionary. No example code in the placeholder: an invented one is something a customer might actually type.
Owner: "table restriction and restaurant oriented stuff only when restaurant enabled. otherwise treat that as normal retail shop." Two things were doing it wrong, and both worked, which is why neither had been noticed. ONE DEFAULT FOR EVERY SHOP. `[dine_in, takeaway]` went out whatever kind of shop it was, so a hardware shop that had never opened the settings page was telling customers it had tables. The customer's page filtered it out afterwards - correctly - which meant the payload itself said something untrue and only the page's manners hid it. A page that has to correct the server's answer is a page that can forget to. A retail shop now defaults to pickup, and a stored list that somehow says dine_in has it removed for a retail shop whatever the document claims. THE TABLE LIMIT ASKED THE WRONG QUESTION. It asked whether a table NUMBER had arrived, never whether the shop runs table service. A printed code can carry a segment for all sorts of reasons - a venue, a unit, a code reused from a floor plan somebody abandoned - and the moment one did, a retail counter refused a customer's order with "Table 5 already has an open order". DELIVERY IS IN NEITHER DEFAULT, deliberately. A shop that has not said it delivers must not be offered to a customer as delivering: that takes an order nobody can fulfil, and the customer finds out when nothing arrives. Missing an option is a smaller harm than promising one. A shop that HAS chosen delivery is believed - the default is silence, not a ban. The fulfilment words were already right and are untouched: the page maps a retail "takeaway" to "pickup" and a restaurant "pickup" to "takeaway", so the two are one thing wearing the word its customer would use. One existing test pinned the exact text of the guard it was protecting. Its stated intent - that `openTableLimit > 0` gates the whole block - is preserved and strengthened, so the assertion now allows further conditions in the same `if`. That is the third source-regex test this week to fail on a spelling rather than a behaviour.
Owner: "live conversations are charged so much. second outside talk is the problem while do live conversation. but press and talk not good ux." Both complaints have one root, and it is not the design - the design was already right and was being fought by a microphone setting. The page decides who is speaking by DISTANCE. The customer is at arm's length; the next table is three metres away, and sound falls off fast enough that the near one is many times louder. That ratio is what NEAR_ENOUGH measures, and it is a good discriminator: people cannot be told apart by a microphone, but near and far can. AUTOMATIC GAIN CONTROL EXISTS TO DESTROY EXACTLY THAT RATIO. It quietens the loud and lifts the quiet until everything arrives at one level, so the table behind was being amplified up toward the threshold meant to exclude them. The hands-free gate was working against its own microphone, which is why press-and-talk felt like the only thing that worked. It is off now. The cost is real and worth naming: a softly spoken customer in a loud room arrives quieter at the far end. That is the right trade. A line that mishears a quiet customer asks them to repeat; a line that hears the next table answers a question nobody asked, on the shop's money. Noise suppression and echo cancellation stay - those help and do not flatten the distance. AND A LINE NOBODY IS TALKING TO NOW HANGS ITSELF UP. A realtime line bills for the time it is held, not only for what is said into it, and the only things that closed one were an order going through or the customer pressing the button again. A phone put face down on a table kept a paid connection open until the tab was closed. Nobody ever sees that happen; it arrives at the end of the month. Ninety seconds, measured on NEAR speech rather than on any sound - a busy restaurant must not hold the line open on the room's behalf, which is the same distinction the gate makes, used for the other purpose. The assistant speaking counts as activity, so an answer somebody asked for is never cut off mid-sentence, and a fresh line starts its patience from the moment it opens.
…ot-the-room The line hears the customer, not the room
…table-rules A shop with no tables has no table rules
…ng-page A coupon works on the ordering page
Owner: "so table order, take away (people who visited, pick up, delivery these kind of methods are there. so order should have these for restaurant. for retails shops may have pickup, delivery options." The model was already right and there was no way to use it. Every shop ran on one hard-coded default - dine in and takeaway - because nothing in the settings page ever wrote `fulfilment`. A shop that delivered could not say so, and a counter that did not was being described as having tables. Three boxes on the Online Ordering form: eat here, collected at the counter, delivered to an address. Saved through the same endpoint the rest of that form uses, into the same field the customer's page already reads. NOTHING TICKED MEANS THE DEFAULT, not "offers nothing". A shop that has never been asked is drawn unticked rather than pre-filled: a default shown as a tick reads as a decision somebody made, and the next person to look would have no way to tell the two apart. The help text under the boxes says what nothing ticked means. The table box is hidden for a shop with the Restaurant module off. Ticking it there would do nothing - the server strips dine_in from a retail shop's list whatever the document says - and a control that cannot take effect is worse than no control. Read tolerantly, because the console caches that switch as the string 'enable'. The form only posts the list when the boxes are on screen, so a screen that never drew them cannot wipe what a shop chose. Five lang_ keys, translated into all seventeen packs.
The first accessibility pass over the customer pages, and it leaves behind
a ratchet rather than a one-off. Contrast is the thing that regresses
silently: a colour is always chosen by eye, and the eye choosing it is
looking at a good screen in a well-lit room.
ONE VALUE WAS WRONG. `#0f8a3d`, used for "ok" and for the veg marker on
both pages, is 4.45:1 on white against the 4.5:1 that AA asks of body text.
Close enough that nobody would notice by looking, and either side of a line
that is not a judgement call. It is #0e8038 now - 5.04:1, and the same
green to look at.
WHAT WAS ALREADY RIGHT, and I checked rather than assumed:
- every other text colour on both pages clears AA, in both themes
- the menu's dark palette is comfortable: nothing under 6.9:1
- the language attribute is set from the chosen language at load, and the
toggle RELOADS rather than swapping text in place, so it can never
disagree with the words on screen. I suspected this one and was wrong
- focus styles exist, reduced motion is honoured, images carry alt text,
buttons carry labels, and the sort radios are wrapped in their labels
rather than orphaned
The test checks its own arithmetic against known WCAG values, because a
contrast test with a wrong formula passes everything.
WHAT IS STILL MISSING, and is not a colour: /order has no dark mode at all
while /menu has one. A menu read across a dinner table in the evening is
the common case, and the ordering page is the one people spend longer on.
That wants somebody watching it render, not a ratio, so it is named here
rather than guessed at.
The menu can be read
…vels A shop says how its food travels
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. Three merged pull requests, all mine.
Merge with a merge commit, never a squash.
#887 - a coupon works on the ordering page
You have shipped coupons since the loyalty release and the channel they exist to drive could not take one: every coupon route sits behind an authenticated user, and a customer at a table has no account.
Two doors. The preview says whether a code is real and what it offers, and never a money figure for a basket. The order decides the value, once, from your own coupon document. An unknown code refuses the order rather than quietly charging full price, a coupon cannot dodge a minimum, and the redemption is recorded only after the order exists.
#888 - a shop with no tables has no table rules
There was one fulfilment default for every shop, so a hardware shop that had never opened the settings page was telling customers it had tables. And the table limit asked whether a table number had arrived, never whether the shop runs table service - so a retail counter whose printed code carried a segment refused customers with "Table 5 already has an open order".
#889 - the line hears the customer, not the room
Both had one root. The voice page decides who is speaking by distance, and automatic gain control exists to destroy exactly that ratio - it was lifting the next table toward the threshold meant to exclude them. The hands-free gate was fighting its own microphone.
Plus a ninety-second idle hang-up: a realtime line bills for the time it is held, and a phone put face down kept a paid connection open until the tab closed.
What to watch
The frontend deploy carries all three customer-facing halves. The API deploy carries the coupon pricing and the table rule.
The first thing worth checking on a real shop is a coupon: type a code that does not exist and confirm the order is refused rather than charged at full price.
Still red on main
Test build (main)is failing and the fix is #883, open and green. It is a test pinning an old spelling, not a regression - but it means every branch inherits a red build until it merges.Verified before opening
Each branch green before merge;
scripts/check-locally.jspasses all seven ondevelop.