Skip to content

dev to main - #834

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

sridharkalaibala merged 15 commits into
mainfrom
develop

Conversation

@sridharkalaibala

Copy link
Copy Markdown
Contributor

What does this PR do?

Acceptance criteria covered

  • Linked issue has clear acceptance criteria, or this PR adds them first
  • This PR completes the listed criteria or names the remaining work

How was it tested?

Checklist

  • One focused change per PR
  • Only people are credited as authors/contributors; no AI tool attribution or signature footer is included
  • Commits are signed off (git commit -s, DCO)
  • Works fully offline (no new external network calls in the local edition)
  • Matches surrounding code style
  • No real customer, tax, payment, credential, token, or production data is included

A regular orders the same thing. Reading their own history, finding five
dishes and tapping each one back in is work the phone can do, and every
ordering app in the world does it. This one did not.

AT TODAY'S PRICES, NEVER THE REMEMBERED ONE. The kept line says what they
paid last week; putting that number in the basket quotes a price the shop
is not offering, and the customer sees it confirmed at checkout. So the
catalogue product is what goes in the cart and the remembered line is used
only for what it is: which dish, how many, and how they asked for it.

AND IT SAYS WHAT IT COULD NOT ADD. A basket that quietly comes back with
three of five dishes is worse than one that refuses: they check out
believing they ordered what they ordered last week. Anything off the menu,
sold out, or waiting on a price the shop has not set today is named, and
the page waits for them to read it before it moves.

It adds rather than replaces - whatever is in the basket was put there
deliberately a moment ago - and it carries the note and the spice level
back, because those are part of "the same again". Not a spice level for a
dish whose picker the shop has since switched off: that would print on a
ticket for a choice the menu no longer makes.

The button appears only where the shop answered and only for this shop.
The remembered order carries a name and a quantity and NO item id - the id
comes from the shop's own copy - and matching a dish by its name is how a
customer ends up with the wrong one. An id from another shop's menu either
misses or, worse, hits something else.

Four new strings, with Tamil, in both copies of the dictionary.
`build:quick` set ELECTRON_BUILDER_COMPRESSION_LEVEL=fast. app-builder-lib
takes a single digit 0-9 there and throws on anything else, so that
script could never produce an installer. It packaged the whole app,
signed every binary, and then died at the archive step with

  ELECTRON_BUILDER_COMPRESSION_LEVEL must be a single digit 0-9

after about two minutes of looking exactly like a build in progress.

Run through a pipe the shell reports the exit code of the last command
in the pipeline, so it reads as a SUCCESS and leaves whatever was in
dist/ from the last real build sitting there. That is how somebody ends
up testing an installer from a week ago.

It also called cross-env, which nothing in this repo depends on and
which is not installed, so on a clean checkout it would have failed on
the first word instead.

Both go away together: electron-builder has a `compression` setting
whose own documentation says `store` is what to use to rapidly test a
build, and a config override needs no environment variable and no
cross-env.

Four tests, over every script that invokes electron-builder rather than
the one that happened to be wrong: a compression level is a digit, a
script that sets an env var has cross-env to set it with, and cross-env
is installed if anything calls it. The first test asserts there ARE
build scripts, because an assertion over an empty list passes for ever.

Verified by building the installer.
A build reached a shop with server.js asking for ./handset-slots and the
file not beside it. The require sat INSIDE the per-request path, and that
path is an override of server.emit, so the throw did not become a 500 -
it escaped as an uncaught exception and the request was never answered.
The handset held an open socket until it timed out. Nothing appeared on
the till. The shop was told the captain app keeps disconnecting, which is
the same sentence this whole area exists because of.

  [MobileTracker] External request: 192.168.1.2 -> GET /api
  [FATAL] Uncaught Exception: Cannot find module './handset-slots'

Localhost stayed instant throughout, because the slots are only consulted
for non-loopback callers. So everything anybody tried on the machine
itself worked.

Two changes.

The module is required ONCE, at the top of the file, inside a try. A
require in the request path is evaluated per request, and a failure there
is invisible; at the top it is one line in the log at startup.

The decision goes through admitHandset, which never throws and always
answers. Gone, broken, or answering nonsense: the handset is admitted.
The cap exists so a shop is not silently serving a stranger's phone; it
is not a security boundary, the API's authentication is. Against a till
that stops answering its handsets mid-service that is the smaller harm,
and every outage handset-slots.js documents came from refusing a handset
that should have been served.

11 tests. admitHandset is lifted out of the source and driven directly,
because the caller is an emit override and cannot be. The source-level
ones pin where the require lives and that nothing calls .admit inside the
request path again - checked by putting that call back and watching it
fail.

Not the cause of the shop's bad build: the require and the module landed
in the same commit (ec6077e) and the file has never been deleted, so no
clean checkout produces what is installed there. This makes that class of
failure answer something instead of hanging.
This is the actual cause of the till that answered nothing, and it is a
packaging fact rather than a code one.

src/server.js is not in build.files. It is in extraResources, copied to
the resources root and FLATTENED:

  { "from": "src/server.js", "to": "server.js" }

So at runtime it is resources/server.js, and require('./handset-slots')
resolves to resources/handset-slots.js - not to
resources/app.asar/src/handset-slots.js, which is a different directory a
relative require cannot reach.

src/handset-slots.js WAS listed, in build.files, so it went into the
asar. Beside main.js, which does not use it. Never beside server.js,
which does.

AND A TEST ALREADY CLAIMED TO PREVENT THIS:

  assert.ok(pkg.build.files.includes('src/handset-slots.js'))

True, meaningless, and green for the whole life of the bug it names.
Twelfth in the silent no-op family and the most expensive so far.

One line added to extraResources. The new test DERIVES the requirement
instead of restating a filename: for every file flattened out of src/ to
the resources root, every relative require it makes must land at that
same level. It also refuses a require that climbs out of the directory,
which cannot work once flattened, and asserts there are entries to check
at all. Verified by deleting the new line and watching it name the exact
failure the shop saw.

The old assertion now checks both places and says why they are not
interchangeable.

2779 desktop tests, 0 failures.
Owner, reading an Azure bill: "if walk in customer no need to show.
(remove it)".

It was supposed to have been removed already. bill-payload.js had a
guard for it:

  if (name && !/^walk[\s-]?in$/i.test(name)) out.push(name);

That matches "walkin", "walk in" and "walk-in". The name this system
actually writes - in five places - is "Walk-in Customer", which it does
not match. The guard was real, the intent was right, and it had never
once matched the thing it existed to catch. Every cash bill printed the
name of a customer who does not exist.

The writer and the reader each knew a string, and the strings were not
the same string. utils/walk-in.js now holds the one name and the one
question; the sale repository writes WALK_IN_NAME and the bill asks
isWalkIn.

It stays deliberately tolerant - walk in / walkin / walk_in, alone or
followed by customer, guest or client - and deliberately anchored, so a
real customer called Walkinshaw keeps their name on their own bill. A
false positive costs a name nobody gave; a false negative is this bug.

21 tests, and the one that matters takes the name the WRITER uses and
asks the READER about it, rather than a string typed into a test. A test
that invents its own input can only confirm what its author believed the
input looked like, and the belief was the bug. It also scans the source
for any other spelling and fails if one is not recognised.

Checked by restoring the old regex and watching it fail.
The till has priced extra cheese from the shop's own option documents
since the handset learned about it, and the storefront sends the option
sets to every client. This page named none of them, so somebody ordering
from the table could not ask for a thing the waiter standing next to them
could ring up.

Three parts, and the middle one is the risk.

THE CATALOGUE KEEPS THEM. The bundle's whole catalogue is one object
literal, and a field it does not NAME never reaches the page however
correctly the server sent it. That is the fourth time this literal has
cost something.

A BASKET LINE IS A DISH AND A CHOICE, NOT A DISH. Two dosas, one with
extra cheese, are two lines. The basket has always been keyed by the
dish's id, so choosing cheese on the second would have silently changed
the first and the kitchen would have made two cheesy dosas for somebody
who asked for one. The key is now the dish plus what was chosen, sorted so
the same two options picked in either order are one line, and the dish's
real id rides alongside as `item_id`. A line written before this change
carries no item_id and still answers, because everything reads item_id
first and falls back to id.

AND IT NEVER SENDS A PRICE. The deltas are drawn on the sheet, because a
charge that appears at checkout without having been shown is what people
write bad reviews about - but what goes back to the shop is which options
were chosen, by name. The shop prices its own extras. A page that could
name the price of cheese could name a discount nobody agreed to.

A group the shop capped at one draws radios and anything else draws
ticks, because a tick that silently refuses to stay ticked is worse than
one that visibly replaces another. An option that costs nothing shows no
price: "+0" reads as a charge somebody has to work out is not one.

The note and the spice level are set against the line the choice made,
not against the dish - setCartItemNote returns quietly when it finds no
line, so the dish's id would have lost them without a word.
The kitchen tells the floor before it tells anybody with a keyboard. A
waiter who heard "no more fish" had to find whoever runs the till, and in
the minutes that took, three more tables ordered it, three more tickets
printed, and three tables were told no after they had chosen.

NOT BY TOUCHING STOCK, which is the whole design. "Sold out" on the menu
has always meant available_quantity <= 0: counted inventory, the figures
the shop buys against. A waiter zeroing that to warn the floor would be
writing a lie into purchasing to solve a problem that lasts one evening.

So it is its own flag with its own lifetime. Set during service, gone by
the next trading day, which begins at seven in the morning like the fish
prices - a dish taken off during Friday service comes back on Saturday
morning rather than at one a.m. in the middle of the last push. A kitchen
that finds another crate can put it straight back, because whether the
dish is off is a fact about tonight and the floor is where that is known.

Only a dish the branch actually sells: a handset holds one shop's menu
and has no business marking another shop's fish off, even by accident.
Behind the ordinary session like every other write here, because taking a
dish off the menu is a shop decision made by a person, even when that
person is standing at a table holding a phone.

THE SEVEN O'CLOCK RULE NOW HAS ONE HOME. It was written inside
_pricedToday, whose own comment said four screens carried the same
constant, and this would have been the fifth copy. A rule copied five
times is five different rules the first time somebody changes one. It
moved to utils/trading-day.js and _pricedToday delegates; the 21 existing
daily-price tests pass unchanged, which is what makes that safe to say.

Nine new tests, and 171 across the suites this touches.
…extras

The customer can choose the extras the shop already charges for
Three CI failures, all mine, all worth more than a green tick.

THE HOUR GUARD CAUGHT THE REFACTOR, which is exactly its job. A test pins
"every screen turns its day at the same hour, and it is seven" by reading
each surface's source, and moving the till's copy into utils/trading-day
left it looking at a file that no longer holds the constant. It now asks
the question in two parts: the shared rule says seven, and the till still
USES it rather than having quietly grown its own again. That second half
is new, and it is the half that would catch somebody undoing this.

THE DOCS GENERATOR ASKED FOR SOMETHING BETTER than a regenerate. Its
placeholder said the endpoint "has no validation middleware, so its shape
cannot be derived from the source. Adding validation improves both
runtime safety and this document." So /items/soldOut now validates: which
dish, whether it is off or back, which shop. The document describes a
real body instead of an apology, and a caller who reads it can call it.

And prettier on the one file it complained about - my lines only, no
whole-file rewrite.
A waiter can say it has run out, without lying about the stock
@sridharkalaibala
sridharkalaibala merged commit e8ee65e into main Sep 16, 2026
17 of 21 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