Repository navigation
Releases: CaspianTools/caspian-store
Release list
v12.0.0 — Register correctness pass, and the till is a PWA only
v12.0.0 — Register correctness pass, and the till is a PWA only
A full review of src/pos/ turned up sixteen defects, from a receipt printed with no
number on it to a real sale that could sit invisible in the outbox forever. All of them
are fixed here. Separately, the Tauri desktop shell is gone: the register ships as a PWA
and only as a PWA.
Consumer action required on upgrade
commitPosSale now returns the priced lines it already computes, and the register builds
the customer's receipt from those. Redeploy the caspian-pos codebase or receipts keep
falling back to the till's scanned prices:
npm install github:CaspianTools/script-caspian-store#v12.0.0
cd firebase/functions-pos && npm install && npm run build && cd ../..
firebase deploy --only functions:caspian-posNothing else changes for a consumer site. If your code imported readStorageMode,
writeStorageMode, readPrinterTransport or writePrinterTransport, delete those calls —
see Removed.
Fixed
- An offline sale with no leased numbers left printed a blank receipt number.
PosSaleQueue.capturereturns a device-local reference for exactly this case and
PosQueuedCloudAdapterwas discarding it, so the customer got a slip with no identifier
at all. The reference is now used and labelled as provisional, on screen and on the
printed receipt. - A sale interrupted mid-send was never retried and never cleared.
send()marks a
salesendingbefore the call; a reload or a closed tab left it there permanently —
drain()only picks upheld,pruneSent()only removessent, and the held-sales
page offers Retry onblockedalone. The queue now sweepssendingback toheldonce
per session, before the online check so the count is right even offline. - Receipt lines could disagree with the receipt total. Lines and subtotal came from the
open ticket while the total came back from the commit, so a catalogue edit mid-sale
printed a slip whose own lines did not add up to its own total.commitSalenow returns
what was charged and the receipt is built from that. - An online sale burnt a leased receipt number the server never honoured.
PosCloudAdapterwas not forwarding thereceiptclaim (orcapturedByUid/
capturedByName) to the callable, so the ordinal was spent locally while the server
allocated a different number from the counter. Leases drained at one number per sale and
numbering developed permanent gaps. - Typing into the register's quick-add dialogs fired phantom barcode scans. The
keyboard wedge was silenced only for the tender phase, which could not see the product
and person dialogs the POS header opens outsidePosRegister. It now detects any open
dialog from the DOM, so consumer-added dialogs are covered too. 1,234.50was read as1.234at the tender screen.String.replacewith a string
argument replaces only the first match, so a grouping separator produced two decimal
points andparseFloatstopped at the second. On the tendered field that is wrong change
handed to a customer.- The register, the connection pill and the held-sales page each built their own
outbox. They shared IndexedDB so no sale was lost, butcapture()andmarkSent()
notified an instance nobody was subscribed to, so the held-sales badge only moved on a
30-second timer andpaused— in-memory state — could never be observed by the pill.
There is now one adapter, provided byPosShell. - Backups silently dropped custom roles.
LOCAL_BACKUP_VERSIONis 2 and carries
roles; v1 files still restore.parseLocalBackupalso validatessalesis an array,
which it never did whilerestoreLocalBackupiterated it. - "0 receipt numbers left" could never be cleared. The low-numbers warning fired on
leasedRemaining === 0, which is also a healthy till before its first lease and a store
with nofunctions-posdeployment. Lease failure is now tracked separately and reported
as "Cannot reserve receipt numbers". - The
Math.randomfallback inrandomId()had no length floor, and the server requires a
sale id of at least eight characters.
Added
- Line discounts have a control at last.
usePosTicket.setLineDiscountwas plumbed
end to end — cloud payload,priceLocalSale,commitPosSalevalidation, receipt
renderer — with nothing calling it, so the ticket's discount row and the receipt's
per-line discount were unreachable. Each ticket line now has a markdown button. PosAdapterProvider/usePosAdapter, the shared register storage seam.PosSoldLine— a line as it was charged, distinct from the scannedPosSaleLine.summariseSoldLinesandparseAmountare exported, both pure and both checked by
scripts/check-standalone.mjs.
Removed
desktop/and its build workflow. The Tauri shell that bundled the register for
Windows is gone; the till is installed from Chrome or Edge with the register's own
Install button. Existingdesktop/v*releases stay on GitHub but are no longer built or
documented, and the POS manual's Windows-installer section has been deleted. Standalone
mode itself (<CaspianStoreProvider standalone>,PosLocalAdapter, the local back
office) is untouched and still exported — it simply has no first-party packaged delivery.readStorageMode,writeStorageMode,readPrinterTransport,writePrinterTransport
andidbCountByIndex. Nothing called any of them: the printer control is hard-coded to
the browser transport until the ESC/POS paths ship, and the storage mode is derived by
resolvePosStorageModerather than stored.promoCodefrom the standalone commit path.priceLocalSalenever applied a promo, so
the field recorded a claim nothing honoured.
Changed
PosQueuedCloudAdaptertakes its queue as a constructor argument instead of building one.PosCommittedSalegains optionallinesandprovisionalReceipt.
v11.0.2 — The Windows register becomes a till with no website
v11.0.2 — The Windows register becomes a till with no website
The Windows register app was a thin window onto a shop's hosted /pos page, and it asked for that
address on first run. An address typed once on setup day and never looked at again is an address that is
sometimes wrong: a till pointed at a company's marketing site showed that site's 404 and nothing else.
v0.2.0 added a check and a way back to the setup screen, but could not help a till that had already
stored a bad address, because it deliberately never re-probed on launch — so the failure survived the
upgrade.
The address is now gone. desktop/ bundles the register and runs it in the library's standalone
mode: the catalogue, the staff, the sales and the receipt numbers live in a local database inside the
app, and the till contacts nothing. There is no address to type, no account, no website behind it, and
no 404 that can occur. It is released separately as desktop/v1.0.0.
Standalone mode is what made this possible, and it removed both objections that had kept the register
from being bundled: signInWithPopup is irrelevant with no Firebase Auth in the tree, and
version-locking the UI to a signed binary is what an offline product is.
No consumer action required
Documentation only in the npm package — the register manual and the version stamp. No library source
changed: the offline app is assembled entirely from the existing public API (standalone,
CaspianRoot, features.posOnly and the framework-adapter contract). The Windows shell is not part of
the published tarball and releases on its own desktop/v* tag, so upgrading the library does not change
a till, and updating a till does not require upgrading the library.
Shops running a hosted Caspian store are unaffected: the cloud register at /pos and its browser
install are unchanged. What is gone is the desktop app's cloud mode — that audience installs the
register from Chrome or Edge as before.
Changed
desktop/— the register is bundled and offline. The app mounts
<CaspianStoreProvider standalone scriptSettings={{ features: { posOnly: true } }}>over a Vite React
bundle served from inside the binary. The setup screen, the address probe, thestore.jsonfile and
theTill → Change shop addressmenu item are all removed; theTillmenu keeps Reload and Quit.
ureqandurlleave the Rust crate with the network code that used them.desktop/— routing is an in-memory adapter. Tauri serves the bundle from a custom protocol with
no SPA fallback, sowindow.locationnavigation to/pos/settingswould ask for a file that does not
exist.desktop/src/memory-navigation.tsxsuppliespathname, a reactivesearchParams,push,
replaceandbackthrough the documented adapter contract — which is why no library change was
needed. External links are inert on purpose: an offline till has no address bar and no back button, so
following one is a one-way trip out of the register.- A
store.jsonleft by a pre-1.0 install is deleted on first launch, so an upgrade needs no
hand-edit.
Fixed
- A till upgraded from
desktop/v0.1.0no longer inherits an unchecked address. v0.1.0 stored
whatever was typed with no validation, and v0.2.0 read it back without re-probing, so a till that had
already been pointed at the wrong site kept opening that site's 404 after the upgrade. There is no
address to inherit any more.
Documentation
- The register manual (
docs/pos-manual.html) rewrites "The Windows register app" for what it now
is, including the thing that most needs saying out loud: the shop's catalogue, staff and entire
trading history exist on that one computer and nowhere else, and Back office → Backup is the only
copy there will ever be. Theaz,ruandtroverlays for that section are deleted rather than left
stale, so it renders in English with the "not translated yet" notice until it is re-translated. desktop/README.mdcorrects what it said about SmartScreen. The warning is not a signature check
alone — it weighs the signature, the reputation of both the certificate and the exact file hash, and
the Mark of the Web, which is why unsigned software installed from a USB stick raises nothing at all.
The cost table gains the cheap options it was missing (Certum open-source signing at roughly €30/year,
Azure Trusted Signing, Microsoft's file-submission review). No signing path has been chosen — the
installer is still unsigned and SmartScreen still warns.
Caspian Register desktop/v1.0.1
Windows installer for the Caspian Register. The till runs entirely on the computer it is installed on - no internet connection, no account and no website are needed. Unsigned, so Windows SmartScreen warns on first run: choose More info, then Run anyway. See desktop/README.md.
v11.0.1 — The register app refuses an address with no register on it
A shop that typed its company website into the Windows register app got a bare 404 and no way out. The
address was saved before anything checked it, nothing on screen named the address the window was asking
for, and the command to change it had no button, menu item or shortcut anywhere in the shipped app — the
only recovery was deleting a file from %APPDATA%.
The shell now checks the address before it saves it, and always carries a way back to the setup screen.
It is released separately as
desktop/v0.2.0.
No consumer action required
Documentation only in the npm package — the register manual and the version stamp. The Windows shell is
not part of the published tarball and releases on its own desktop/v* tag, so upgrading the library
does not change a till, and updating a till does not require upgrading the library.
Changed
desktop/— the address is checked before it is saved. The setup screen requests/poson
whatever was typed and refuses a definitive 404, naming the mistake it is almost always catching: a
company's marketing site rather than the address the shop itself runs on. Only a definitive 404 or
410 refuses. An
unreachable host, a timeout, a TLS failure or a 500 are all accepted, because a till is routinely set
up before the shop's site is live and on connections having a bad morning — refusing then would strand
a shop that had typed exactly the right address. The check is a catch for the obvious mistake, not a
promise that the address is right, and the manual says so.- The register window has a
Tillmenu — Change shop address (Ctrl+Shift+A) and Reload. It is
native, so it still works when the page below it is a 404 or a blank "can't reach this page", which is
exactly when somebody needs it.reset_store_urlexisted in v0.1.0, but no shipped UI could call it
and the remote origin has no IPC access, so it was unreachable code documented as a feature. - The window title names the address —
Caspian Register — shop.example.com. A page that fails to
load can now be traced back to what was typed. - Repointing a till is an edit, not a retype. The setup box opens prefilled with the current address,
and the saved value is overwritten only once a new one passes the check, so closing that window leaves
a working till working.reset_store_urlis renamedchange_store_urland no longer deletes the
config file.
Fixed
http://localhost.evil.comcounted as local and bypassed the https requirement, because the test
was astarts_with("http://localhost")prefix match on the whole string rather than on the host. The
host is now parsed out and compared. A barelocalhost:3000is also accepted now and defaults to
http, rather than being rejected as "not a web address".- The desktop release build could ship the previous release's installer.
desktop-build.ymlcaches
src-tauri/target, which carries the last build'sbundle/with it, so the collect step globbed a
directory holding two-setup.exefiles and took whichever sorted first. The 0.2.0 build produced a
correctCaspian Register_0.2.0_x64-setup.exeand was rejected because the cached 0.1.0 one won the
glob. The bundle directory is now cleared before building, and finding more than one installer is a
hard failure rather than a coin toss. - The register manual told owners to change a till's address by deleting
store.jsonby hand. It now
documents the menu, in all four languages, and states plainly that the new check cannot catch a wrong
address on a site it cannot reach.
v11.0.0 — A till that needs no shop behind it
Many physical shops have no online presence, and until now the register assumed one: a Firebase project, a website, a cloud admin panel. This release adds standalone mode — mount the provider with standalone and the register runs entirely on one computer. Catalogue, staff, sales and receipt numbers live in that machine's IndexedDB, and the till contacts nothing.
It comes with its own back office at /pos/admin, because a till you cannot put products into is not a till.
Consumer action required on upgrade
For almost everyone this is a reinstall and nothing else:
npm install github:CaspianTools/script-caspian-store#v11.0.0Rules, indexes and Cloud Functions are unchanged. The major bump is for one narrow type change: CaspianStoreContextValue.firebase and .collections are now | null, because in standalone mode there is no Firebase project. If you read them off the low-level escape hatch:
// before
const { firebase } = useCaspianStore();
firebase.db;
// after — the documented accessor, which still returns non-null
const { db } = useCaspianFirebase();useCaspianFirebase() and useCaspianCollections() are unchanged and still return non-null, so the ninety-odd storefront and admin call sites need no edits.
Added
- Standalone mode.
<CaspianStoreProvider standalone>boots the whole tree with no Firebase project. Deliberately explicit rather than inferred from a missing config: falling back automatically would mean a real shop whose credentials broke came up as an empty local register and started taking sales into a database nobody knows about — a failure that looks exactly like a working till. PosLocalAdapter, implementing thePosStorageAdapterseam that has been in the codebase since v10.0.0 waiting for it. No queue, no lease, no commit fuse and no online check: those exist to paper over the gap between a sale happening and a server hearing about it, and here there is no gap.- Local accounts and three tiers —
superadmin(Support) →admin(Owner) →staff(Cashier), cumulative rather than exclusive so an owner can work the counter without signing out. Passwords are PBKDF2-SHA-256 with a per-user salt and per-user iteration count. Technical Support commissions a machine by creating the first account; the shop's own staff are added afterwards, from the back office. - A local back office at
/pos/admin— items (add, edit, delete, search, CSV template / export / import), sales and takings with a CSV export, people and roles, shop details and receipt wording, and backup / restore. - Backups. A dated JSON file holding items, people, sales, the receipt counter and the shop record. Restore is additive and never overwrites a sale already on the till, and the receipt counter only ever moves forward — winding it back would reissue numbers already printed.
- CSV headers match the cloud products export where the two models overlap, and unknown columns are ignored rather than rejected — so a spreadsheet exported from an online store imports straight into a standalone till.
scripts/check-standalone.mjsand a CI workflow: 32 assertions over the money arithmetic and the CSV round-trip. Plainnode:assert, no test runner and no new dependency.
Changed
- Where sales are stored is now shown at
/pos/settings, not chosen. The radio was previously disabled and labelled "Available in a coming release". It now reflects the mode the till is actually in and stays read-only. Offering it as a per-device switch was a trap: a cloud shop that picked "this computer only" would have got a register backed by an empty local catalogue and no way to fill it. reportServiceErroraccepts a nullFirestore;PosSaleQueue,CartProvider,WishlistProviderandScriptSettingsProvideraccept a null database.ScriptSettingsProvidergains aseed— it was the single provider making an unconditional Firestore read at mount, and a standalone till would otherwise have waited forever on a subscription that never fires.- The register's queue tab and connection pill are hidden in standalone mode, where there is no outbox and nothing to be offline from.
Fixed
docs/pos-manual.htmlno longer describes local storage as unavailable in all four languages.- README's POS manual section count said 38; it is 41.
Notes
- The Windows installer still asks for a shop address. The desktop shell is a window over a shop's own
/pospage and has not moved to standalone yet — packaging a standalone till as an.exethat needs no website at all is the next desktop release, on its owndesktop/v*tag. - Nothing syncs to a website. Attaching a shop link later and pushing local history up to it is a separate release.
docs/pos-manual.htmlgains no standalone chapter in this release: a shop cannot reach the mode until the desktop shell delivers it, and the manual documents what an owner or cashier can actually do.create-caspian-storeis unchanged — no scaffolder CLI surface change, so no republish.
v10.5.0 — The register keeps selling when the internet drops
v10.5.0 — The register keeps selling when the internet drops
A till no longer stops when the connection does. The cashier scans, takes payment and prints a receipt
exactly as always; the sale is written to the computer instead of the shop and sent by itself when the
connection returns. The customer gets an ordinary receipt with a real receipt number on it, because
the till reserves a block of numbers in advance while it is still online — not a slip saying it is not
a receipt.
Consumer action required on upgrade
Redeploy the register's Cloud Functions if you have not already (v10.4.0 added the lease callable):
npm install github:CaspianTools/script-caspian-store#v10.5.0
firebase deploy --only functions:caspian-posRules and indexes are unchanged since v10.4.0.
Added
- A hold-and-forward queue in IndexedDB. Sales, leased receipt blocks, a projected catalogue, and
the open ticket. IndexedDB rather than localStorage because this holds money: localStorage has no
transactions, so "write the sale" and "spend the receipt number" could not be made atomic, and a
crash between them would either lose a sale or reuse a number. PosQueuedCloudAdapter, which wraps the cloud adapter rather than replacing it, so the register
never learns whether the network is up. It writes the sale to disk and awaits that commit before
attempting the network — a request that timed out and a request that was refused are
indistinguishable from the client, so the queue decides before acting rather than guessing after.- A local catalogue cache, synced through the
getPosCatalogDeltaendpoint that has existed with
no callers since v10.0.0. Same lookup precedence offline as online: barcode, then SKU, then id. /pos/queue— Held sales. A sale that cannot be sent is money in the drawer with no record a
cashier can see, so it gets a page, with a reason and a Try again button.- A connection marker in the register's top bar, silent when everything is normal. It warns that
receipt numbers are running low while the till is still online and can top up — discovering that
mid-outage in front of a customer is the wrong time. - An error taxonomy the register has never had.
pos-register.tsxonly ever readerror.message,
which for any infrastructure failure is the literal stringINTERNAL. The queue reads the callable
code instead: transient failures back off, an expired token refreshes without consuming an attempt,
a revoked role pauses the whole queue and says so, and a deleted product stops for a person.
internalis retried but bounded, because firebase-functions collapses a transient Firestore
abort and a permanent crash into the same opaque code. - 26 new strings in all four languages, and a register-manual section, Selling when the internet
drops.
Changed
- Five statements in the register manual were made false by this release and are rewritten in all
four languages — every place that said the register does not work offline, has no queue, or that
a lost connection means a lost sale. PosStorageAdaptergains an optionalqueue;PosSaleDraftgainsreceipt,capturedTotal,
capturedSubtotal,capturedByUidandcapturedByName. Both additive — a consumer's own adapter
still compiles.PosCommittedSale.pendingis finally set. It has been declared since v10.0.0 and read by
nothing.
Notes
Three limits, in the manual rather than left to be discovered:
- Clearing the browser's site data while sales are waiting destroys them. They exist nowhere else
yet. The Windows app keeps its storage separately, which is a reason to prefer it on a busy counter. - Waiting sales are only sent while a register tab is open. They go as soon as somebody opens the
register again; they will not go overnight on their own. - An item the till has never downloaded cannot be scanned offline. The catalogue refreshes
whenever the till is online, so this only affects something added very recently.
The commit fuse is 3 seconds rather than the SDK's default 70. Seventy seconds of a frozen till with a
queue forming is not a thing anyone would choose, and leaving the call running costs nothing: the
leased receipt number is identical on both paths, so a late landing reconciles through the idempotency
gate with no visible consequence.
v10.4.0 — Receipt-number leases, and the till stops emailing you
v10.4.0 — Receipt-number leases, and the till stops emailing you
Groundwork for offline selling, plus a bug that has been filling shop owners' inboxes.
The hard problem with an offline till is the receipt: the server allocates receipt numbers inside the
commit transaction, so a sale captured offline has no number to print at the moment the customer is
standing there. A till can now reserve a block of real numbers in advance and spend from it while
offline, so the customer gets an ordinary receipt with a final, store-unique number on it — not a
slip that says NOT A RECEIPT.
Nothing user-visible changes yet. The register does not use leases until the offline queue ships; this
release lands the server half first, so an old client keeps working against the new server.
Consumer action required on upgrade
Redeploy two Cloud Functions codebases. Rules do not need redeploying — the new collections are
server-only, and unlisted paths already deny by default, so the added blocks change no behaviour.
npm install github:CaspianTools/script-caspian-store#v10.4.0
firebase deploy --only functions:caspian-pos,functions:caspian-emailFixed
- Every counter sale emailed the shop owner a "new order".
runEmailOnOrderCreatefires on any
orders/{id}create, and a POS sale is written withstatus: 'paid', which maps to the
new_order_admintemplate. The customer half was already gated on there being an address to write
to; the admin half was not. A shop doing two hundred sales on a Saturday got two hundred emails.
POS orders now return early — the sale is already on the Point of sale page and in the Orders list. - The commit transaction could exceed Firestore's 500-write ceiling. It writes one update per
distinct product, plus the order, plus (now) the receipt-number claim, while the line cap was 500.
Over the ceiling it fails as an opaqueinternal, which a retrying client reads as transient and
replays forever. The cap is now 400. - A backlog drained by a manager was recorded against the manager.
cashierIdcame from the
caller, so a held sale replayed the next morning lost the cashier who actually rang it. The client
can now sendcapturedByUid, and the caller is kept separately asreplayedByUid.
Added
leasePosReceiptBlock— reserves a block of receipt numbers for one register. Bumps
posCounters/receiptonce in a two-write transaction; the till then spends ordinals from the block.commitPosSaleacceptsreceipt: { leaseId, ordinal }. The client never sends a receipt
string — the server derives it from the lease document it wrote itself, so a tampered client can
only pick a number inside a block genuinely issued to its own device. Omit it and allocation works
exactly as before, so old clients are unaffected.posReceiptNumbers— a claim registry that makes a duplicate number impossible. This is not
belt-and-braces. Imaging a Windows till, which is routine in multi-till rollouts, clones the device
id and any stored lease, so two registers can hold the same block and spend the same ordinal. The
claim catches it and the second sale falls back to a fresh number instead of handing two customers
the same receipt.posReceiptLeasesandposReceiptNumberscollections, refs incollections.ts, and explicit
server-only rules blocks matching theposCounters/posLicensespattern.
Notes
The counter is only read when a sale has no lease. Reading it on every sale is what would serialise a
whole shop on one document, and removing that contention is half the reason leases exist. Gaps in the
numbering are expected — a till that leases 200 numbers and sells 3 has burnt 197, and receipt numbers
were never contiguous across tills anyway. What matters is that no number is ever issued twice.
v10.3.2 — Point the scaffolder at the org that actually exists
v10.3.2 — Point the scaffolder at the org that actually exists
Housekeeping ahead of the offline work. The scaffolder installed the library from
github:Caspian-Explorer/... while the repository lives at CaspianTools/.... GitHub's rename
redirect has been quietly covering for it, so nothing was broken — but a redirect is not a guarantee,
and it stops the day somebody else claims the old organisation name.
No consumer action required
Existing installs are unaffected; both spellings still resolve today. New scaffolds now install from
the canonical URL instead of relying on a redirect.
Fixed
- 24 GitHub URLs repointed from
Caspian-Explorer/toCaspianTools/acrosspackage.json,
README.md,INSTALL.md,scaffold/create.mjs,scaffold/README.mdandcreate-caspian-store/.
The load-bearing one isscaffold/create.mjs, which is the spec every scaffolded site installs from.
Caspian-Exploreris left untouched wherever it is the author or copyright holder — that is a
name, not a typo. - Install examples were three majors stale.
README.mdandINSTALL.mdtold people to install
#v8.0.0, and INSTALL.md asserted "v8.0.0 is the current release", while the package was on v10.3.1. stripLocalePrefixhad a third private copy inlayout-shell.tsx; it now uses the shared helper.
Added
scripts/check-manuals.mjsgains check C7 — the install examples that claim to be current
must pin the current version. This is the same rot as the manual's footer version stamp, which sat
atv10.0.0through two releases because nothing checked it. Historical migration notes are left
alone: naming v9.0.0 in a v9 upgrade section is correct.
v10.3.1 — A Windows installer for the register
v10.3.1 — A Windows installer for the register
The register can now be handed to a shop as a file. desktop/ is a small Tauri shell — a native
window pointing at the shop's own /pos page, using the WebView2 runtime already on Windows rather
than shipping a browser, so the installer is about 1.5 MB. It releases on its own desktop/v* tag and
is not part of the npm package.
No consumer action required
Documentation and a new sibling project. Nothing in dist/, firebase/ or scaffold/ changed, and
the library API is untouched. The installer is an optional extra for shops that would rather run a
setup file than install a web app from a browser prompt.
Added
desktop/— the Windows register shell, released as
desktop/v0.1.0.
One binary for every shop: the store address is asked once on first run rather than compiled in,
because SmartScreen reputation accrues per signed binary and per-customer builds would leave every
customer staring at "Windows protected your PC" forever. Onlyhttps://addresses are accepted —
Firebase Auth will not sign a cashier in over plain http, so an http till would look correct and
then refuse every login..github/workflows/desktop-build.yml— builds the installer onwindows-latest, derives the
icon set from a committed source PNG, and attaches the.exeto adesktop/v*release. CI is the
build path because the shell is Rust and most people maintaining this repo have no Rust toolchain.- The register manual gains The Windows register app in all four languages.
Notes
Two limits, stated plainly in the manual rather than left to be discovered:
- The installer is unsigned, so Windows warns on first run until a code-signing certificate is
bought. That is procurement, not a code change, so it is not wired up with placeholder secrets that
would fail every build. - v0.1.0 does not print directly to a thermal printer. Receipts go through the same print dialogue
as the browser. Direct ESC/POS printing over the Windows spooler is the main reason to want the
shell and needs a physical printer to validate, so it is next rather than now.
The shell is not a bundled copy of the register — it points at the live origin, so a shop that
updates its site gets the new register on the till's next launch with nothing to reinstall.
v10.3.0 — The register installs as its own app
v10.3.0 — The register installs as its own app
A till can now install the register: its own icon, its own window, no address bar. It is still the same
web page — nothing to download, nothing to keep up to date by hand — but it starts like a program and a
cashier can no longer type a URL into it by accident. The storefront and the register install as two
separate apps on one origin, so a counter machine gets an icon that opens straight into the register.
Scaffolded sites get the whole layer generated. Existing sites can adopt it by hand — see below.
Consumer action required on upgrade
Nothing breaks if you do nothing; the register keeps working exactly as before. To make it installable
on an existing site you need the generated files, which is easiest to get by scaffolding a throwaway
project and copying them across:
npm install github:CaspianTools/script-caspian-store#v10.3.0
npx create-caspian-store@latest /tmp/pwa-ref -- --with-pos
# copy from /tmp/pwa-ref into your project:
# src/app/_pwa-brand.ts src/app/manifest.webmanifest/ src/app/pos.webmanifest/
# src/app/icon/ src/app/pos/ public/sw.js public/sw-pos.js
# public/offline.html public/pos-offline.html
# then merge the redirects() and headers() blocks from its next.config.mjsIf you already wired the storefront PWA by following INSTALL.md §9.5, your build has been failing —
see the third entry under Fixed. Change that one import to @caspian-explorer/script-caspian-store/pwa.
Added
- A fourth entry point,
./pwa—buildWebManifestand the newbuildPosWebManifest, importable
from a server route handler. This is the fix for the broken manifest route described below. buildPosWebManifest()— the register's manifest.scope/start_url/pos,short_name
"Register" (deliberately not the brand: the OS shows it under the icon, and that word is what tells a
cashier which of the two apps to tap),idfor a stable install identity, and
launch_handler: focus-existingso tapping the icon during a sale focuses the open window instead of
re-navigating and destroying a half-rung ticket.<PosInstallButton>in the register's top bar, and<PosServiceWorker>, which registers a
/pos-scoped worker, asks for persistent storage, and offers updates on a strip that never applies
itself mid-sale.- The scaffolder generates the PWA layer: both manifest routes, a generated icon route, the
/pos
route segment with its own layout, two service workers, two offline pages, plusredirects()and
headers()innext.config.mjs. Verified by building a scaffolded site end to end. <ServiceWorkerRegister>and<PosServiceWorker>gainenableInDev— offline behaviour was
untestable undernpm run dev, which is untenable for a feature about the network being down.stripLocalePrefixis exported, and the register manual gains Installing the register as an app on
a till in all four languages.
Fixed
/az/pos/settingsrendered the register instead of the settings page.PosRootmatched
^/pos/against a pathname that still carried its locale prefix, so nothing matched and it fell to
the default branch. A cashier on any non-English till could not reach their own settings at all —
language, register name, scanner speed, printer. The nav highlight was wrong for the same reason.- The storefront's install banner floated over the register's cash keypad. It is
position: fixed; zIndex: 800; the tender dialog iszIndex: 60.<InstallAppPrompt>now takes
hideOnPathPrefixes, defaulting to['/pos','/admin']. - INSTALL.md §9.5's manifest route has never worked. It told consumers to import
buildWebManifestfrom the main entry, but the build stamps that entry'use client', so the
Next.js build fails with "Attempted to call buildWebManifest() from the server." Broken from
v9.10.0 until now. The example now imports from./pwa, and it also stopped pointing at
examples/, a directory that is not in the package'sfileslist and so is absent from every
installed copy. - Brand colours were silently dropped from the manifest.
NEXT_PUBLIC_BRAND_THEME_COLOR=#1d4ed8
reads as empty, because dotenv treats an unquoted#as the start of a comment. The generated
.env.examplenow quotes them, and the generated code accepts a bare1d4ed8too rather than
making every consumer find that out the hard way. - Two service workers on one origin shared a
CacheStorage, and the reference worker deleted every
cache key that was not its own on activate — the storefront worker would have wiped the register's
shell out from under a live till. Both now clean only their own prefix. stripLocalePrefixwas defined privately in two components; it is now one shared helper.
Notes
Installing does not make the register work offline — it still needs the network to look up a
product and to complete a sale. The manual says so in the first note of the new section rather than
letting a shop discover it during an outage. The offline sale queue is a separate piece of work.
create-caspian-store sibling unaffected — no change to create-caspian-store/ and no scaffolder CLI
surface change.