Two compounding issues, both pre-existing (this exact <span>-joining
pattern and CSS was carried over unchanged from the old settings.html
preview I moved in the previous commit):
1. Preview lines are joined as adjacent <span> elements with no
separator, so without an explicit block display they render as
one continuous inline line instead of stacked rows.
2. .receipt-preview sits directly in body's flex-column layout and
has overflow-x:auto — per the CSS Flexbox spec, an overflow-bearing
flex item's automatic minimum size becomes 0 instead of its content
size, so the flex column was free to squeeze it down to a sliver
regardless of its 19 lines of content.
Fixes: span{display:block} for the per-line stacking, flex-shrink:0
so the flex column can't shrink it below its content height.
Also fixes a live bug found while refactoring: buildEscPos returned a
Uint8Array, but its caller in printThermalBluetooth() ran the result
through TextEncoder.encode() (which expects a string) before writing
to the Bluetooth characteristic — encoding a Uint8Array's default
toString() ("27,64,27,...") instead of the actual bytes. Regular
(non-account) sale slips printed over Bluetooth have been sending
garbled data. Both builders now return plain strings, matching
buildEscPosDelivery's already-correct pattern.
Also preserves a pre-existing divergence rather than silently
resolving it: Bluetooth's delivery note prints letterhead business
info (name/address/phone/VAT), the network XML version never has.
Kept as-is per transport (folded into Bluetooth's header_title block)
rather than unilaterally adding or removing content — worth an
explicit decision later on which behavior is correct.
Also fixes a logo-ordering bug this surfaced: slip-layout.js's sale
and cashup defaults put header_title before logo, which (once a real
logo renders instead of the always-empty placeholder used in earlier
regression tests) reversed the original image-then-title print order.
Reordered both to logo-first and added the missing logo block to
cashup's default catalog, since cashup's original code already
attempted (brokenly) to print one.
- checkoutSale's stock-availability loop called pb.getOne() with no
try/catch, so a stale/deleted stock_item_id in a persisted cart
threw all the way up to a bare 500 with no useful message. Now
caught and returned as a clear pb_error the till can display.
- Removed the insufficient-stock block entirely: the till is used
ahead of a physical stock take, so a sale must never be blocked
for lack of stock — current_qty is now allowed to go negative and
gets corrected at the next stock take. Drops the now-dead
insufficient_stock/shortages branches from the route, service
types, and client error handling.
Enhanced error messages to include specific field validation details from PocketBase, allowing callers to identify issues more easily. Additionally, implemented a safeguard to truncate the email body to a maximum of 5000 characters to prevent validation errors from surfacing as generic messages.
confirmSale() called toast.error(...) on checkout rejection (e.g.
insufficient stock), but no toast global was ever defined anywhere
in the app, so the ReferenceError swallowed the intended error
message and operators saw nothing. Adds a minimal self-contained
showToast() and bumps the SW cache so tills pick it up.
One row per letterhead, holding the reorderable/toggleable block
list for each thermal slip (sale, delivery note, cash-up) plus the
delivery note's default pricing toggle. Migration already run
against the live PocketBase instance.
Covers the reorderable block-list editor for sale/delivery/cash-up
slips, per-letterhead persistence, and the delivery-note pricing
default, worked out via brainstorming with the visual companion.
Added support for discounts in the POS checkout process, allowing operators to apply either a fixed amount or a percentage off the gross total. Updated the state management to include discount details and modified the receipt and cart rendering to display pre- and post-discount values. Enhanced the checkout API to handle discount inputs and ensure proper calculations. Updated relevant tests to cover new discount logic.
Enhanced the PDF generation for quotes by expanding the customer relation to include the associated company information. This allows the company name to be displayed prominently in the "TO:" block of the PDF, improving clarity for users. Updated test data to reflect the new structure and ensure proper rendering of the company name when available.
Introduced a new link in the dashboard that allows users to access the mobile view of the application. The link features a smartphone icon and is styled for better visibility, enhancing user navigation options.
Updated the service worker registration logic to allow for specific service workers based on the route. Added support for a mobile service worker at `/m`, while maintaining existing functionality for `/counter` and `/new-order` routes. This enhances the PWA experience by serving optimized service workers for different app sections.
The pricing modal saves quote_description correctly, but the PDF
template rendered item.description unconditionally, so the
operator's edit never showed up on the customer-facing quote.
Auth gates redirected to a bare /login with no memory of where the
user was headed, and the login page always sent them to /dashboard
afterward — so opening the /m PWA while signed out landed you on the
main dashboard instead of back on /m. Both gates now pass ?next=,
and login honors it (falling back to /dashboard if missing/unsafe).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a chevron button at the left edge of every delivery-note row on
/deliveries. Clicking reveals an inline items panel (description +
qty) so the operator can match against the printed DN paper in their
hand before loading.
Items come from a client-side filter of the order group's items array
(items.filter(i => i.delivery_note === dn.id)). No new server endpoint.
DNs whose items aren't in the order group's slice show a 'See full DN →'
link to /delivery-notes/[id].
Expand state is held at the OrderGroupView level as a Set<string> of
expanded DN ids — independent per DN, resets on refresh. (State lifted
to OrderGroupView rather than DeliveriesPage because order.items is
already in scope there; avoids prop-drilling through CustomerGroupView.)
Service change: deliveries-service.listDeliveryItems now fetches the
delivery_note (relation id) + unit fields on each item, so the page
can filter and render units without an extra round-trip.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The 'Total {sumQty}{unitSuffix}' header line now explicitly documents
how mixed / missing units are handled: suffix is omitted when all
items have empty/null units (operator defaults to 'EA'); when units
are mixed, no suffix is shown; when all items share a non-EA unit
(e.g. BOX), the suffix is shown.
Self-review pass; no other changes.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Click-to-expand on each delivery-note row reveals an inline items
panel (description + qty) below the row. Operator can match the items
against the printed DN paper in their hand before loading. Pure
client-side render — items already loaded by /api/v1/deliveries are
filtered by delivery_note == dn.id. Empty state with fallback link to
/delivery-notes/[id] for DNs whose items aren't in the page slice.
Single-file edit on src/app/(app)/deliveries/page.tsx. No server,
schema, API, or MCP changes.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The plan + summary landed locally during the implementation but were
not committed alongside the code. Both documents describe the
rep-scoped Orders / Quotes / Invoicing / Delivery Notes list filter
work that already shipped (commits 8faa443, ed07617, 29eb003, 9cf8e39
etc).
- 2026-08-31-rep-scoped-list-filter.md — the implementation plan
- 2026-09-01-rep-scoped-list-filter-SUMMARY.md — post-implementation
summary with the commit log + verification notes + the known
limitation (Orders/Quotes tab counts are unfiltered for reps)
Co-Authored-By: Claude Code <noreply@anthropic.com>
The /login page previously had three tabs (Login / Register / Reset
Password) wired through useAuth(). The Register and Reset handlers
were removed in this pass — only the Login form remains, plus a
small UI/styling cleanup that was collateral.
- Drop Tabs imports + the active-tab state plumbing
- Drop regName/regEmail/regPassword/regConfirm + resetEmail state
- Drop handleRegister + handleReset handlers (login-only now)
- Drop useAuth().register / useAuth().requestPasswordReset destructuring
- Trim the local stylesheet comments that referenced the Base UI
tabs triggers (no longer applicable)
Co-Authored-By: Claude Code <noreply@anthropic.com>
The /deliveries To Load + Back from Driver + Checked In tabs each
render delivery-note rows showing only 'DN {number}' + a one-line
status subline. Operators couldn't identify the customer or tell
whether the signed copy was back without scrolling up to the
customer/order group header.
Replace the existing subline with a compact pieces list:
customer name (truncated, with title tooltip) · company name ·
parent order number · signed-copy indicator (paperclip icon +
filenames in title=) · delivery date (if set). Counter-pickup DNs
(no parent order) collapse to 'Counter pickup' in italic.
All data is already on the row payload from /api/v1/deliveries —
no server or schema changes.
Also removed the now-unused 'subline' const that the prior
implementation used for the old single-line status subline.
Verified visually via dev server: counter-pickup fallback renders
correctly ('Counter pickup' italic). Order-attached and signed-copy
code paths are structurally correct per the spec — would need a
DN with order + signed files in the live data to exercise them,
but no such DN was visible in the current dev data.
Co-Authored-By: Claude Code <noreply@anthropic.com>