Skip to content

Release: nutrition that can be filled in, and filters that answer with something - #759

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

sridharkalaibala merged 35 commits into
mainfrom
develop

Conversation

@sridharkalaibala

Copy link
Copy Markdown
Contributor

Promotes develop to main. Eleven merged pull requests since the last release, from several parallel sessions.

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

What reaches production

Nutrition can actually be filled in (#745). The feature shipped in the last release and is invisible on every shop, because the way to fill it in was eight numbers per dish and the assistant did one dish per press - 272 presses on a real menu. There is now a pass over the whole menu under Items, Tools.

The rule the feature was built on needed extending to survive that. Until now every stored figure was somebody's word, because the only way numbers reached a dish was a person typing them or pressing Estimate and then Save. A pass that walks 272 dishes unattended breaks that, and an unchecked guess would have started earning "Heart healthy" and "Diabetic friendly" on a live menu - the same harm as a tick box for those words, by a door that is much harder to see. So nutrition now records who said so, and nothing derived from an estimate is published - no badges, not even the calorie figure - until a person opens the dish and saves it.

A filter that would answer with nothing is no longer offered (#745). Owner: "whenever you show filter, no item in the list then dont show that filter in menu." The new sort-and-filter sheet already worked that way; two older chips did not, because each was gated on a question slightly different from the one its own filter asks. "Veg only" appeared whenever any dish had a diet mark - so a steakhouse offered a button whose only possible result was an empty menu. "Available now" is hidden in both directions now: nothing available empties the menu, everything available changes nothing.

Checked against the real production menu rather than assumed - Azure Costal Kitchen, 29 sections, 272 dishes, zero diet marks, 272 of 272 available. "Available now" is on that page right now with nothing it could do.

Bill and kitchen work from other sessions: a bill can print twice (#751), the bill adds up (#746), one place decides what the bill carries (#747), the bill card and customer GSTIN recovered after never reaching develop (#756), the phone says how many copies (#754), and the kitchen reads "less spicy" (#744).

Housekeeping: develop is green again (#752) and three dependabot bumps (#748, #749, #750).

Verified before opening

Root suite 2,458 of 2,461 on the develop tip. The three remaining are the release-artifact tests that need frontend/public built, which CI does. API suite 10,625 of 10,625.

Note for after the merge

Nothing in the estate has nutrition entered, so badges and health filters stay invisible on production until a shop with an AI key runs the pass. That is the design - nothing is ever claimed from missing data - but it means this release changes nothing a customer sees until somebody fills a dish in.

sridharkalaibala and others added 30 commits September 15, 2026 01:02
Owner: "whenver i start polling, i see all prints are going. i dont know
how 10 or 15 prints going".

The chain, end to end:

1. The server keeps offering a job until last_printed_change_index
   advances past it. That index is the durable guard.
2. It only advances when POST /sales/markKitchenPrinted succeeds.
3. That call's answer was never looked at. fetch does not throw on a 401
   or a 500 - it resolves with ok:false - so the very next line logged
   "Marked N order(s) as printed" whatever came back.
4. Within one run, the in-memory set hid it.
5. On a restart that set is empty, the server is still offering
   everything from today, and the whole day prints again. Ten or fifteen
   tickets, exactly as described.

So the ledger in the previous commit stops the reprint, and this stops
the backlog that fed it. The response is checked now, and a failure says
what happens next rather than only that it failed - "they will be offered
again; the print ledger is what stops them printing twice" is the part
somebody reading a log at nine in the evening actually needs.

THIS IS THE THIRD TIME IN THIS AREA that a return value which was the
only evidence of success was thrown away: the four winspool calls, the
result of silentPrint at its call site, and now this. Worth naming as a
pattern rather than three separate fixes.

The silentPrint one is still not fixed, on purpose: correcting it means
retrying failed prints, and retries are how duplicates happen. It wants
the queue and a decision.
Owner, holding a ticket: "when item print, item notes not printed.
example 'less spicy' not printed in the kot. its bad very bad".

He is right, and it is worse than it looks. The note is the one line on a
ticket the kitchen cannot work out for itself - the dish, the table and
the quantity a cook could reconstruct, the note they cannot. A customer
who asks for something, is told yes, and does not get it blames the
restaurant, and the restaurant never finds out it happened.

WHERE IT WAS LOST, and not where anybody would look first.

The note was stored correctly all along. _priceOnlineLine puts it on the
sale item as item_description, and both ticket builders print it -
escpos-kot renders ** less spicy ** under the dish when the field is
there. Reading the printing code finds nothing wrong, because nothing is.

But the kitchen ticket is not printed from sale.items. The poller builds
its print jobs out of changes[].items - the record of what changed - and
that list carried eight fields: id, name, quantity, process, code, unit,
price, total. No note. FOUR places build that list and all four dropped
it: a new order, an amended line, a cancelled line, and a line removed
from the order.

The fourth was nearly missed. It reads from a snapshot of the existing
items taken earlier in the same function, and that snapshot did not carry
the note either, so it needed fixing in two places rather than one. A
removal ticket says which dish to stop, and a table with two of the same
dish is told apart by the note - exactly the case where getting it wrong
cancels the wrong plate.

On an amendment the request's note wins over the stored one, because the
request carries what the person has only now said and the stored copy is
what they said before. Reading the stored one first would quietly ignore
a change of mind.

Also in here: the acknowledgement check from the print-ledger work, which
was pushed after #741 had already been merged and so never reached main.
Without it a failed markKitchenPrinted still logs success, the server
keeps offering the same jobs, and a restart prints the whole day again.
Nutrition shipped last night and is invisible on every shop in the estate,
because the way to fill it in is eight numbers per dish and the assistant does
one dish per press. Azure Costal Kitchen has 272 dishes. Nobody was ever going
to press that button 272 times, so the panel stays empty everywhere and the
feature may as well not exist.

So there is a pass over the whole menu, in Tools beside the other catalogue
levers. And the moment that exists, the rule the whole feature was built on
comes under a new kind of pressure.

A GUESS MAY BE STORED. IT MAY NOT BE PUBLISHED.

Until now the question could not arise: the only way numbers reached a dish
was a person typing them, or pressing Estimate and then Save. Either way a
person put them there, so every stored figure was somebody's word. A pass that
walks three hundred dishes unattended breaks that, and an unchecked guess would
start earning "Heart healthy" and "Diabetic friendly" on a live menu - the same
harm as a tick box for those words, arriving by a door that is much harder to
see, and one I would have walked through without noticing.

So nutrition now records WHO SAID SO, and dish-facts publishes nothing derived
from an estimate: no badges, and not even the calorie figure, because a number
on a menu is itself a claim. The shop sees them on its own screen, where they
are a draft. Confirming is opening the dish and saving it, which is one
deliberate act by somebody accountable.

Empty means kitchen, deliberately: every number stored before this field
existed got there because a person typed it and saved, so reading the absence
as "unverified" would strip the badges off every dish already done.

The server sets that word, never the caller. A client that could send
'kitchen' alongside its estimate would publish the guess and nothing anywhere
would say so.

THE OTHER THREE THINGS THE SCREEN OWES THE SHOP.

What it costs: one call to their own AI provider per dish, so the count is
read first and put in the button. "Run this over your menu" with no number in
front of it is not a choice anybody can make.

That it can be stopped: the browser walks the list, one dish at a time, so
Stop means stop and closing the window means stop. A job on the server could
do neither, and would have to invent its own progress reporting besides.

That a refusal ends it: 400 from that route means the AI is refusing - no key,
provider down, cap spent - and every remaining dish would fail identically.
Asking three hundred times in a row is how a rate limit becomes a ban.

The write is four fields by name, not a replay of the item form's upsert -
that writes every field on the document, and anything the caller did not send
comes back as a default. Survivable when a person is looking at a form; not
when a loop is walking a menu unattended. It skips any dish a person has
already answered, so the pass is safe to run twice.

The entry explains itself instead of vanishing. The single-dish button hides
when there is no AI key, but the only place that asks about availability is
the item FORM and this lives on the item LIST - a gate written the same way
would have been dead code and the entry hidden on every shop forever. It says
where to paste the key instead, which nobody finds on their own.

No local t() wrapper in the new file. That is how the requests dock ended up
with its entire key set in no language pack: the scanner reads literal
PosnicPro.i18n.t calls and a helper hides every one of them. All 24 keys are
spelled out, translated into all seventeen packs, and coverage held at 93%.
A printed bill read "Paneer Starter  2 x 240  480.00" above a subtotal of
400.00. Two faults were hiding each other: the line carried the tax-inclusive
amount while the subtotal added the exclusive ones, and the unit price was
never printed at all, so the only number a customer could check against the
menu had tax folded into it and the two halves of the document disagreed by
exactly the tax.

The line is now the pre-tax line, with a RATE column beside the quantity:
200.00 x 2 = 400.00, and the tax added once underneath where it can be seen
being added. The column appears only when the payload carries rates, so a
counter receipt is laid out exactly as before.

Three things the bill said that are not its business are now off by default:

  UNPAID   a document presented at a table asking for money is unpaid by
           definition. Removed from both print paths - the browser view and
           the main process - or a shop would see it come back depending on
           who pressed print.
  From:    which device sent the order settles a dispute for the shop. The
           kitchen ticket still prints it every time.
  Table, order type, covers, item count
           all of it is the restaurant talking to itself on the customer's
           copy, and the kitchen ticket carries every one.

None of it is deleted. A hotel restaurant's tax invoice prints table, session,
steward and covers, so each is a switch: bill_print_source, bill_print_table,
bill_print_dine_type, bill_print_covers, bill_print_steward and
bill_print_total_qty. Absent means OFF, which is the direction that matters
with 90 shops printing today: a default of on would put new rows on every one
of their bills the morning this deploys.
…s does

TWO THINGS, one card.

A customer who gave a GSTIN now gets it on their bill. A business buying a
meal can reclaim the GST on it, but only against an invoice carrying its own
GSTIN - without one the document is not something the claim can be made on, and
the customer does not find out at the counter, they find out months later at
filing time holding a bill that looks perfectly correct.

Nearly all of that chain already existed: the customer form validates a GSTIN,
the sale copies it onto customer_gst_number, and the GSTR reports read it. The
only broken link was the last one. The bill never printed it.

NO SETTING GATES IT. Every other optional row is a switch, because whether a
shop wants table numbers on its paper is a decision it makes once. A customer
handing over a GSTIN is not a shop decision, and making them wait at the
counter while somebody finds a settings page is the failure being fixed. Given
means printed. A malformed one is not printed at all: the shape is checked
again here, because a wrong GSTIN on a tax invoice fails a claim months later
and by then the bill is the only record.

Company Name is new, on the customer and copied onto the sale. A GSTIN belongs
to an entity and the name above it has to be that entity; a customer record is
often the person who walked in, and "Ramesh Kumar" over a company's GSTIN is a
defective invoice. Empty is right for a sole proprietor and prints nothing.
Copied onto the sale rather than read back, so a bill reprinted in March says
what it said in September.

AND THE SWITCHES BECOME REACHABLE. The rows the bill stopped printing - table,
order type, covers, steward, total quantity, where the order came from - were
switches nobody could reach, which is a row a shop can never get back. They now
sit on the Receipt Print tab beside Auto Print and Print Logo, under "What the
bill carries", each with a line saying what it is. All off by default: a shop
that never opens the card keeps the bill it prints today.

The chain has five links and any one drops the value silently, so a test walks
all five for every switch, and another proves each one changes the bill on its
own so a dead toggle cannot hide behind a working neighbour. None of them
reaches the kitchen ticket.

Eighteen new strings, translated into all seventeen packs rather than left in
English: a shop running the till in Tamil should not meet six English toggles
on a settings card. Two labels reuse keys that were already translated.
Bumps the desktop-patch-and-minor group with 1 update: [lint-staged](https://github.com/lint-staged/lint-staged).


Updates `lint-staged` from 17.5.0 to 17.5.1
- [Release notes](https://github.com/lint-staged/lint-staged/releases)
- [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md)
- [Commits](lint-staged/lint-staged@v17.5.0...v17.5.1)

---
updated-dependencies:
- dependency-name: lint-staged
  dependency-version: 17.5.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: desktop-patch-and-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the api-patch-and-minor group in /api with 8 updates:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1127.0` | `3.1131.0` |
| [compression](https://github.com/expressjs/compression) | `1.8.1` | `1.8.2` |
| [joi](https://github.com/hapijs/joi) | `18.2.8` | `18.2.9` |
| [mongoose](https://github.com/Automattic/mongoose) | `9.9.5` | `9.10.0` |
| [morgan](https://github.com/expressjs/morgan) | `1.12.0` | `1.12.1` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `10.0.1` | `10.0.8` |
| [@redocly/cli](https://github.com/Redocly/redocly-cli) | `2.51.2` | `2.52.1` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.5.0` | `26.5.1` |


Updates `@aws-sdk/client-s3` from 3.1127.0 to 3.1131.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1131.0/clients/client-s3)

Updates `compression` from 1.8.1 to 1.8.2
- [Release notes](https://github.com/expressjs/compression/releases)
- [Changelog](https://github.com/expressjs/compression/blob/master/HISTORY.md)
- [Commits](expressjs/compression@v1.8.1...v1.8.2)

Updates `joi` from 18.2.8 to 18.2.9
- [Commits](hapijs/joi@v18.2.8...v18.2.9)

Updates `mongoose` from 9.9.5 to 9.10.0
- [Release notes](https://github.com/Automattic/mongoose/releases)
- [Changelog](https://github.com/Automattic/mongoose/blob/master/CHANGELOG.md)
- [Commits](Automattic/mongoose@9.9.5...9.10.0)

Updates `morgan` from 1.12.0 to 1.12.1
- [Release notes](https://github.com/expressjs/morgan/releases)
- [Changelog](https://github.com/expressjs/morgan/blob/master/HISTORY.md)
- [Commits](expressjs/morgan@1.12.0...1.12.1)

Updates `nodemailer` from 10.0.1 to 10.0.8
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](nodemailer/nodemailer@v10.0.1...v10.0.8)

Updates `@redocly/cli` from 2.51.2 to 2.52.1
- [Release notes](https://github.com/Redocly/redocly-cli/releases)
- [Commits](https://github.com/Redocly/redocly-cli/compare/@redocly/cli@2.51.2...@redocly/cli@2.52.1)

Updates `@types/node` from 26.5.0 to 26.5.1
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1131.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-patch-and-minor
- dependency-name: compression
  dependency-version: 1.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-patch-and-minor
- dependency-name: joi
  dependency-version: 18.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-patch-and-minor
- dependency-name: mongoose
  dependency-version: 9.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: api-patch-and-minor
- dependency-name: morgan
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-patch-and-minor
- dependency-name: nodemailer
  dependency-version: 10.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: api-patch-and-minor
- dependency-name: "@redocly/cli"
  dependency-version: 2.52.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: api-patch-and-minor
- dependency-name: "@types/node"
  dependency-version: 26.5.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: api-patch-and-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps the actions group with 1 update: [github/codeql-action/upload-sarif](https://github.com/github/codeql-action).


Updates `github/codeql-action/upload-sarif` from 4.37.9 to 4.38.0
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@cdf488f...b96794f)

---
updated-dependencies:
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.38.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Owner: "when captain app send print bill we need to have 2 copies actually.
better to keep this as settigs how many copies in the settings page."

A restaurant hands one to the guest and keeps one, and the second used to be a
second walk to the printer - so a shop either did without it or a waiter lost a
minute a table on the busiest night of the week.

ONE JOB PER COPY, rather than one job that says "twice". Every till already on
a shop floor drains this queue and prints what it is handed, so a shop gets its
second copy the moment it changes the setting, with no new version of the
desktop app. It is also the truer shape: each copy succeeds or fails on its
own, and a printer that jams on one leaves a job to retry rather than a job
half done. The labels say "(2 of 2)" so the counter can see a pair rather than
two bills for one table.

Settings, Receipt Print: a count from one to three. Default ONE, because
ninety shops print bills today and none of them asked for a second copy this
morning. Capped where it is read, so a number arriving by any other route
cannot spend a roll of paper on one table, and read defensively because that
form posts strings.

Counter receipts are untouched. This is the bill a waiter asks for from the
handset.
A bill asked for from the floor can come out twice
The bill agrees with itself, and stops talking to the restaurant
…ill-carries

One place decides what the bill carries, and a customer's GSTIN always does
…atch-and-minor-e2184960c7

Bump lint-staged from 17.5.0 to 17.5.1 in the desktop-patch-and-minor group
…-9449540d05

Bump github/codeql-action/upload-sarif from 4.37.9 to 4.38.0 in the actions group
…atch-and-minor-a302956bf3

Bump the api-patch-and-minor group in /api with 8 updates
Nothing disagreed. This branch appended the nutrition-pass strings and develop
appended the bill-copies strings, both after the last existing key in all
seventeen packs, so every pack conflicted on the line that had to grow a
trailing comma.

Resolved as JSON rather than as text: each pack is this branch's keys in its
own order, then any key develop added that was not already there. Checked
first that no key had its value changed on both sides and none was deleted on
one side only - that would have been a real disagreement, not a collision of
two appends, and is not something to settle mechanically.

check-translations.js says All good and coverage is unchanged at 93%.
Owner: "whenever you show filter, no item in the list then dont show that
filter in menu. example heart healthy food not in our menu then dont show the
filter itself."

The sort-and-filter sheet was built that way already - every option is counted
against the menu first and only offered if a dish carries it. Two older chips
were not, because each was gated on a question slightly different from the one
its own filter asks.

VEG ONLY was gated on "does any dish have a diet mark at all". A steakhouse
marks every dish non_veg, so the mark is present on all of them, the chip
appears, and tapping it empties the menu and says "Nothing on the menu is
marked vegetarian." The gate now asks exactly what the filter asks - veg or
vegan - with unmarked still never assumed either way, because a shop that
never filled the field has promised nothing.

AVAILABLE NOW, on the public menu, was shown to every restaurant. It is hidden
in both directions now: nothing available means it empties the menu, and
everything available means it changes nothing at all. A control that cannot
alter what you are looking at is the same annoyance as one that empties it.

Checked against the real production menu rather than assumed. Azure Costal
Kitchen: 29 sections, 272 dishes, ZERO diet marks, 272 of 272 available, none
carrying a health claim. So the veg chip is already correctly hidden there and
the health filters never appear - but "Available now" is on that page right
now with nothing it could possibly do, which is the complaint exactly. Driving
the live page confirmed it: vegChip hidden, availChip SHOWN, 272 dishes.
…ithub.com:Posnic/POS into feat/fill-in-a-menu-without-three-hundred-presses
Two dependabot merges took develop red, and every branch cut from it inherited
the failure. Nothing here is a code fix; both are decisions the automation is
not allowed to make on its own.

MONGOOSE 9.10 CANNOT TALK TO THE TEST DATABASE

#749 moved mongoose from 9.9.5 to 9.10.0 as part of a patch-and-minor group.
Mongoose 9.10 moves its bundled mongodb driver from ~7.5 to ~7.6, and the
handshake 7.6 sends is rejected outright by the mongod that
mongodb-memory-server runs:

  MongooseServerSelectionError: Missing required sub-document 'driver' in the
  client metadata document

So it is not a test that broke, it is the connection. Every suite that opens a
database failed at beforeAll. Reproduced locally by installing 9.10.1 against
mongod 8.2.6 and watching the same error, then cleared by going back to 9.9.5.

Held at ~9.9.5, which still takes patches, and dependabot is told the version
range and the reason rather than being left to reopen the same pull request on
Monday. The note says how to lift it: run the api suite against 9.10 or later,
and if it connects, widen it back.

THE CODEQL PIN WAS NOT APPROVED, AND THAT GUARD WORKED

#750 bumped github/codeql-action/upload-sarif to v4.38.0. The workflow carries
the new SHA; tests/scorecard-workflow.test.js carries the list of SHAs a person
has actually checked, and nobody had checked this one, so it failed. That is
the guard doing its job, not a stale test.

Verified upstream before approving, the way the existing comment instructs:

  gh api repos/github/codeql-action/git/ref/tags/v4.38.0
    -> annotated tag 4bd7200e1f146b1c937cae12d258b50f41a53cf8
  gh api repos/github/codeql-action/git/tags/4bd7200e...
    -> commit b96794f015dfd88f77b49b1c93e0fa7110f94c63

which is the commit the workflow now pins. The comment records the commands and
the trap: a signed release's refs/tags/vX points at a TAG OBJECT, so reading
.object.sha off the ref approves the tag rather than the commit it names.

API 10625 pass, desktop 2427 pass, both zero failures.
develop is green again: a held driver and a verified pin
…-hundred-presses

A menu can be filled in without three hundred presses
Bumps the frontend-build-tools group in /frontend with 1 update: [sass](https://github.com/sass/dart-sass).


Updates `sass` from 1.103.1 to 1.104.0
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](sass/dart-sass@1.103.1...1.104.0)

---
updated-dependencies:
- dependency-name: sass
  dependency-version: 1.104.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-build-tools
...

Signed-off-by: dependabot[bot] <support@github.com>
Owner: "its better two copies from captain itself... if its from desktop
sometimes if print bill not workin from app everytime all will be printed 2 and
2 times. then configuration change reequired pos guy wont have permission. lets
keep in app itself."

He is right about who should hold it. The person who wants a second copy is the
one holding the phone, and making them find somebody with access to the till's
settings page is how a setting stays wrong for a year. So a request may name a
number and it is honoured; the shop's setting answers when it does not, which
is how every handset in the field today keeps behaving exactly as it does.
Clamped at three wherever it came from - a phone is not allowed to spend a roll
of paper on one table, and neither is a stray value in a branch document.

The duplication he feared was real, and it was not about copies. A waiter
walking to the counter taps Print bill again because nothing has come out yet,
and the queue was guarded by how many tickets are OPEN rather than how many
this tap actually marked: two taps, two bills. With copies at two it would have
been four.

The intent was always in the update, which skips a ticket that already carries
a timestamp - it simply never reached the queue. Taking the ids first is what
joins the two, so a repeat tap has an empty list and prints nothing.

Counter receipts are untouched. This is the bill a waiter asks for.
…elop

PR #747 says MERGED and its work was not on develop.

It was stacked on fix/the-bill-adds-up while #746 was in review. #746 went into
develop at 05:44:45, and #747 went into fix/the-bill-adds-up ten seconds later,
into a branch that had already been merged and had nothing left to carry it
onward. Both pull requests are green and closed, one of them delivered nothing:
the settings card, the customer GSTIN on the bill, the company name and the six
switches were all sitting in a dead end.

Nothing is rewritten here. This merges that branch into today's develop, so the
history stays honest about where the work came from.

The seventeen language packs conflicted, exactly as expected: #745 appended the
nutrition-pass strings and this appended the bill-card strings, both after the
last key. Resolved as JSON rather than as text - this branch's keys in order,
then any key develop added that was not already there - after checking that no
key had its value changed on both sides and none was deleted on one side only.

check-translations.js says All good, coverage holds at 93%, and the recovered
content is present: bill_print_table in the branch model, customer_company_name
on the sale, the card in settings_write.html.
The phone says how many copies, and a second tap adds none
tests/a-stored-claim-never-reaches-a-customer.test.js matched the storefront's
destructuring with `...rest } = item;` - a single space before the brace. That
was true when it was written. Adding nutrition_source to the list made the
statement long enough for prettier to break it across lines, and the pattern
stopped matching code that had not changed meaning at all.

It has been failing on develop since #745 merged, and failing in the most
expensive way a test can: its message says the storefront may be leaking stored
health claims to customers, which is a real and serious thing, and it was not
happening. The code destructures all four raw fields correctly.

Now tolerant of whitespace between `...rest` and the brace, and of the spacing
around `=`. What it asserts is unchanged: the raw fields must be destructured
away in the same statement that spreads the rest.
Recover the bill card and the customer GSTIN, which never reached develop
Owner: "daily price starts in the morning only. means 7am. not midnight coz up
to 1am restaurant might open."

A restaurant sets its fish prices when it opens and serves until one. On a
calendar day those prices expire in the middle of service: at midnight every
one reads as yesterday's, the handset starts asking waiters for numbers they
were given at eleven that morning, and this till refuses the dishes until
somebody retypes the lot - at one in the morning, during the last push of the
night.

Shifting the clock back seven hours before the date is read moves the boundary
into the dead hour. A price entered at 11am is still current at half past
midnight and goes stale at 7am, when the shop is opening anyway and is about to
set the new day's rates.

The till decides in the shop's own timezone, as it did. The screens decide on
the reader's clock, as they did. What changed is where the day turns, and all
four now turn it at the same hour - the constant is pinned by a test that reads
it out of the source rather than restating it, so a screen that quietly went
back to midnight would fail rather than disagree in silence.

Found by a test that went red at one in the morning because "two hours ago" was
yesterday. The test was wrong and the rule underneath was worse.
This assertion has been wrong twice for the same reason, and the second fix was
mine.

It began as a regex requiring `...rest } = item;` on one line, which stopped
matching the moment prettier wrapped the destructure. I patched the whitespace,
which fixed the formatting problem and left the real one: the pattern only ever
NAMED nutrition, food_tags and menu_marks. Deleting nutrition_source from the
destructure passed it.

That field is not incidental. It is the flag saying the figures were ESTIMATED
rather than entered by the shop, and dish-facts reads it to decide what may
honestly be claimed. If it travels raw beside the derived list then the thing
this file's own comment warns about has happened: the two lists travel together
and a page picks one.

Asked by name now, so the guard reads the list it is guarding. Each of the four
fields was removed in turn to confirm it goes red:

  field              before      after
  nutrition          caught      caught
  nutrition_source   PASSED      caught
  food_tags          caught      caught
  menu_marks         caught      caught

The approach is the one from the closed pull request #755, which reached this
first and was set aside as not worth a conflict. The conflict is gone now and
the gap was real, so it is worth the ten lines.
The claim guard reads the whole list, not three quarters of it
The trading day starts at seven, not at midnight
…frontend-build-tools-3070532446

Bump sass from 1.103.1 to 1.104.0 in /frontend in the frontend-build-tools group
@sridharkalaibala
sridharkalaibala merged commit e513b87 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