Restyle booking fields as high-contrast cards, add visible required/optional indicators and ARIA requirements, and make lookup and form errors more prominent.
Two things on the collection pages.
Descriptions were clamped to two lines (`line-clamp-2`) on the
Collection Confirm items table and the Collection Pool card, so a long
description lost everything after the second line - exactly the part
that distinguishes one line from another (size, spec, colour). Both now
wrap to full height, with `break-words` so a long unbroken part number
cannot widen the table.
And a POS item's price was never a decision. `stock_items.selling_price`
is what the till charges, and it could move on its own: a bulk import
wrote a spreadsheet's selling price straight over the catalogue, and
the /stock pricing grid recomputed the selling price whenever its cost
cell was edited. Nothing asked.
Now the operator decides, per item, when they confirm a PO card. A
price confirmation dialog opens BEFORE the invoice/stock confirm - once
per card, and only when confirming would move a till price. Every row
shows the old price and the new price; the default is Keep price, so
moving a till price is always deliberate. Where the line cost and the
catalogue cost differ, both are shown and the operator picks which one
prices the till.
The price is derived, never accepted from the client: the route reads
the item's own markup and computes it, so the number the operator saw is
the number that lands. Update writes cost AND price together (a price
with a stale cost behind it would mis-price the next markup run), plus
a zero-quantity cost_history entry and a stock_ledger adjustment row. The
markup field is never written - the operator's rate is theirs. A failed
price write does not block the confirm: goods stuck unconfirmed is a
bigger problem than a till price that did not move.
Also fixes a live bug found on the way: `listCollectedItems` expanded
`purchase.stock_item`, but `stock_purchases` has no `stock_item`
relation - it lives on `stock_purchase_items` - so `expand.stock_item`
came back undefined on every row and the POS pricing was never
available. Expanding `stock_item` directly takes the stock leg from
0/27 rows carrying POS pricing to 27/27 (verified on live PB).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reorganize the sidebar navigation by moving Invoicing and Delivery Notes from the Logistics group to Operations, while keeping Delivery Pool under Logistics.
Board: chips scroll in one row (44px, was 136px over three rows), the
board opens on a new "Open" chip (not Done, not Cancelled; derived from
the same counts so Open + Done + Cancelled = All), cards say how long
the machine has waited, the badge drops when one status is chosen, and
the loading line shows a real ellipsis instead of "…". The one-tab
bar is hidden until a second tab exists.
Repair page: a way back, labels above values, the customer quote and
order as plain text (the office pages show prices), the quote listed
once, and Cancelled no longer drawn as the step after Done.
Book-in: a way back, photos first, placeholders marked "e.g.".
listRepairs: brackets each filter clause. The search is an OR chain, so
a status filter used to bind to serial_no only and a search returned
repairs of any status. Adds status "open".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Thirty-three findings were triaged and deferred during the build, plus
the ones the whole-branch review raised. The task ledger was scratch and
gets deleted, so this is the record - and the list is worth having: the
bridge guard that rejects pasted lines containing '@collection.', the
skill file's family tables covering 68 of 148 tools, and the reorder
points that gate the deferred dashboard view.
Giellie can now update stock quantities from a pasted list. The operator
pastes SKUs and quantities into the chat; Giellie resolves each line
against stock_items, asks whether the numbers are the new quantity on
hand or an addition, shows the plan, and writes the change against the
signed-in operator's name in the stock ledger.
The write path is ledger-first and audited, a failed write is its own
status and is compensated so the ledger cannot drift from the quantity,
and a dry run writes nothing at all. Nothing blocks on a low quantity -
the till oversells on purpose and the stocktake is what corrects it.
The till now shows the quantity on each catalogue tile, drawn under the
price as it is, negatives included.
The system prompt justified avoiding the stock_items.list +
stock_items.adjust loop by claiming that path "writes a ledger row with no
actor". That stopped being true when the branch routed adjust through the
audited write service: adjust now requires actor.id and its ledger rows
carry created_by. The instruction itself is unchanged and not weakened —
the signed delta and the missing batch call are still the reasons to
avoid the loop, and the new tool's own advantages are now named.
stock_items.create now also requires actor.id, so an external MCP client
that omits it gets a 500 JSON-RPC error. The generated catalogue row is
truncated at 240 chars before that sentence, so both authoritative docs
get a hand-written row and an input-shape block instead. The generator is
not re-run; the catalogue is still 148 tools.
The spec's till bullet described the first design, not the code that
shipped: the quantity is drawn under the price on the right (the
operator's own decision during the build), and the service-worker cache
was bumped to `pos-v39`.
- The tile bullet now points at `pos-app.js:290` and says the `.sub`
line keeps `sku · brand · unit · M in cart` while the quantity sits
under the price.
- The cache bump reads `pos-v37` -> `pos-v39`.
`.agents/skills/wfdash-giellie/SKILL.md` says "trust the docs", so the
two source-of-truth docs override the branch's guidance — and they still
sent every quantity write to the pre-branch `stock_items.adjust`.
- Regenerated the tool catalogue with the generator the doc itself
names (`python3 scripts/mcp-doc-gen.py`): the count is now 148 (was
144), `stock_items.set_quantities` is in the catalogue and in the
alphabetical index, and the `stock_items.adjust` row carries its new
description. `python3 scripts/mcp-doc-gen.py --check` now passes.
- Added a `stock_items.set_quantities` row to the stock tool table in
both docs, matching the tool's real input shape: `mode` ("set" |
"add"), `lines` (1-100, each `{ sku?, description?, qty }`),
`dry_run`, plus the required `actor.id`.
- Repointed the quantity guidance in both docs away from
`stock_items.adjust` and toward `stock_items.set_quantities`, and
marked `adjust` as superseded for quantity work.
The ledger asked the final review to close this gap, and it was still
open: the negative-quantity test asserted the ledger row (the delta and
the note) but never the persisted `stock_items` patch, and nothing pinned
a negative `targetQty` under `allowNegativeQty` — where the code applies
the negative allowance only to `currentQty`.
- The negative-quantity test now also asserts the persisted patch
carries `current_qty: -3`.
- A new test asserts a negative `targetQty` is still rejected while
`allowNegativeQty` is set, and that nothing is written.
Creating a stock item with `current_qty > 0` wrote the opening-balance
`stock_ledger` row with `created_by: ""`, so the row landed in the audit
trail unattributed — and it was written after the quantity, so a failure
in between left the two out of step.
- The create now requires `actor.id` (the same 15-char check and error
style as `stock_items.set_quantities`) and stamps the opening-balance
row with it.
- The opening balance is booked through the audited write path: the item
is created with no quantity, the ledger row is written first, and the
quantity second. If the quantity write fails, the service reverses
the ledger row, so `SUM(change_qty)` still rebuilds `current_qty` and a
retry is safe.
- A zero or negative opening balance is written on the create itself,
exactly as before.
`stock_items.adjust` wrote its `stock_ledger` row with no acting user
(`created_by: ""`), and it updated `current_qty` with no compensation —
the same orphan-ledger shape the new quantity tool removed. The docs
advertise this tool, so it is a load-bearing write path.
- `updateStockItemsQuantities` takes optional `reason`, `referenceType`,
`referenceId` and `notes` overrides. The defaults are unchanged
("adjustment", "manual", the service's own note), so the /stock page
and the agent write exactly what they wrote before.
- The adjust handler now requires `actor.id` (the same 15-char check and
error style as `stock_items.set_quantities`), reads the row's current
quantity, turns the signed delta into an absolute target and calls the
service. Attribution, ledger-first ordering and the compensating
reversal row come from the service instead of being duplicated here.
- The caller's `reason` and `notes` survive the conversion, and a
negative resulting quantity is allowed — the till oversells on purpose.
- The tool description marks the tool as superseded for quantity work
and points at `stock_items.set_quantities`.
The quantity went into the .sub line, which is nowrap + ellipsis: a
28- or 38-character SKU truncated it, so the number fell off the right
edge and pushed the in-cart count out with it. The sub line is back to
what it was — sku, brand, unit, N in cart — and the quantity now sits
under the price, right-aligned, where nothing truncates it.
The POS tests asserted the file's prose rather than its expressions, so
five mutations of the implementation all passed them: a min_qty cue, a
lost zero boundary, a clamp, a round and the segment deleted. They now
pin the expressions, the interpolation, the class wiring and the dark
rule's colour value. All seven mutations tried are killed.
A non-numeric current_qty rendered "NaN in stock" and was not badged —
NaN <= 0 is false. It now falls back to 0 and badges.
Bumped the service-worker cache: pos-app.js and pos.html are both
served cache-first out of the shell, so without a new CACHE name the
tills keep the old ones.