Skip to content

Promote develop to main: the kitchen can make a sound - #854

Merged
sridharkalaibala merged 12 commits into
mainfrom
develop
Sep 17, 2026
Merged

sridharkalaibala merged 12 commits into
mainfrom
develop

Conversation

@sridharkalaibala

Copy link
Copy Markdown
Contributor

Promotion so the desktop installer can be re-staged with the kitchen audio in it. main currently has none of this, and the exe is built from main, so an installer staged before this merge is silent whatever else is done to it.

What this carries

The kitchen audio, end to end (#848, #852)

  • The CSP blocked every sound the product makes. Every tone is a WAV synthesised in the main process and handed to the page as a data: URL, and helmet never named media-src, so it fell back to default-src 'self' and the browser refused all of them. Found in a customer machine's log, where the new order alarm had been mute. One directive, media-src 'self' data:.
  • There was no way to turn the announcement on. The setting and the IPC were real and the only route in was typing into the developer console, so nobody ever did. Two switches now sit on Core Settings, plus a Test the sound button, which is the thing that would have found both faults months earlier.
  • The beep is gone. A plain sine contains nothing but its fundamental, which is what a beep is. The bells are struck notes with partials and a decaying envelope: one when the order arrives, a smaller tap before each dish. Three of each, chosen per shop with Play buttons, because a kitchen with a fryer roaring needs something different from a quiet dining room.
  • The voice picks the accent first and the voice within it, so it lands on a woman's voice in Indian English rather than whichever one Windows enumerates first. Stored by name, with an empty name meaning best-on-this-machine, so a till that lacks the chosen voice falls back rather than going silent.
  • A kitchen ticket carries the waiter's note and never the menu's. Two server paths treated a line with no note as one that should borrow the dish's marketing copy. A note on a ticket is an instruction and a cook assumes somebody asked for it.

Also on develop, from other work (#853 and two commits)

  • A call carries its date
  • A sync that existed on paper only
  • A test naming the method the repository actually has

Verified on develop by content, not by PR state

ARRIVAL_BELLS: 3        ITEM_BELLS: 3
bell picker: 2          media-src: 2
menu-copy leak remaining: 0

That check exists because #848 merged with two of its commits left behind: GitHub reported a stale head while the merge button was pressed, and only reading the base branch caught it.

After this

The installer has to be built through npm run build, which runs prepare:frontend and rejects stale artifacts. The last staged exe carried a dashboard bundle built before the renderer half of the audio existed, so the main process was sending posnic:kitchen-call to a page where nothing was listening.

Then, on the machine by the pass: Core Settings, both switches on, press Test the sound.

Do not squash. A squashed release PR conflicts with the next one.

The tones are synthesised in the main process and handed to the page as
data: WAV URLs, so that a till with no internet and no sound files on
disk can still make a noise. helmet's defaults do not name media-src, so
it fell back to default-src 'self', and the browser refused all of them.

Found in a customer machine's log, where the new order alarm had been
mute since the header was tightened:

  Loading media from 'data:audio/wav;base64,...' violates the following
  Content Security Policy directive: "default-src 'self'". Note that
  'media-src' was not explicitly set, so 'default-src' is used as a
  fallback. The action has been blocked.

This is the failure that looks like success: a console warning nobody
reads, and a symptom of silence in a room where silence is expected. The
kitchen announcement could not have been heard whatever else was fixed.

data: only, and media only. A data: URL cannot reach the network and
cannot execute, so the worst a bad one does is make a noise.
The announcement shipped with no way to turn it on. The setting was real
and the IPC was real, and the only way to reach either was to open the
developer console on the machine at the pass and type

  posnic.kitchenCall.set({ ting: true, speak: true })

Nobody was going to do that, so nobody did, and a finished feature sat
switched off for its whole life. A setting nobody can find is not a
setting.

Two switches on Core Settings, beside the auto-focus ones and stored the
same way: per machine, saved immediately, because somebody flipping this
is standing next to the speaker waiting to hear the difference. Hidden
outside the desktop app, where there is no bridge behind it - a dead
control is worse than an absent one.

And a Test button, which is the point of the block. Until now the only
way to learn whether a kitchen would hear anything was to send it a real
order, which is why both faults found in this feature were found by
reading a log rather than by listening. It goes down the road a real
ticket takes: the same switches, the same tone, the same sentence, the
same window, and it says why when nothing happened.
Owner: "make female voice. more casual than machine voice. indian accent?"

A SpeechSynthesisVoice carries a name and a language and nothing else,
so the only way to ask for a particular voice is by name. The plain
language match picked Ravi, the male half of the Windows en-IN pair,
purely because Windows enumerates him first. Both voices satisfied
"en-IN", nothing was wrong, and the wrong voice came out anyway.

Now the accent is chosen first and the voice within it second, so an
Indian woman beats a British one: the dish names are the whole point,
and a name not on the list falls through to whatever that language does
offer rather than failing.

Delivery off the flat baseline as well, and kept close to normal on
purpose. Past about 1.15 a synthesised voice stops sounding relaxed and
starts sounding like a cartoon, which a kitchen laughs at twice and then
ignores.

Verified on this machine under Electron: it now picks Microsoft Heera,
English (India), where it picked Ravi before.
…-a-sound

The kitchen can actually make a sound
Owner: "beep sound is not good. i want like new order came. make it good.
not beep." He was right, and the reason was in the generator: a plain
sine contains nothing but its fundamental, which is what a beep is. Two
things make a struck object sound struck and that tone had neither. The
new one carries partials above the note and an envelope that decays from
the instant it is hit.

Owner: "First bell is we got new order. I want one bell for each line
item before read it." The opening and the plate count are about the
ticket; everything after is a dish and gets a smaller tap in front of it.
Where that boundary falls is decided beside the words and travels with
them, so the page never guesses by counting sentences - a guess that
breaks the first time the wording moves.

Owner: "how about user picks the bell sound as choice how you gave me.
also give choice voice also." Three arrival bells, three taps, and the
voices the machine has, each with its own Play button, because nobody
can choose a sound from its name. A kitchen with a fryer roaring needs a
different bell from a quiet dining room and that is not a decision to
make once, in one file, for every shop.

Owner: "different countries might need different voice and accent." The
voice is stored by name and an empty name means the best one here, so a
machine that lacks the chosen voice falls back rather than going silent.
A downloaded voice pack later adds another name to the same list.

Everything merges onto what is stored: turning the reading off must not
lose the bell somebody spent five minutes choosing.
Owner, looking at a live ticket on the handset: "i see active kot i see
some descirption details inside the line items. actually we need to show
only item name if any customization note delibertly captain entered.
otherwise dont show any other details. dont confuse captain."

Two server paths treated a line with no note as a line that ought to
borrow one. Adding a dish to an open order stamped the catalogue's
description onto the sale line, and the ticket view model fell back to a
`description` key that a sale line has never had in its schema, so it
could only ever pick up menu copy from something catalogue-shaped
passing through.

Worth being blunt about why that is a fault and not clutter. A note on a
kitchen ticket is an INSTRUCTION, and a cook reading one assumes somebody
at the table asked for it. Menu copy in that position is an instruction
nobody gave, in the one place where the sane response is to obey it, and
it buries the line somebody did ask for.

A blank note means nothing was asked for. Blank is the honest answer.
The description still belongs to the item and is one lookup away for
anything that genuinely wants it.

Open orders created before this keep whatever was already stored; new
lines are clean.
The waiter-call feature marked its collection synced the day it shipped.
Nothing built the lane: the sync agent has none, the sync-gateway allows
none, and no build since has carried one. A call from a table on the
ordering page lands in the cloud and a till with its own database never
sees it.

The packaging guard compared the promise with the bundle and refused the
build, which is what it is for. This records the truth: waitercalls is
undecided, with what it will take to sync it written next to it. The
classification test now names it among the undecided instead of the
promised.
The waiter-call row was a native insert with no updated_date. The sync
agent finds its work with { updated_date: { $exists: true } } and the
gateway sends a till only rows whose updated_date moved, so even with the
lane built (Gateway "A call reaches the till") the row would have stayed in
the cloud for ever. Same trap as the settings save (#838).

The row is stamped on the way in, and again when the till marks it seen,
so the cloud learns the call was answered and the ordering page stops
telling the customer "already calling".

waitercalls goes back to synced, per branch. It was moved to undecided
when the packaging guard found the promise had no lane behind it; the
guard keeps checking, against the agent actually bundled.
The bells, the pickers, and the note that was never the menu
@sridharkalaibala
sridharkalaibala merged commit e9e30e2 into main Sep 17, 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