← Acopio

What's new

Every change that reaches your installation, newest first. Support and updates are the same subscription — when something here ships, you get it.

September 2026

Labels you print as you receive, on 4×6 thermal paper. Deliveries laid out like a week instead of a list, and marking one arrived now fills in its entry for you. A report that arrives on its own every evening. Manage Users rebuilt to read like the movements table. And two long runs of work on the same two questions: when several people use the app at once, does everyone see the same warehouse — and when you press save, does the screen change now or in eleven seconds? Deleting thirteen movements went from about two and a half minutes to fifty-five seconds along the way.

v12.74 New

Scanning works after all — with a photo

Open Scan label and tap “Take a photo of the label”. Your phone camera opens, you take the picture, and the app reads the code out of it.

What Google blocks inside this app is the live viewfinder. A photo is a different thing entirely: the app is not asking for your camera, it is asking for a file, and your phone handles the camera itself — exactly the way the app has taken photos of documents for months. That was the clue, and it came from the warehouse, not from us.

Everything after the read is unchanged: the app offers what you can do with that label, fills the form, and still never saves anything on its own.

And the connection dot stops stealing taps. It had an invisible 44-pixel target drawn around a 10-pixel dot, which overlapped the tabs next to it on a narrow screen. Same dot, same target, but the space is now reserved instead of borrowed.

v12.73 Fix

Why the camera does not open, said plainly

Scanning opened a black box that asked you to point the camera at a label. It could never have worked, and the reason is not in our code: Google serves this app inside a frame belonging to another site, and since Chrome 64 a frame like that gets no camera unless the page around it grants permission. The page around it is Google's. Nothing you or the app can do from that screen changes it.

The browser never even asked for camera permission — which is why it looked like a phone problem. It was not.

The app now asks whether the camera is allowed before it opens anything. Where it is blocked, the button stays out of the way and the message says what is in the way, rather than sending you to fight with your phone settings over a permission that was never going to help.

The reader itself — reading the code, deciding the movement, filling the form without saving — is built and works. What it needs is a different way in, and that is the next piece of work.

v12.72 New

What was left over, from which job, and where it ended up

Material that comes back from a job and gets used on a different one is neither waste nor a transfer. It is material you already paid for, going somewhere else. Until now the app had no way to say so: a return asked where it came from as free text in a location box, so the answer existed and no report could group it.

A Return now asks which project it is coming back from, from the project list. That single answer is what the rest is built on.

Leftovers then show under the material in the stock table — 28 left over from BLACK HOUSE 2 — and the material stays what it is. A window screen that comes back from a job is still a window screen, so your stock is not split in two and your totals still add up.

When you send leftovers out to another job, the app offers them and tells you how many are available before you type a quantity. The movement then reads both ways: the project it went to, and the job it was left over from. What was left over from where, and where it was used, are now the same row read by two columns.

And nothing is counted twice: a return is not a purchase, so the value of your inventory does not inflate when material comes home.

v12.71 UI

Scan from where you work, and a notice that keeps its word

Scan label is now next to Entry and Exit. It was only in the account menu, which is two taps and a small target for a gloved thumb — and the two buttons it leads to were on another screen. Scanning always ends in an entry or an exit, so it belongs where those live. It stays in the account menu as well.

On a computer with no camera the button stays out of the way rather than sitting there doing nothing, and it is hidden for read-only accounts: a shortcut to a form you cannot submit is a road that ends at a locked door.

And the green notice no longer leaves before your data does

It used to say “back online” and disappear six seconds later — while the new data was still ten to twenty seconds away. Anyone who read it believed the numbers on screen were current. They were not.

Now it says Updating and stays there until the new numbers are actually on screen. Only then does it say the data has been updated, and only then does it start counting down to leave. If the data never arrives it keeps saying Updating, because that is what is true.

The offline notice now reads in two lines — what happened on top, what to do underneath — because those two things are not read at the same moment. The first you catch from across the room; the second only if the first made you stop.

v12.70 New

Scan a label with the phone camera

Scan is in the account menu. Point the camera at a label — one off a bundle or one off a rack — and the app offers what you can do with it: take that material out, move it, or see everything on that shelf. Scan a bundle and a shelf and it offers the move with the destination already filled in.

Scanning never saves anything by itself. It fills the form and stops there, so you read it and press save like always. A camera that can be pointed at the wrong label by accident has no business writing to your sheet on its own.

It uses the phone's own code reader when there is one and falls back to reading the picture itself when there is not. One code at a time, not two at once: with two codes in a single frame the fallback reader returns nothing at all, so the app asks for them one after the other — which also means it can tell you which one it just read.

And when the app does not start, it now says so

A loading screen that waits forever is worse than an error. On 14 October both of Jose's installations sat on “Connecting to your spreadsheet…” and said nothing more, on every computer and every phone. The server was fine the whole time — every execution completed, and nothing was wrong with the data.

The app already had three protections for exactly this, and all three were written inside the very block of code they were meant to protect. A block that fails to compile runs none of its lines — not the first, not the last — so they went down together with what they were guarding, and the loading screen stayed up saying connecting when it meant I died before I started.

There is now a small, separate watchdog that cannot go down with the rest. After eight seconds without a sign of life it replaces the loading message with what actually happened and what to do about it — and it tells the two causes apart, because the fixes have nothing in common: this copy of the app did not start (paste the file again, whole) or could not reach Google (check the connection; nothing is wrong with your data). Either way there is a Reload button instead of a screen that waits forever.

Two smaller holes of the same kind closed with it: the charts are loaded from Google's own servers, and if that download failed it took the entire app down with it — now you just lose the charts and keep the warehouse. And loading the app used to spend three of its own rate limits per visit instead of one, which on a busy morning could have made the incoming list vanish with no explanation.

v12.69 UI

The offline notice stops pushing the app around

What was wrong with it was never the colour. The red and green connection messages took up space at the top of the page, so the whole app jumped down when one appeared and back up when it left — twice in five seconds, for a message that asks nothing of you. They now float over the top instead, as a rounded pill, and nothing moves.

And what is underneath stays clickable, which is the part that matters. An earlier version of this app floated the notice and on a phone it swallowed the whole navigation bar: the tabs and the account button sat under it and could not be pressed. The fix is not the position, it is that the notice does not intercept touches at all.

The notice about your two files not matching still takes its space, on purpose. That one can live for months and is the only sign that half an update was pasted in — it must never be covered or missed.

v12.68 New

Labels for your racks, not just your material

Pick one location, several, or all of them in Settings → Locations, and print their labels. Three per sheet, and it tells you how many sheets that is before you print — sixty racks is twenty sheets, and thermal paper comes on a roll that does not come back.

They look nothing like a material label, on purpose. These are landscape; the material ones are upright. You can tell them apart across the room without reading a word, because two white stickers with a code on them get stuck in the wrong place sooner or later.

A rack label carries the name and the code and nothing else. No quantity, no date, no PO. It gets stuck on once and lives for years, so anything that can go out of date would be a label that lies by tomorrow — which is exactly the opposite of a material label, where the whole point is what was in that bundle on that day.

The sheet is drawn as fractions of the page rather than in millimetres, so it fills whatever paper your printer gives it. That matters on a phone, where some print services offer only one paper size and then shrink the page to fit the sticker: in millimetres the label would be shrunk twice and come out tiny.

v12.67 Fix

The print window tells you the paper size

A label that comes out the right size in the middle of a big sheet is not a printing problem. It is a dropdown. The Android print dialog usually opens on ISO A4, and since the label is drawn at its true 4×6 inches, what you get is a correct label surrounded by wasted paper. Neither the app nor Chrome was doing anything wrong, and there was nothing on screen that said so.

The Print window now says the size before you spend a sheet — and on a phone it also says where to change it, because that is where the dialog resets to A4 and that is where the paper gets wasted.

v12.66 New

Every label now carries a QR code

The dotted box that said “QR / barcode” is a real code now. It is generated inside the app itself — no service is called, nothing about your materials leaves your installation, and it works with the wifi down. A warehouse prints labels on the day the internet is out too.

The code is ours, not the manufacturer's, and that is what makes it useful from day one: nobody has to label what arrives from the factory, and no product barcode has to exist. It carries what the app already knows about the bundle — category, material, rack, and the movement it came from.

Reading it with a phone camera, to fill in a material on the way out, is the next step. The code printed today will be the one that gets read then: its first line is the format version, so a label stuck to a shelf two years from now will still be understood.

v12.65 New

The app finally tells you what changed

We wrote two release notes for every version, published both, and inside the app there was not one link that led to them. A customer had no way to find out what had improved short of asking. Now, the first time you open Acopio after it has been updated, a window tells you what changed — and then never shows you that version again.

It shows the actual before and after, not a decorative picture: the string that used to appear and the one that appears now, taken from the same entry as the text, so the two cannot drift apart over time.

You can close it, and that costs you nothing — which is the whole point. It lives permanently in the account menu under What's new, with a dot on your account circle when there is something unread. A notice you cannot get back forces you to read it; one you can is safe to dismiss. Four releases are kept inside the app, so it works with no internet and nobody has to leave to find out what happened; the link to the full note is there for whoever wants it, and points at that version only.

If you have renamed the product, the link and the support address do not appear at all. An app that no longer says Acopio should not be telling your team where it came from.

v12.64 Fix

A location that doesn't exist now says so while you type

Type a rack that isn't in your warehouse and the app said nothing at all — not while you typed it, not when you added the quantity, not even when it wrote the notification email naming that rack. The whole form behaved as though the place were real, right up to the second you pressed Save. Now the field says so as soon as you leave it, offers the one you probably meant (A1p → A1A), and offers to create it if you're allowed to. It never blocks saving: the list in your browser can be a few seconds out of date, so the server still has the last word.

And when it did finally complain, it complained in code. What appeared was Error: UNKNOWN_LOCATION|P|B in a red bar that vanished after six seconds. That looks like the app broke. It hadn't: it refused correctly and reported it terribly. The app marks certain refusals so the screen can offer a way out instead of showing red text — but that handling was written three times, once per save path, and only one of the three was complete. Multi-material entry, which is the one most people use, had the shortest list. It is now written once and all three use it.

The explanation was already in the code, word for word: “the day the server marks an entry error, the user will read SYSTEM_BUSY| on their screen”. The day came. It wasn't SYSTEM_BUSY.

v12.63 Safety

Six doors that checked who you are but not how often you knock

Six of the app's doors had a lock and no limit. Six functions — reading incoming deliveries, adding, changing and deleting them, reading your watched materials, and asking whether the installation is set up — checked identity correctly but never counted calls, while the two heaviest ones already did. None of them let anyone do anything they shouldn't. What they let anyone do is burn through the installation's daily Google quota, and that leaves the app dead for everyone until the next day. A forgotten browser tab retrying in a loop could do it by accident.

All six now have a ceiling, and every number is two orders of magnitude above real measured use: it should get in the way of a loop, never of a person. The three that write share one counter, so alternating between them doesn't triple the allowance. The counter is per person, so one jammed tab doesn't lock out the warehouse. And the limit sits after the identity check, so a stranger is still told they are not signed in rather than told to come back later.

v12.63 Fix

“+ Add it” stops disappearing for good

Type a new project, click back into the box, and the offer to add it was gone forever. Not slow to return — gone. Clicking into the field cleared the prompt (correct: while you are editing, it would be describing a value that is no longer there), but the only thing that could bring it back fired when the text changed. Leave the box without touching a character and nothing fires. Nothing ever would again.

What made it confusing is that the card in the corner deck stayed, so the app offered the project in one place and not in the other, and it looked like the project had been half-created. Now the prompt is re-checked every time you leave the field, whether the text changed or not.

v12.62 UI

The same label stops looking like two labels

“Arrived” is one label, and it was drawn two ways. In the incoming table its green capsule had one amount of padding; inside the morning popup, the same label had another. Nothing in the app required them to match, so they didn't. Twenty rules across the app draw that same thing — a short word or a number inside a coloured capsule — and between the twenty there were fourteen different paddings, ten text sizes and six corner radii. Now there is one capsule. The morning popup still uses bigger text, because you read it from further away, but the capsule around it is the same one as everywhere else.

The corners were free. These capsules are 16 to 22 pixels tall, and anything above a 10 pixel radius already rounds them completely — so 10, 16, 20 and 999 looked identical. Four values became one and nothing moved by a pixel. The three that stay square stay square on purpose: they are counters, not labels, and that is how you tell them apart at a glance.

And the small line under a bigger line. Fourteen places put a quiet second line under a title — a date under a name, an email under a user, a note under a card — using three different gaps. One job, one gap.

Underneath, the spacing got a scale. 697 paddings, margins and gaps with 57 different lengths between them, and the same illness the text sizes had: between 4.8 and 12 pixels there were ten values each used thirty times or more, spaced eight tenths of a pixel apart. Nobody can see eight tenths of a pixel. This release converts only the 355 declarations that land exactly on a step, so nothing moves at all — verified in a real browser, 504 rules resolved before and after, zero differences. The ones that would move are counted and named for next time.

Visible change in total: twenty capsules whose side padding shifts by at most two pixels, in exchange for all of them finally matching.

v12.61 UI

Four depths instead of forty-five shadows

Every panel, card and menu that lifts off the page had its own hand-written shadow. Forty-five different ones across sixty-one places — near enough one bespoke shadow per use. Nothing was wrong with any single one; what was wrong was that two cards sitting side by side could float at visibly different heights for no reason anyone chose. There are now four depths — resting on the page, floating above it, a panel, and on top of everything — and the steps between them are proportional, because the eye reads blur as a ratio and not as a count of pixels.

The focus rings turned out to be a second system wearing the first one's clothes. A ring around the field you are typing in is written the same way as a shadow, but it has no blur and it is not depth: it says which one is selected. Separating them left four rings — neutral, good, bad and warning — so a selected row looks the same whichever screen you are on.

Twenty-one shadows stay outside the system, each for a written reason: the three tinted with the brand blue (a tinted shadow looks like the same material; a black one looks like tape), the one that casts sideways because the drawer slides in from the edge, the small sharp ones on controls, the inset ones, and the frames of animated pulses. They are recorded as exceptions rather than left as leftovers.

Nothing moved, nothing resized, no screen changed shape. If it looks like nothing happened, that is the result.

v12.60 Fix

Photos stop arriving sideways, and a PO stops being two POs

Photos were being uploaded rotated, and that caused two complaints that looked unrelated. A phone rarely turns the pixels when you take a picture: it stores the sensor's own landscape bitmap and attaches a tag saying “turn this 90°”. Google Docs ignores that tag — so the photo went into the PDF sideways, and a sideways photo on an upright page fits the width and leaves the bottom blank. One cause, two symptoms. Photos are now straightened in the browser before upload, which fixes the PDF, the preview and the shelf thumbnail at once.

The document preview cut off the top of the page. The header was not to blame: centring something taller than its box pushes its beginning out of reach, and the scrollbar cannot get back to it. Now it centres while it fits and stops centring when it does not.

“Amsco” one day and “AMSCO” the next left two suppliers where there is one. Category, name and project were already stored one way; PO, GC, supplier and received-by were not. They are now. Dashes survive — a PO like 07-6329 is still 07-6329. Nothing already saved is touched.

v12.59 Fix

Three answers that were not true

The app blamed a colleague who did not exist. Release a reservation, press Release again, and it said “someone else got there first”. Nobody else was there — you released it yourself, seconds earlier. All the code knew was that the reservation was no longer active; who released it was a guess. Now it reads the UnlockedBy column that was sitting there all along and says “you already released this one”, or names the person, or simply says it was already released. No accusations by elimination.

Searching said “nothing matches” without looking in the archive. Search a PO from last year and the only possible conclusion was that it does not exist. On an inventory system that is worse than an error: it is a false answer wearing the face of a good one. Now it says the archive has not been searched and offers a button to do it.

Merging two locations left reservations and photos behind. It rewrote the archive and the location list — everything you can see — and left the lock and photo records pointing at a shelf that no longer exists. Material held on a shelf the map does not show, and photos the surviving shelf never inherits. Both move now.

v12.58 Fix

Four things found by filming the app on a phone

You could not read what you were dispatching. In the rack drawer the material names came out as “P…”, “L…”, “KA…”. One line was the cause: the name was the only part of the row allowed to shrink, so it shrank to nothing while the Reserve button kept its full width. That is the screen you press Exit from — material was leaving the warehouse with its name unreadable. On a narrow screen the name now gets the whole line to itself.

The keyboard hid the Save button. The sticky bar was already there; the fault was 90vh, which on Android does not shrink when the keyboard opens. The window stayed the height of the whole screen and its bar sat below the keyboard. One letter fixes it: dvh.

The cards moved in two ways coming in and one going out. Now one way in, and two different ways out that mean different things: a card you accept carries on to the left, into the app — a card you dismiss just fades where it is. Moving means something happened; not moving means nothing did.

Clicking a second card cancelled the first one's progress. Adding a project redrew the whole pile, which wiped the “Adding…” state off any card still working — and let you send the same one to the server twice. The redraw now waits until nothing is in flight, and a progress line counts them off at the bottom left.

The header also shrinks as you scroll down on a phone, giving back about a third of the screen, and comes back when you scroll up.

v12.57 UI

Delete is red now, and exactly the same size

The Delete button in Movements and in Manage Users was grey, the same as Edit and Labels beside it. The one button on that bar that destroys something looked like the ones that do not. It is red now.

Measured before and after, because “make it red” turned out not to be free: .btn-ghost carries a 1px border and .btn-danger carried none, so simply recolouring it would have made it 2px shorter and 2px narrower than the buttons next to it. It now carries a transparent border of the same width, so the box is identical — 44×76 on a touchscreen, 26×76 with a mouse, before and after.

That also lines up every other red button in the app with the grey ones beside it. They had been 2px out of step in every bar where both appear.

v12.56 UI

Tables: two row densities instead of eight near-identical ones

Second batch of the standardisation. Eight rules set the padding of a table cell and between them held eight different values — but looking at them, they were not eight decisions. They were two nobody had named: the normal table and the compact ones, with half a pixel of noise around each.

Cell padding is the one thing in this batch that changes something you can see: it sets the row height, and with it how many rows fit on a screen. So the two values were picked so nothing moves more than 0.8 of a pixel per side, not so they would come out round.

Table text sizes and line heights now come from the same scale as the buttons. The largest any of them moved is a third of a pixel.

v12.55 UI

Buttons: one shape, and the ones that delete are big enough for a thumb

The app had 147 button rules between them holding 26 different paddings, 9 corner radii and 16 text sizes — mixing units for the same thing. Nobody decided that; it accumulated one reasonable choice at a time.

What the measuring turned up was not what we expected: the app already had the design tokens written down. --radius and --shadow had been sitting in the stylesheet for versions. The colour tokens were used properly — --border in 135 places. The shape ones were not: --radius appeared in 5 of 224 corners. There was no missing system. It just was not being used.

Buttons now share one corner radius and one text scale of thirteen steps. The three most-used sizes in the whole app did not move a pixel, so the screens look the same.

And one change that is not about looks. Button heights were 34, 36, 38, 40 and 44 pixels. The minimum a thumb reliably hits is 44. Anything that deletes something is now 44 on a touchscreen, even inside a table row — missing “Edit” costs you a tap, missing “Delete” destroys something, and the same size for both was the wrong call. The other table buttons stay compact, because raising all of them would double the height of every row.

v12.54 Data

Five quiet things in the spreadsheet, now looked after

Total Cost is watched. It is Qty × Unit Cost, always — and it was the one calculated column with nobody checking it. Correct a unit price by hand and the total kept the old number for good, so whoever adds up that column was adding up something that had stopped being true. It is now recalculated on every rebuild, and the correction is reported with the row number so you can go and look. Rows with no unit cost are left alone: an empty total there is deliberate, and a zero we invented in an accounting column would be a false figure.

Five columns that were written and never read are gone. Two of them were not even data — Location_Type held the word RACK in every single row, and Status held “At Site” in every single row. The other three held the same timestamp repeated once per material: with 400 materials, the same minute 400 times. Nothing outside the app reads these sheets, and they are rebuilt whole on every save, so there was nothing to migrate.

CONFIG now says what it is ignoring. Two of its columns — Admin Email and Archive Cutoff Months — are single settings read from row 2 only, in a sheet whose every other column is a list that grows downwards. Write a second admin address underneath the first and nothing happened, and nothing said so. Check this installation now names the column, the row numbers, and which value is actually in use.

The RESERVATIONS tab stops misinforming you. It is empty because nothing uses it — reservations live in MATERIAL_LOCKS — but its own note said it was “rebuilt from the archive, edits here are overwritten”. Neither is true, and anyone opening it could only conclude the app had deleted their reservations. The tab is kept, in case an older installation left data there; the note now says what is going on.

Two column names in AUDIT_LOG were describing something that was not there. Old Value and New Value: of 56 places that write to the audit trail, 19 leave both empty and 17 fill one with context rather than a previous value. Read from the sheet, that looks like a broken record. They are now Detail 2 and Detail 3. Not one entry moved — only the labels changed, and only where they still said the old words.

v12.53 UI

User management: no buttons until you pick somebody, and a warning you can read

Edit, Delete and Deactivate used to sit there greyed out whether or not a user was ticked. They do not act on the screen — they act on a row — so with no row ticked they are not “unavailable”: there is nothing for them to act on. They now appear when you tick somebody, and the one you get is Deactivate or Reactivate depending on where that person stands. Add User stays, because it is the button that creates the first row.

The delete warning was three bullets and a closing paragraph — which is not read, it is skipped. It is now one sentence that names the person and says what happens and what does not. The rest sits folded behind an ⓘ, there for anyone who hesitates and out of the way of anyone who does not.

And confirmation dialogs have an ✕ in the corner, where people look for it. The safe button stays as well: in a dialog that destroys something, the safe way out must not be smaller or further away than the dangerous one.

v12.52 Fix

Signing in could send you back to a copy of the app that no longer answers

Press Sign in with Google, the window opens, you pick your account — and the window dies on a Google Drive error while the page waits four minutes to say “Timed out. Please try again.” Trying again does exactly the same thing.

The return address is stored, and it can end up pointing at an older deployment of the app: that happens when the sheet is copied, when a new deployment is made instead of updating the existing one, and when the Cloud project is changed. Nothing ever compared it with the app actually running.

Now the page compares them before opening the window. If they are not the same app it says so at once, shows both, and names the menu item that repairs it — rather than opening a window it already knows cannot come back. Check this installation resets it, and publishing an update keeps it in step from then on.

v12.51 Fix

A user row you never meant to create can now be removed

An address registered with a typo could not be renamed — the correct one already existed on another row — and could not be removed either, because the only option was Deactivate. Two sealed exits and no door: the mistake was locked in.

There is now Delete permanently, inside the user's own card, in red and away from Save. It is for a row that should never have existed: a typo, a duplicate, something a migration brought in twice. For somebody who actually worked here, Deactivate is still the right answer and the warning says so.

The whole row goes into the audit log before it disappears, and the address is cleared from the old CONFIG list at the same time — otherwise the account simply comes back on its own the next time that list is checked.

v12.50 Fix

Deleting and editing now update the figures the moment they finish

Deleting a movement took the row off the screen at once, but the totals underneath kept the old number until the next refresh — which reads as if the delete had not counted. The figures now come back with the delete itself.

They come back only on the delete that actually rebuilt the totals — the last one of a batch. Working them out on every delete would bring back the slow burst fixed in v11.96, and sending the old numbers as if they were the new ones would be worse than sending nothing.

Editing a movement used to redraw the row by redoing the server's own tidying in the browser — two sums that had to agree with nothing making them. The server now returns the row exactly as it was written, and the totals for both materials, because changing a category or a name moves stock from one to the other.

v12.49 Fix

What you just saved now appears the second the server confirms it

Saving ten deliveries, timed frame by frame: the window closed after 14 seconds and the movement list took a further 16 and a half to show them. Sixteen seconds looking at a table that does not contain what you just entered is exactly when somebody decides it failed and presses save again.

It was not slow. The browser was asking for the whole picture again to learn about rows the server had just written and was already handing back. Those rows now go straight into the list, with their own ids so the quiet refresh behind them cannot show anything twice.

Opening this up turned up something bigger: the live stock figures the server has calculated since v12.24 — so the screen would not have to ask — were never being passed on by any of the three save paths. The browser asked for them and got nothing. Fixed in all three, with a test that now pins the whole return trip.

v12.48 Fix

A typo in an email address could only be fixed in the spreadsheet

An address registered with one letter wrong — @ox-glasss.com instead of @ox-glass.com — is a perfectly valid email, so nothing refused it. It sat in the user list looking active, and simply never let that person in. And the email field could not be edited, so correcting one letter meant opening the Google Sheet by hand.

An administrator can now correct an address in the app. Three guards sit behind it: an address already registered to someone else is refused, nobody can change their own address (they would lose access the instant it saved), and the change is recorded from-and-to in the audit log.

And before saving, an address whose domain is one or two letters away from one everybody else already uses now asks whether you meant the other one. Close-but-different is exactly what a typo looks like; a genuinely different domain — a contractor on Gmail — says nothing.

v12.48 Fix

The nightly backup can stop without anybody noticing

The switch stays green, the list stays full, the newest copy still has its date and a link that opens. Nothing said that date was from last week. A backup system that fails in silence is worse than having none, because anyone with none knows it.

Settings → System now says it outright when the nightly copy has not run for more than a day and a half, names the most common cause, and points at Back up now. It stays quiet when the backup is switched off on purpose, and it does not cry wolf over a single late night.

v12.47 Fix

“Google rejected that key” when Google had rejected nothing

Saving an AI key while Google’s service was busy produced “Google rejected that key (HTTP 503)”. A 503 means Google is overloaded. It does not look at the key at all — so a perfectly good key was reported as bad, and the obvious response is to go and make another one, and another.

A busy Google is no longer treated as a verdict on your key. The key is saved, and the app says plainly that it could not be checked right now and why. Only the errors that really are about the key — copied incompletely, or the Generative Language API switched off — still refuse it, and now they say which.

Checking a key also stopped trying four models in a row. That fallback list exists so the reader keeps working when Google retires a model; it proves nothing about a key, and it made the wait four times longer than it needed to be.

v12.46 Fix

Renaming: one at a time, and nothing you typed gets thrown away

In Settings, pressing the pencil on several categories opened them all for editing — but saving one repaints the list and threw away whatever you had typed in the others, with no warning. The app was offering work it could not deliver.

Now one rename is open at a time. If the open one has unsaved text, the new one does not open and the app says why — your typing is never discarded to make room. If it is untouched, it just closes and the next one opens, because pressing the wrong pencil and correcting yourself is the most common thing there is.

It saves one at a time on purpose: renaming a category rewrites every movement that used it and then rebuilds the stock cache. Five at once is five of those passes, and if the third fails you are left with half a batch applied and no way to tell which half.

v12.45 Security

Two guards that said yes by default

Who may record movements is now a list of who can, not a list of who cannot. The app used to ask "is this person a VIEWER?" instead of "may this person write?". With three roles those give the same answer — which is why it never showed. But any new role would have walked straight through, simply by not being called VIEWER, and in a warehouse that means permission to move material. Nothing could do that today; it would have been true the day after a fourth role existed.

And a category that is not in your catalog can no longer come in with material. A category is half of a material's identity here — file something under WINDOWS when the catalog says WINDOW and it is not the same material badly labelled, it is a second material with its own stock that nobody ever adds up.

You could never type one: the category box is a closed dropdown. What could get through was everything that does not go through the screen — an import, mainly. Imports still bring their categories in, because an import means "this is my warehouse as it stands", but now they are written into your catalog and counted. That matters more than it sounds: a material whose category is not in the list cannot even be selected to take it out again.

Taking material OUT is never blocked by either check — whatever is already inside has to be able to leave.

v12.44 Fix

The daily report was telling you about a day that had not happened

Three things were wrong at once, and together they meant the movements of a working day were never reported at all. The email arrived, looked fine, and always said nothing had happened.

It went out at the wrong hour. The app wrote down the hour it had scheduled the job for, and later trusted that note instead of the job itself — and Google gives no way to ask a scheduled job what time it runs, so the note could never be checked. Once the two drifted apart, Settings said 4:00 PM and the email came at 2:53 AM. Now the job wakes up every hour and the app decides when it is time, so there is nothing left that can drift. Changing the time in Settings takes effect without touching anything.

And it always reported "today", whenever it ran. At 2 AM, "today" is three hours old and empty. The rule now: a morning email reports the day that ended; an evening one reports the day that is ending. The email still names the day it covers, so a quiet day reads as a quiet day instead of as a lie.

The date is written the US way. October 6th was printing as 06/10/2026, which reads as June 10th.

Separately: 🏭 Acopio → Check installation now fills in missing column names on your tabs. The code to do it existed and nothing ever called it outside first-time setup, so an installation that was already running could never get it — which is why RESERVATIONS and AUDIT_LOG had blank headers. A column with no name looks like junk, and sooner or later somebody deletes it.

v12.43 Fix

Material can no longer be put in a rack that does not exist

Typing a rack that is not in your warehouse — A1P when you meant A1B — used to save it anyway, and a brand-new location appeared on your warehouse map with material in it. That is worse than a typo: an invented rack is not on any shelf, so the app says the material is somewhere nobody can go to.

Now the app checks the rack the material is arriving at, and if it is not one of yours it says so — and offers the one you probably meant. If you can manage the catalog, you can create it right there and the movement saves without retyping it.

Taking material OUT of a rack that does not exist is still allowed, on purpose. Whatever ended up in A1P before this fix has to be able to come out, or the fix would trap it there for good. So the rack list you pick from when material leaves still shows everywhere stock actually is; the one for where it goes shows only real locations.

Importing a file still creates the racks it names — importing means "this is my warehouse as it stands" — but it adds them to your location list and tells you how many, instead of creating them quietly.

v12.42 Easier

The link to the app is a link now — click it

Publishing an update used to hand you the address as plain text in a narrow box, cut off, with a scrollbar. To use it you had to drag-select it blind, copy it, switch to the browser and paste. That is because the alert box Google gives us can only show text — it cannot show a link.

Now both 🔧 Advanced → Push Update Live and Open App show a proper window with an Open button that goes straight there, the full address in a field you can select, and a Copy button that only says Copied when the copy actually worked.

New people get the link by email. Adding someone in Manage Users now sends them an invitation with the address, which Google account to sign in with, and what their role lets them do. Nobody has to find the address and forward it by hand. You can untick that if you are setting accounts up in advance — and if the email cannot be sent, the app says so instead of claiming it went.

Bookmark that address once and the spreadsheet stops being part of anybody's day.

v12.41 Security

One user list, not two

Installations upgraded from an older version carried two lists of users: the one you manage in Manage Users, and an older one still sitting in the CONFIG tab. Both let people in — but only one of them is on your screen. So anybody who was in the old list could sign in without ever appearing in Manage Users, which means you had no way to stop them from inside the app.

The old list is not deleted: in a half-migrated installation the people in it are real staff, and removing it overnight would lock somebody out of their job on a Tuesday morning. Instead it has become a one-way door. The next time one of those people signs in, they are written into your real user list on the spot — same role, still active, nothing changed about their access — and from that moment they appear in Manage Users with their own switch, so you can turn them off.

🏭 Acopio → Check installation now does the whole crossing at once instead of waiting for people to sign in. It tells you how many were brought across, which emails carry a different role in each list (Manage Users is the one that counts), and which rows of your user list have something other than an email where the email goes — those grant nothing, but they show up on screen. It never deletes a row.

v12.40 Security

Refused sign-ins are now written down — and you get told

The door worked: somebody who is not on your user list was turned away. But the refusal left no trace anywhere, so from the inside an attempt to get in and a quiet day looked exactly the same. That is a lock without a peephole.

Every refusal now lands in the AUDIT_LOG tab of your spreadsheet under ACCESS_DENIED, with who tried and why. One line per person per minute — a forgotten browser tab retries on its own every twenty seconds, and without that cap a single abandoned screen would bury the one line that mattered.

And at ten refusals in ten minutes you get an email, at most one an hour: who tried, what to do if it is one of your own people, and where the full record is. A log nobody opens warns nobody.

Being refused permission for one action — a read-only user pressing save — is not recorded here. That person is inside and simply lacks a permission; mixing the two would hide outsiders among legitimate people.

v12.39 Security

Deactivating a user now actually locks them out

If your installation was upgraded from an older version, switching someone off in Manage Users may not have been stopping them. The screen said "deactivated"; the server let them in anyway.

The check looked them up in the current user list and, finding them switched off, carried on to the old list the app was migrated from — and that old list still has everybody in it. So the "off" switch was read, and then quietly overruled by a leftover.

Now the current user list decides, both ways: switched on, they get their role; switched off, they are out, and the old list is not consulted. Somebody who was never added to the current list still gets in exactly as before, which is the one case the old list is there for.

This is worth checking on your own installation today: open Manage Users and confirm everyone switched off is someone you meant to switch off.

v12.38 Safety

The app now tells you when it has lost the connection

Until today the only sign was a small coloured dot in the corner, and you had to hover over it to read why. Nobody hovers over a dot. You keep working, you keep typing — and none of it is being saved.

Now a red bar across the top says you are offline, nothing you type can be saved, and the app is still retrying on its own. It cannot be dismissed: while it is still true, the warning is still true. When the connection comes back it turns green, says Back online, and disappears on its own after five seconds — the opposite rule, because by then it has stopped being news.

Opening the app never shows it: starting up is not a recovery, and greeting you with "Back online" would be reporting an outage that never happened.

v12.37 Fix

The Save button no longer scrolls out of the window

In a form taller than the screen — Add Expected Material, Entry, Exit — the buttons at the bottom sat at the end of the scrolling content. You had to scroll down to reach Save, and on a short screen the button was sliced in half by the edge of the window.

The row of buttons is now pinned to the bottom of the window, the same way the title and the ✕ have been pinned to the top for months. The form scrolls underneath it; the buttons stay.

And this matters more than convenience: the error message lives in that row. "Material 1 needs a name" appears right beside Save. With the row off-screen you press Save, nothing happens, and the reason is exactly where you are not looking.

If the window fits on your screen, it now fits — the ceiling went from 82% to 90% of the screen height, which is what Add Expected Material needed to stop scrolling at all on a normal laptop.

And every button in that row is now the same size. A "Cancel" at 88 pixels beside a "Save" at 120 was two sizes in one row; they are all one size now, whatever class they were written with, and none of them can wrap onto a second line and shove the row around.

v12.36 Fix

Every published version now says which one it is

Publishing from the menu labelled every version the same way, so the list in Google's deployment dialog read as a column of identical names with no way to tell one from the next. Each version is now stamped with the app version, its build and the date it went out.

The deployment's own name deliberately stays the same, and that is not an oversight: it is the key the app uses to find its existing deployment and update it in place. Change it and the next publish would create a new deployment — which means a new address, with every bookmark in the warehouse pointing at the old one.

v12.35 Safety

Your categories and locations can no longer be wiped by a hiccup

The code that saves your lists — categories, suppliers, projects, locations — emptied the column first and wrote the new list second. Between those two steps sits every ordinary interruption Google can throw: a quota, a timeout, a passing error. Anything landing there left that list empty, with nothing to warn you. You would find out on opening a dropdown with nothing in it.

It now writes first and clears what is left over afterwards. If the write fails, your old list is still there. An out-of-date list is a small, visible problem; an empty one is a large, invisible one.

And a second fault, found while fixing the first. A location's name and its type live in two columns side by side and only mean anything together — and they were written in two separate operations. If the first one landed and the second did not, the names and the types slid out of line: one rack took another's type, a rack appeared archived, one stopped being archived. Nothing failed and nothing said so. They are now written as a single block, which for Google is one operation: the half-done state no longer exists.

v12.34 Fix

"Load more" no longer throws you to the bottom of the list

Pressing it brought the next 75 movements and then jumped the view past all of them, to the very last row. You lost the place you were reading and had to scroll back up to find it.

Measured: the row you were looking at moved about 5,000 pixels off the top of the screen. Now it stays exactly where it was and the new rows arrive underneath, which is the whole point of a "more" button.

And the other button that grows the same list got the same treatment. "Load Older History" was quietly resetting the table back to its first 75 rows — so anyone who had pressed "Load more" ten times lost all ten, and then had to press it ten times again to reach the older movements they had just asked for. A button that brings things cannot hide the things that were already there.

v12.33 Fix

After a real archive run, the report says how each sheet ended up

Running the archive for real finished correctly and reported every step — but the one line that says what the two sheets hold now was missing. The numbers were in the step-by-step, spread across it; the summary that puts them side by side only ever appeared in the rehearsal.

It could not appear: the two figures were worked out inside the rehearsal's own branch, so a real run never had them. They are now worked out for both — and on a real run they are read back from the sheets after writing, not carried over from what was about to be written. Once something has been written, "how many did I mean to write" and "how many are there" are two different questions, and the second is the one worth printing.

v12.32 Fix

"Open WMS App" could hand you someone else's address

On a spreadsheet copied from one that had already been published, Google answers the question "what is this app's address?" with the original file's address. The copy has its own, somewhere else. Following the one Google gives you lands on "Sorry, unable to open the file at this time", and nothing about that page suggests the link was the problem.

The app already knew this — it keeps the real address in a setting that wins over Google's guess, and the setup wizard has been reading it that way for months. The menu entry was the one place that never asked.

Now it asks. And Push Update Live writes the address down the moment it publishes, which is the only moment it is known for certain, so the menu is right from then on without anybody typing anything.

A second one came out of the same check. Signing in with Google sends you back to the app afterwards, and on a copy with nothing saved that return address was Google's guess too — so the sign-in could send people back to somebody else's installation, failing with an error that looks nothing like its cause. It now prefers the recorded address as well.

v12.31 Internal

A table of contents for the code, and a test that keeps it honest

Nothing in the app changed. This one is for whoever has to work on it — today that is one person, and one day it will not be.

The server file is 13,279 lines across 68 sections. It now opens with a numbered index of those sections, so finding something is reading a list instead of scrolling. The index is generated from the file's own section headings, not typed, and a test regenerates it and compares it line by line: rename, move, add or remove a section without updating the index and the test fails.

That part is the point. A hand-written index is one more list that has to agree with something else with nothing forcing it — the exact shape of bug this codebase has caught itself making twice. And an index that lies is worse than no index at all: no index makes you search, a wrong one makes you believe you already have.

v12.30 New

Archive the old movements now, instead of waiting for 3am

The job that moves old movements out of the main list runs once a night. That made every attempt to check on it cost twenty-four hours: press save, wait, look tomorrow. There is now a button that runs the real job on the spot — the same job, the same safety checks, not a practice copy — under Advanced → Archive old movements NOW.

It rehearses first and shows you the numbers: how many would move out, how many would come back in, how many would stay, and what each sheet would hold afterwards. Nothing is written until you answer yes. The report you get after it runs is the same one, in the past tense.

And the last 30 days are never archived again, whatever the cutoff says. With the cutoff at six or twelve months that was already true by arithmetic — but it was true by accident, because it depended on a setting. A setting can be wrong, and a cutoff that reads wrong can send every movement to the archive and leave the main list empty. Now it is a rule, and when it has to step in, the report says so out loud.

v12.29 Fix

Every nightly run now records which version of the app it was

This is the one that explains the last two weeks. The failure on 1 October left a stack trace, and the stack trace said the function that broke was at line 2,148, twenty-two lines above its own trigger. In the version that added the safety checks, that same function sits at line 4,371 and is about three hundred lines long.

So the safety checks were not in the code that ran. The nightly job had been executing an older copy of the script while the new one sat in the editor — which is why three fixes in a row appeared to change nothing. Nothing anywhere in the app could answer "but is this the new code?", so the question went unasked for two weeks.

Now every run writes it down twice: once when it starts, in case it breaks on the first line, and once when it finishes. The version also rides along in the error record. One short line, and the question can never go unanswered again.

And the rehearsal button now actually starts. It was shipped without declaring who it is, so the only thing it ever said was "not authenticated". A diagnostic that does not start is worse than no diagnostic: it costs a day to somebody who was counting on it.

v12.28 Fix

When the nightly job fails, the log now says where it got to

Three failures in a row left exactly the same line: "the number of columns in the data does not match the number of columns in the range. The data has 23, and the range, 20." That line does not say which sheet, or at which step, or how wide either sheet was. It is the shape of the failure without the failure.

The job now leaves a trail as it goes — how wide each sheet is against the width the rows need, what the cutoff is, how many rows go each way, which of the two writes it was attempting, and whether the first one had finished. When it throws, all of that goes into the error log along with the stack and its line numbers.

Putting the rows back, added in v12.27, is a seatbelt — it is not the fix. Losing rows must not become a thing the app routinely recovers from. This is what turns the next failure into something that can actually be diagnosed instead of guessed at.

One detail worth recording: the trail very nearly never arrived. The error log discards anything that is not an object, so the first version — which passed a plain string — would have been dropped silently, and we would have had the same useless line while believing it now said something.

v12.27 Fix

If the nightly job loses rows, it puts them back

Since v12.14 the job has counted the movements before and after writing, so it could tell when rows went missing. And all it did was say so — a line in the error log, an email, and the sheet left broken. That happened three times, and all three times it was found hours later and fixed by hand from a backup.

Noticing you have just lost a thousand rows and not putting them back is nearly worse than not noticing: everything needed to repair it is in memory, two lines away. Both sheets are still held exactly as they were before anything was touched. So now they go back — straight away, without asking.

This does not depend on knowing why it failed, and that is the point of writing it this way. The cause of the three incidents is still not established. A repair that only works once you understand the cause is not a safety net, it is a hope.

The email still goes out either way: a silent repair would hide the cause, and the cause still needs finding.

v12.27 Fix

Older history loads newest first, a batch at a time

Load Older History fetched the entire archived history in one go and then said "already loaded". With 56 rows that looks like the app can only ever load 56; with twenty thousand it would be a trip that never finishes.

It now brings 300 at a time, starting with the most recent and working back, and each press continues where the last one stopped. Only the rows in that batch are read, so it stays quick however much history there is.

And the real gap was not the batching — it was that the button never said whether it had finished. "Older History (56)" cannot tell "56 of 56" from "56 of 1,271", so you have to guess. Now it says "Load 300 more — 56 loaded, 1,215 older still there", or "All older history loaded" when there is nothing left.

v12.26 New

You can rehearse the nightly archive whenever you like

The job that tidies old movements runs at 3 in the morning with nobody watching, and writes what it did to a tab nobody opens. When it went wrong it was found hours later and by accident. A failure you can only observe at 3am is a failure you cannot investigate.

There is now 🌙 Test the nightly archive in the Advanced menu. It runs tonight's job in full — the same checks, the same counts, the same split — and stops just before the two writes. It is safe to press at any time on real data, and it tells you what tonight would do: how wide each sheet actually is against the width the rows need, how many movements are in each, what your cutoff is and what it counts as old, how many would move each way, what each check says, and the exact error if one happens.

The rehearsal lives inside the real job rather than being a copy of it. A copy would be a second version that always agrees with itself, which is how a broken safety net went unnoticed for ten days. And it is tested like anything else — including that it truly writes nothing, which caught a first version that quietly widened the sheets while claiming to be a rehearsal.

v12.25 Fix

An empty movement list now tells you which kind of empty it is

The alarm added in v12.14 could never have gone off. It was written to notice the one thing that cannot happen on its own — a warehouse with stock and an archive with no movements — and it looped over the stock list as if it were an array. It is not; it is a map keyed by material. The loop ran zero times and the answer was always no. Ten days with the net up and a hole in it.

That is the same shape as the backup snapshot that never worked: a protection written, assumed good, and never once executed. What the two have in common is not carelessness — neither had a test. A protection without a test is not a protection, it is an intention. This one now has a test that runs it, and the old version fails it.

And there are three kinds of empty, not two. The third is the one that caused the scare: the recent list is empty because the nightly job moved everything past your cutoff into the archived history. Nothing is lost — but the screen looked exactly like the disaster. Telling somebody their warehouse broke when they only need to press a button is nearly as bad as saying nothing when it really did break.

So the list now says which one it is: a filter found nothing, everything is in the archived history (with how many, and the button that brings them back), or the impossible one, which still says to stop and touch nothing. And the nightly job itself now writes a line and emails when it leaves the recent list empty — even though the counts add up, because they do add up in that case and that is exactly why nobody was told.

v12.24 Fix

The number changes when the app says it changed

Save a movement and the app said done — and then took a few more seconds to change the quantity, put the stock back, and move the badge from In Stock to All at Site. Measured with a stopwatch on a real save.

What it was doing was worse than waiting. After a save the app repainted from its own cache — and the cache is the picture from before the movement you just saved. So the figure that appeared "instantly" was the old one. That is worse than a loading skeleton: a skeleton looks like something loading, an old number looks like a number. There was nothing on screen to say it was not true yet.

The fix was not to redo the arithmetic in the browser. That was the obvious route and it was the wrong one: two sets of arithmetic can disagree, and then the figure flickers to a wrong value before correcting itself — worse than waiting, and it breaks the rule this app is built on.

The server already had the after figures. It walks its own stock picture movement by movement to check the save is allowed, so by the time it answers it knows exactly what you have — and it was throwing that away. Now it sends it. There are not two sets of arithmetic: it is the server's, quoted. The full picture is still fetched behind, quietly; this brings forward what you look at, it does not replace the truth.

And the badge comes free. Since v12.17 the status is decided from those numbers rather than stored beside them, so correcting the numbers corrects In Stock → All at Site on its own. That was the point of unifying the statuses, and this is the first place it pays.

v12.23 New

The spreadsheet behind the app looks like part of the product

Open the file itself and three tabs were properly designed — the welcome page and the two legal documents — while the other seventeen were raw spreadsheet dumps with a bold header row. The contrast did more harm than no styling at all, because it showed we knew how.

The tab colour now tells you whether it is safe to edit by hand, which is the most common support question there is and was written down nowhere. Navy for the pages you read, blue for the data the system lives in, grey for the sheets the app rebuilds, pale grey for the logs that are only ever added to. And each rebuilt sheet carries a note on its first cell: "Rebuilt by Acopio from MASTER_ARCHIVE_V3. Edits here are overwritten — change the movement instead." That one line saves somebody an afternoon of correcting a total that keeps coming back.

Headers are navy with white type and a frozen first row. The rebuilt sheets and the logs are protected — as a warning, not a lock, deliberately: the app writes to them with the same account, so a real lock would be a new way for it to fail at night. The warning stops a person and does not stop the code.

Nothing you have set is overwritten. Column widths, tab order and cell formats are only applied to sheets that are still empty — a template, or a fresh install. On a system with months of data the app sets the colours, the headers and the notes and leaves everything else exactly as you arranged it, and says which sheets it left alone rather than staying quiet about it.

It runs when the sheets are created, when a master template is prepared, and as part of Check this installation — so a system restored from a backup gets its look back, since a copy brings the data and loses the formatting.

v12.22 Fix

Renaming a material takes everything of its own with it

A material is known by its category and its name — and five other things were filed under that name: how many units come in a box, its average cost, anything reserved of it, its minimum, and whether it is watched for low stock. Renaming a material, moving it to another category or merging it rewrote every movement and left all five behind.

That does two kinds of damage, and the second is the worse one. You are left with an orphan line pointing at a material that no longer exists — visible, at least. But the renamed material has quietly lost its figures. The next delivery by the box does not get divided, so the cost per unit comes out twelve times too high and blends into that material's average from then on. Nothing says a word.

All five now move with the material, and what moved is written into the audit log. When you merge two materials that both have figures, the survivor keeps its own — the one people will go on using should not quietly take on the numbers of the one that disappeared — and the dropped ones are counted in the log rather than vanishing.

v12.22 Fix

The Stock Dashboard columns stop moving when you filter

Every filter shifted the whole table. Measured across the categories: Stock started 114px further along in Window than in Shower, and Location 97px, without changing anything but the filter.

It was not the filter — it was the contents. The browser shares out the width by what is inside, and what is inside changes completely between categories: Window's names are long and Mirror's are short, Shower's locations read MIRRORS/SHOWERS WAREHOUSE (234) and Window's read A3A (54). So any filter that changes the mixture moved the table.

Each column now declares its own width, the way Movements has since v12.11, and the widths are measured rather than chosen — drawn with the real worst cases and taken from there. Name stays elastic: it is the one that grows with the window, and it absorbs the 200px that Category frees up when you filter by one, so nothing to its right moves at all.

Incoming and Project View are next.

v12.21 Fix

Putting a movement back can no longer break your stock

The warehouse can move on while a movement sits in the trash, and nothing was checking. Saving an exit has always been refused when the material will not cover it. Putting one back from the trash went through no check at all — it just added the row. The same door, with a lock on one side and none on the other.

Here is the sequence, and it needs no bug in deleting and none in restoring. You ship 37 of something. You delete that exit, so the 37 come back. Somebody ships 37 again — perfectly legal, there are 37 there. Then you put the first one back. Now 74 have left a shelf that only ever received 37, and every figure for that material is wrong.

Nothing said a word, and that part has its own cause. The stock arithmetic clamps at zero — a shelf cannot show minus 37 windows — so the impossible state could not even be represented. The extra went into "dispatched" instead, which has no ceiling. The app showed a calm zero and a quietly inflated total.

Two fixes, because it was two failures. Putting a movement back now goes through the same check as saving one, against the same numbers: a restore is never more permissive than a save. When it no longer fits, it says what changed and the movement stays in the trash — nothing is lost, and you decide.

And Check my data has a new kind of finding: Less than nothing, for any material whose movements add up below zero. It counts without the clamp — the only thing in the app that does, which is the whole point of it. It shows what came in, what went out, and the real number, and it offers no button: the fix is either a movement that should not be there or one that was never recorded, and only somebody who knows the warehouse can say which.

v12.20 Fix

Four small things that were each quietly wrong

The warning that cried wolf. Switching from Entry or Exit to Adjust always said "1 material was not carried over" — even with the form completely empty. It was counting the blank card that Entry and Exit always keep open, so it believed a material was there when nothing had been typed. It now counts materials, meaning the ones with a name. It still warns when something really is lost, which is the half that matters: that warning exists because four materials once vanished on a tab switch with nothing said.

The lists were in two different orders. Categories, projects and suppliers came out alphabetical in Settings and in creation order — whatever was typed first, months ago — in the movement form. Sixteen categories in an order nobody can predict means reading all of them to find one. They are now sorted once, when the data arrives, so both lists agree by construction. Locations are deliberately left alone: their order is arranged by hand and that arrangement is the point.

You can add a units-per-box figure without pretending to receive something. Editing one was already possible; adding one was not — the only way in was to start an entry, pick the material, choose Box and answer the question. A catalog fact was hidden behind a movement. Settings → Materials now has + Add a pack. It only accepts a material that already exists, only the four units that hold other units, and never a pack of one — and if the material already has a figure it sends you to Edit, which still asks twice, because changing a figure that already exists changes what future entries cost.

The Qty / Unit heading is centred. It is the only heading in the table that is, and it is not a whim: every other column starts its values where the heading starts, so left-aligning ties them together. This one has no starting point — it has two halves meeting at a seam — so the heading now sits on the seam and stops drifting away from the numbers as they get longer.

v12.20 New

The trash says which movement it is holding

Delete a movement, put it back, delete it again, and the trash showed two lines reading exactly the same thing — same material, same quantity, same type, same rack — with nothing to say whether that was one movement listed twice or two real movements that happen to look alike. Those two answers call for opposite actions, so leaving the question open was the defect.

Each line now carries the movement's own date, its PO, and its ID underneath. The ID is not there to be read — it is there to be compared, against the other line or against the sheet.

v12.19 Fix

The quantity sits on its own row again

In v12.18 the new Qty / Unit column was correct sideways and wrong downwards: the figure floated above the middle of its row while every other cell stayed centred. On a long table that is worse than it sounds — your eye follows a horizontal line, and if one column sits a few pixels high you end up reading the quantity of the row above.

The cause was in the way the two halves were joined. Splitting a cell into two fixed halves is done with a grid, and the grid had been put on the cell itself — which stops a table cell behaving like one, and with it goes the centring that every other cell gets for free. The grid now lives in a wrapper inside the cell, so the cell is an ordinary cell again and the split is unchanged.

The table now has a permanent check for this: it compares the vertical centre of every quantity against another cell in the same row and fails if any of them is off by more than three pixels. It also checks that the grid is on the wrapper and not on the cell, so this exact mistake cannot come back quietly.

v12.18 New

Qty and Unit are one column now

They always belonged together — a quantity without its unit does not mean anything. Now they sit in one column, the figure on the right and the unit on the left, meeting at the same point on every row: 14 │ UNIT, 150 │ BOX, 243 │ BAG. The line is not drawn; it does not need to be.

That alignment comes from a fixed split inside the cell, not from letting each half take what it needs. Left to itself the meeting point would wander from row to row, which is exactly what makes a column of numbers hard to read. The two halves are measured: the widest quantity and the longest unit in the app's vocabulary.

And it closes a hole nobody had noticed. Qty could not be hidden, but Unit could — so it was possible to end up with a table saying +25 without saying twenty-five of what. Joined, the unit can no longer be lost. The pair cannot be hidden or moved either: type, material and quantity are what a movement is.

If you had Unit somewhere else in your own column order, nothing breaks — the saved order is translated, and everything that is not locked keeps the place you gave it.

v12.17 Fix

The status filter knows all four states — and one of the two it had was wrong

A material can be in four states, and the badge on each row has always said so: In Stock, Reserved, All at Site, Zero Stock. The filter offered two of them. Both missing ones are now there.

And the more serious half, which was not visible from outside. "In Stock only" did not mean the same thing as the green badge. It filtered on "is there anything on the shelf", which includes material that is on the shelf but reserved — material you cannot take. So someone filtering to see what they could pick up was shown things they could not pick up. That is the kind of mistake that sends a person to the rack for nothing.

All three had the same cause: the badge was decided in one place and the filter did its own arithmetic in another. Two lists that had to agree, with nothing making them. There is one list now — the badge comes from it, the dropdown is drawn from it, and the filter compares against it. Adding a fifth state is adding one line; there is no second copy to remember.

v12.16 Fix

A three-figure quantity no longer breaks in half

−300 was being drawn as −30 with a lonely 0 underneath, right next to a −200 that fitted fine. Same number of digits, different outcome.

The column was not too narrow — the padding was too wide. 58 pixels minus 12 on each side leaves 34 for the figure, and a four-character quantity needs 37. Four pixels a side instead of twelve leaves 50, and everything up to four figures fits with room to spare. Five figures drop one step in size, the same ladder the reserved strip already uses: shrink only when needed, and only as much as needed.

And the reason one fitted while the other did not: in a normal typeface a 0 and a 2 are narrower than a 3 or a 6, so the same three digits can measure differently. Quantities now use fixed-width figures, which also means a column of numbers lines up by units, tens and hundreds — in a column of numbers that is not decoration, it is what lets you compare them without reading them.

They are right-aligned now too, which is how numbers are read and how they will sit once Qty and Unit become one column.

v12.15 Fix

The label box now respects the boxes you ticked

Tick 🏷 Label on two of the ten materials in an entry and the box used to open with all ten ticked anyway — unless you had ticked at least one, in which case it honoured your choice. So the one answer it ignored was "none", which is the answer that most deserves respecting. Asking a question and then overriding the answer teaches people not to read the question.

Now the ticks are exactly the ones you made. Every material still appears in the list, in case you change your mind. And if you ticked nothing, the box does not open at all — you go straight on with your day. Nothing is lost by forgetting: select any movement in Movements, press Labels, and reprint whatever you need, whenever you need it.

The card counter stops promising nine and showing five

The badge on the corner deck counted everything waiting, while the deck itself only ever holds a handful of cards — printing them all made a staircase that ran off the top of the screen. Both halves were telling the truth and together they lied: the number promised nine things to deal with, with no way to see or reach the other four.

The badge now says both: 5/9, and hovering explains that dealing with one lets the next take its place. When everything fits it shows the plain total, as before. How many cards are drawn has not changed — the staircase was a real problem and this does not reopen it.

v12.14 Fix

The nightly archive job can no longer delete your movement history

On the night of 26 September 2026 the 3am archive job emptied an entire movement history — every row, in both the active archive and the older history. It was found fourteen hours later, by accident. The daily backup runs at 2am, an hour before this job, and had everything; nothing was lost. That one hour of difference is the only reason.

What happened. The job cleared the sheet and then wrote the rows back. The write failed — the sheet was 20 columns wide and a movement row is 23 — and by then the clear had already happened. Sheets created before the cost columns existed are the normal case on an upgrade, not the odd one, and a helper written months ago for exactly this was never being called here.

Three things changed, because it was three failures and not one. The sheets are widened before anything is touched. The job now writes first and clears afterwards, so a failure half way through leaves the rows where they were — that part would have mattered even with the width right, because a quota limit or a timeout between those two lines had the same effect. And the sheet that gains rows is written before the sheet that loses them, so the worst case is a duplicate you can see rather than a hole you cannot.

Two counts now have to agree before a single cell is written. This job only moves rows between two sheets, so the total has to be identical before and after. If it is not, nothing is written at all. A second count runs afterwards against the sheets themselves, because "the arithmetic is right" and "the write landed" are different questions and the second one was nobody's job.

And it says so. The failure went to a log tab nobody reads, and the app carried on showing "No movements match your filters" — the same sentence it shows when a filter matches nothing. The job now emails the admin when it fails or when the counts disagree, and the app itself refuses to be quiet: stock on the shelves with zero movements on record is impossible, and it now says that in plain words instead of showing an empty table.

v12.13 Fix

The eye button in the column editor is reachable again

Open ⚙ Columns and the button that hides a column was missing on the narrow ones, or tucked under the heading next to it. It was literal: a heading in edit mode carries a drag handle, a name field and the eye, and on Qty or Unit all of that lived in 58 pixels. The eye did not wrap — it spilled out and the neighbouring cell covered it. The control for hiding a column was invisible on exactly the columns you would most want to hide.

Headings are now sized by their controls while the editor is open, not by their data. The table gets wider and scrolls sideways for as long as the panel is open, which is the right trade: a button you cannot press is worse than a scrollbar.

The measurement turned up something reading the code would not have: a hidden column draws 🚫 instead of 👁, and 🚫 is two pixels wider. At the first value tried, the five columns that ship hidden spilled by one pixel while the other eleven were fine — so testing with any one column would have come back clean.

Type, Name and Qty stay put

Those three carry a lock in the column editor, and the lock only ever meant "cannot be hidden". They still had a drag handle, and dragging them actually worked — so they looked movable because they were. A movement without its type, its material or its quantity is not a row that says anything, so now they are genuinely fixed: no handle, nothing can be dropped on them, a darker heading so they read as one block, and the lock explains why instead of only stating it.

What this costs, plainly: the material name can no longer be placed before the type and date. That was possible before and some people may have set it up that way; their saved layout is corrected on the next redraw. Every column that is not locked keeps the order its owner gave it.

v12.12 Fix

Showing one more column no longer squeezes the material name to nothing

Show an extra column in Movements — Supplier, say — and the material name could collapse to almost no width at all, printing one letter per line and stretching rows from 83 pixels tall to 428. It took showing a single column to trigger it, and it was there from the moment the columns were given fixed widths.

The cause was one number written by hand. The table had a minimum width of 1,300 pixels, but the widths of all sixteen columns add up to 1,748. A number typed into a stylesheet cannot know how many columns you have chosen to see, and that is a choice each person makes with the eye icon in ⚙ Columns. With Supplier showing, the declared widths came to 1,296 and the material was left with four pixels.

Each column's width now lives beside the column itself instead of in a separate block of styling, and the table works out its own minimum on every redraw: the widths actually on screen, plus a floor for the material name. The floor is measured too — 192 pixels, which is what the longest category badge needs so it does not spill out of its own cell. Below that the table scrolls sideways instead, which is the right answer: sixteen columns do not fit on a narrow screen, and a scrollbar beats text you cannot read.

A check now measures the material's width at two window sizes, in four column layouts, through both of the paths that draw the header. Putting the old fixed number back fails it seventeen times.

v12.11 Fix

The movements table stops jumping when you change the filter

Pick a different category and every column used to slide sideways. The Project column started 476 pixels from the left on one filter, 1,318 on another and 415 on a third — the same column, nearly 900 pixels apart, because the table sized itself from whatever rows happened to be showing. You could not learn where to look.

Every column now has its own width and keeps it, whatever you filter to. One column stays flexible — the material name, because it is the one that varies most and the one you read — and it absorbs the space when you hide or show another column. Nothing else moves, at all.

The widths are measured, not guessed: the table was drawn with real warehouse data under seven different filters and each column was given the width it actually reached. Two came back too narrow the first time and were corrected — the TRANSFER badge and the longest email were being cut off. A check now re-measures every column under every filter and fails if a single one moves by one pixel.

v12.10 Fix

The reserved strip says which way it opens

The reserved list on the Warehouse Map said click to hide even when it was already hidden — so the one hint telling you what the click would do was wrong half the time. It now says click to show when it is folded and click to hide when it is open.

The heading was rearranged while we were in there: the three numbers run together —441 units · 8 reservations · in 8 racks— instead of being split by a sentence, and that sentence moved into the small i beside them. It is something you read once; it was taking up half the line every day. What stays in plain sight is the only part you can act on: how many reservations are holding nothing.

v12.09 Fix

Every window is now the size of what is inside it

Every window in the app was the same height — 82% of your screen — whether it held four fields or twenty. A short one, like Add User, left a huge empty space below the last field. A long one, like Edit Movement, scrolled even on a big screen, because the limit had nothing to do with the content.

One line caused both, and it had been written that way on purpose, so that every window would match. The rule now is the one that actually helps: a window is as tall as its contents, and never taller than your screen. Add User went from 700 pixels to 277. Edit Movement now opens complete, with no scrolling, on a normal screen.

Edit Movement is also as wide as the window the movement was created in — it was 120 pixels narrower, which is why the same fields stacked taller and had to be scrolled. A check now opens every window in the app and measures it against its own contents, so no window can quietly go back to filling the screen for no reason.

v12.08 Fix

Your account menu opens in front, and the app behind it holds still

On a wide, short screen — a tablet held sideways — opening the account menu put it behind the notification cards in the corner. The cards stayed live underneath it too: moving the mouse over them opened the pile, and the page behind scrolled.

All three are fixed, and they are one thing now: a menu you opened on purpose takes priority over anything that appears on its own. It does not take priority over a window you opened — a form you are filling in still covers it, which is the right way round.

Underneath that, every floating layer in the app — the cards, the bell, the rack drawer, windows, confirmations, document previews, alerts — now takes its position from a single ordered list instead of each carrying its own number. That is what made this bug possible to miss: the menu declared a number higher than the cards' and still lost, because a number like that only competes inside its own group. A check now measures which one is actually in front, rather than comparing the numbers.

v12.07 Privacy

The setup check-in now sends exactly what the Privacy Policy says it sends

The setup check-in is the one thing your installation sends without you asking: if no movement has been recorded three and seven days after setup, it emails your support address so somebody reaches out instead of letting you quietly give up. Our Privacy Policy describes it field by field and promises exactly four things — your company name, your administrator's email, how many users are registered, and how many days it has been — and nothing else.

It was sending a fifth: a link to your installation. Nothing left your control that shouldn't have — the email only ever goes to the support address configured in your own installation — but the policy was narrower than the code, and the policy is the promise. The link is gone. Four things, as written.

A check now runs on every release that reads the email the code actually produces and fails if it carries anything the policy does not list. The two checks that existed could not have caught this: one compares the two copies of the legal text against each other, the other verifies when the email fires and how often. Neither looked inside it.

v12.06 New

Incoming: a search box, and a warning before you enter the same delivery twice

Expected deliveries were the one long list in the app without a search box. There is one now, in the same place and working the same way as the one in Movements — type and the list narrows. It looks through the name, PO number, supplier, PM and notes.

And before a delivery is saved, the app checks whether it is already on the list. The PO number decides: a supplier issues a new PO for a new order, so the same material under the same PO twice is almost always the same delivery written down twice — and two different POs are two real orders, which the app now knows not to complain about. When there is no PO to compare, it falls back to the same material, from the same supplier, within a week of the same date, and says so more softly.

The warning shows both deliveries side by side, field by field, with whatever differs marked — so the comparison is the review, rather than sending you off to find the other one. From there you can open the one that already exists and edit it instead (no second delivery is created), decide they really are different and add it anyway, or cancel. The same material from two different suppliers is never flagged; it is two real deliveries.

Underneath both of those: names in Incoming are now tidied the way a movement's name always has been — uppercase, extra spaces collapsed. Deliveries already on the list keep what was typed until you edit them, and the search and the duplicate check compare regardless of case, so nothing older is missed.

v12.05 Fix

Reservations: a list that folds away, columns that hold still, and a way to clear the dead ones

The reserved list on the Warehouse Map now starts folded. The heading still says everything you read at a glance — how many units are held, across how many racks, how many hold nothing — and the list itself opens when you click it. With a hundred reservations that heading is the whole point. Whether you leave it open or closed is remembered.

Its columns no longer move. Every column is a fixed width, so adding a reservation with a longer name doesn't shift every other row sideways. Text that doesn't fit steps down one or two sizes and, if it still doesn't fit, wraps to a second line — it is never cut off with dots, because the end of a rack name is where the letter and number that tell it apart live.

And a reservation that holds nothing can now be released. Until now the only Release button lived inside a rack's drawer, on the material's row — and a reservation holding nothing has no row there, so the app warned you about it and gave you no way to act. Every row on the list now has its own Release, and when several hold nothing the heading offers to clear them all at once, naming each one first. Releasing these changes no quantity anywhere; it only tidies the list.

v12.04 New

Labels: only the ones you want, one per piece, and printable later

Receiving ten materials used to open ten label previews, whether you wanted labels or not. Now each material line in an entry has its own 🏷 Label box. Tick the ones you want and only those come up. Tick none and nothing changes from before — everything is offered, the way it always was.

Five door screens going on the same shelf are five pieces, not one bundle of five. Any row with more than one unit now offers one label per unit: five labels that each say a quantity of 1 and 1 of 5, 2 of 5, so a piece carries its own tag wherever it ends up. The ×N copies box still works on top of that.

And labels are no longer tied to the moment you receive. Select any movements in the movements table and press 🏷 Labels to print them again — with that movement's own date, not today's, so a label for something that arrived on Tuesday still says Tuesday.

The label itself was rearranged: location on top, quantity underneath, each across the full width with its heading above the value. Short names like B1B or P4C are exactly the size they were. Long ones — WINDOW WAREHOUSE — used to wrap onto two lines; now they step down just enough to fit on one.

v12.03 Fix

The evening report is in English, and says who did what

The evening email was going out in Spanish. Not a setting in your inbox — the app was written that way, and it had been for months in the one place nobody looks at: a report that sends itself at eight in the evening. It is in English now, and a check runs on every release that fails if any text the app shows or sends is not.

The "Who" column was answering two different questions depending on the row: normally the person who received or took the material, but if that field was empty it quietly showed the email of whoever recorded the movement instead — a different person. It is two columns now. Received by / Taken by is the person on the floor, and it says nothing rather than guessing. User is who recorded it, with their name above their email.

And the number beside each heading now says what it counts: "3 movements" instead of "(3)".

v12.02 Fix

A zero was being drawn as a blank

A quantity of zero showed up as an empty gap instead of a 0 — anywhere in the app that printed a number: rack contents, delivery cards, the storage-space meter. A gap looks like data that went missing; a 0 is an answer.

Jose found it on a reservation that was holding nothing. That is a real situation — the material left the rack, or its movements were deleted — and the app now says so in plain words instead of showing an empty space, tells you where the material went if it is still somewhere, and points out that the reservation can be released. Releasing one of those changes no numbers; it just tidies the list.

v12.01 Fix

Locking a material is now called reserving it

The app had two half-features that meant the same thing. "Lock a material" worked — it holds material at a rack so it cannot leave the warehouse — but it was called locking. "Reservations" had the friendlier name and no way to create one: the screen for it never existed, which is why Reserved on the dashboard always read zero.

They are one thing now, and it is called reserving. Nothing you had changes: what you locked is what is reserved. The difference is that the dashboard finally shows it, and you can see every reservation in the warehouse from three places — a card on the Stock Dashboard, a strip above the Warehouse Map that takes you to the rack, and a mark on each location in Settings.

A reservation holds whatever is in that rack, so the number moves with the stock by itself. And the check that refuses to take out reserved material was already written — it had simply never had anything to count.

v12.00 Fix

The screen changes the moment the save lands

Marking a delivery arrived took about eleven seconds to show up, long after the save had already gone through. Two things were wrong in the same line: nothing updated the copy the screen draws from, and the refresh that followed read from the cache — so it painted the old answer a second time and only corrected itself once a full reload finished.

Now the screen updates as the save lands. Nothing is drawn before the server agrees; it is drawn the instant it does. The same fix covers attaching a document to a movement and editing a movement's details. One exception worth knowing: editing a quantity updates that row immediately, but the material's total still waits for the refresh — only the server can work that out.

v11.99 Fix

The corner pile counts what it holds, and opens on the newest card

The badge on the pile was counting two of the three kinds of card it holds. Deliveries that arrived without their entry recorded were left out, so the badge could read 4 with six cards underneath — and it disagreed with the bell in the top bar, which had been counting all three all along.

The pile also opened at the top, showing the oldest card, while the newest sits at the bottom. It now opens at the bottom and stays there while it fans out. Scroll up and it lets go — reading the older ones is what the scrollbar is for.

v11.98 New

Manage Users works like the movements table

Seven columns became four: the name sits over the email, the role over the date it was added. The Add User form no longer takes up the top of the window — it is a button that opens it when you need it.

Edit and Deactivate moved off the rows and onto a bar, and switch on when you tick somebody. Edit needs exactly one; deactivating works on several at once. Your own row cannot be ticked, which is the same rule the server already enforced, said before you press instead of after.

And users can be let back in from the app. Until now the window told you to go and edit the spreadsheet by hand; the button reads Reactivate when everything you ticked is inactive, and switches off on a mixed selection rather than guessing which half you meant.

v11.98 New

Two smaller things

The wall of orange cards now stops at two thirds of the window instead of growing over whatever you were looking at. If there are more cards than fit, the pile scrolls inside its own space — none is hidden.

And in the warehouse map, tapping a material in a rack puts its name in the header while its options are open. On a phone your finger covers the row, and the header was the only place left to tell you what you are about to press Exit on.

v11.97 Fixed

The last movement of a batch reaches the other account

Deleting thirteen movements from one account, the last one never disappeared from the other. Not slowly — never. It stayed on screen for as long as anyone cared to wait.

The app asks the server "has anything changed?" by comparing a stamp. The load that fetches your data was reading the movements first and that stamp last, so a deletion landing between those two reads left a browser holding the old list together with the new stamp. From then on every check said "nothing changed" and it never asked again.

The stamp is now read before the data. When the same race happens the browser ends up with an old stamp instead, notices the difference on its next check, and asks once more. The cost is one extra fetch; what it replaces is a screen that was wrong for good.

v11.96 Fixed

Deleting several at once stops being slow

An earlier version made a run of changes rebuild the stock tables twice instead of once per change. It worked — for saves. Deletes have had their own queue since a while back and never went through that code, so deleting thirteen movements still paid for thirteen full rebuilds.

Timed off a recording: three done at 1:20, six at 2:00, ten at 2:40. About twelve seconds each. That is also most of what looked like "the other account is slow to update" — the other window cannot show the truth until the work is finished, and the work was taking two and a half minutes.

Thirteen deletes now cost two rebuilds. Two and not one, and it is worth saying why: the first cannot wait, because when it leaves the other twelve have not been queued yet, and the last refreshes on its own because nothing follows it.

v11.95 Fixed

Ticked rows stop clearing themselves

Ticking several movements to delete them, the ticks would sometimes all vanish at once. The table was folding back to its first page every time fresh data arrived — which, with two people working, is every twenty seconds — so anything ticked past the first page stopped being drawn.

The half that did not show was the one worth fixing: the selection itself survived, so Delete stayed armed on rows that were no longer on screen. Ticking eight, seeing none, and having Delete take eight is worse than losing the selection, because losing it is at least visible.

Fresh data no longer changes which page you are on — that still happens when you switch screens or change a filter, where it means something. And the rule is now simple: what is ticked is what is on screen. A row that leaves the view, for any reason, leaves the selection with it.

v11.95 New

The name card is always the same size

Hovering a name in the User column opened a card whose width followed the length of the email — and because a wider card has to shift further to stay inside the window, it also appeared in a different place each time. One cause, both complaints.

Fixed width now, with the name and email on one line. Anything too long is shortened and can still be read in full by hovering it.

v11.94 New

Everything that disappears is seen to disappear

A deleted movement has shrunk away instead of blinking out since an earlier version, and so has one put back from the trash. Everywhere else a row still vanished between one frame and the next, which reads as a glitch rather than as something that happened.

Now the same thing happens when emptying the trash, merging locations, catalog values, duplicates or materials, deleting a location, and removing a catalog value or a contact. On a merge you see exactly which rows left, which is the part that was hard to follow when three go at once.

Anyone whose system asks for reduced motion gets none of it, and the work still happens immediately.

v11.93 New

The notices that stayed are shorter now

Ten notices confirm something you just pressed yourself — saving a movement, editing one, deleting an expected delivery, loading older history. Those stay, because the confirmation is worth having, but they now clear in two seconds instead of three and a half. Long enough to read five words; short enough that three in a row stop stacking over the table.

Everything else keeps its timing. Errors and warnings have to be read. So do the two notices that look like confirmations and are not: the one that tells you what is still missing after starting an entry from a delivery, and the one that explains why a count adjustment changed nothing.

v11.92 New

Fewer notices, and a progress line where they were piling up

Ten notices were removed outright — merging things, saving the company details, adding or removing a contact, renaming columns. In every one of those the screen already shows the result the moment it happens, so the notice was repeating something you were looking at.

Five more moved to the progress line: emptying the trash, importing a file, uploading a rack photo, adding several suppliers or projects at once, and marking several deliveries as arrived. These are the ones that take a while or repeat, and the line now spins while they run instead of leaving the screen still until the end.

Two that were on the list to remove were kept, and it is worth saying why: saving the warehouse map no longer says "saved", but it still names any location that was deleted — that is the one thing on that screen you cannot undo by dragging back. And a rack drawer that empties out still says so, because it closes itself, and closing on its own without a word looks like a fault.

Errors and warnings were not touched. Those you have to read.

v11.92 New

The Email button opens Gmail

It used to hand off to whatever mail app the computer is set to open. That is a per-computer setting, and one wrong click on "Always" leaves the button stuck on an app you never use, with no way back from inside the app.

Now it opens a new Gmail message with the person already filled in — the same as the Meet and Schedule buttons beside it, which already went straight to Google. If your desktop mail app is linked to Gmail, the message still lands in the same inbox.

v11.91 New

Putting movements back counts them too

Restoring five movements from the trash used to raise five notices over the Settings window you were working in — and the rows were already leaving the list on their own, so the notices said nothing you could not see.

Now the same progress line counts them and ends with how many went back. It also sits on the left instead of the right, where it was covering the cards on a wide screen, stays six seconds instead of under three, and closes on a click.

v11.90 Fixed

Deleted rows stop coming back

Deleting several movements at once looked wrong, and in one case was wrong. The rows left the screen right away, as they should. Then, a few seconds later, they came back — one by one, greyed out, to leave all over again. Some came back not greyed out at all, and could be selected and deleted a second time, which then failed with "already deleted".

Nothing was ever deleted twice: the app recognises each movement by its own name and refuses the second attempt, saying who deleted it and when. But the screen was showing rows that no longer existed, which is its own kind of wrong.

The cause was a refresh that arrived mid-run carrying a picture of the warehouse taken before the run finished. A refresh no longer paints while a deletion is still in the air.

v11.90 New

One progress line instead of a stack of notices

Deleting thirteen movements used to put thirteen notices on the screen, one on top of another, over the very table you were looking at — and none of them said the one thing worth knowing while you wait: how much is left.

Now a single line in the corner counts them off — "4 of 13" — and ends in a green check. A deletion that fails still gets its own notice, because that one has to be read.

v11.89 Fixed

A run of changes stops rebuilding everything, every time

After each change — deleting a movement, applying a "Check my data" fix, merging two things — the app rebuilt its stock tables from scratch. That is about nine round trips to the spreadsheet, and doing ten changes in a row paid it ten times: roughly forty seconds of work that only needed a few.

Now a run of changes rebuilds twice instead of ten times — once at the start, because nothing is behind it yet, and once at the end. A single change on its own behaves exactly as before.

Nothing can be oversold because of this: whether there is enough stock to take something out is checked against the movement history, not against the tables being deferred. And if a run is cut short — the window closes, the connection drops — the next person to open the app brings the tables up to date before showing any number.

v11.88 Safer

The checks stop breaking for reasons that are not bugs

Nothing in the app changes. This is about the checks that run before anything reaches you.

Six times in one week they went red without anything being wrong: each check carried its own hand-written list of which pieces of the app it needed, and those lists went stale every time a piece grew a new dependency. They now work it out from the source instead. A check that goes red on its own teaches people to stop reading the checks, and the day a real one goes red it gets the same shrug.

v11.87 Fixed

The apostrophe again — this time when editing

The previous version fixed saving a movement. Editing one still added an apostrophe, and only to the fields you actually changed, which is why creating an entry looked fine and editing one did not.

It matters more on this path than on the other: when the category or name changes, the material's identity is rebuilt from them, so an apostrophe there would have made the row stop matching its own material.

Third time the same mistake came out through a different door, so it is no longer looked for by hand — a check now counts them. The rule it enforces is one line: a function that writes the row through the text guard does not also guard its own values. Quote once, at the edge.

v11.86 Fixed

An apostrophe in front of everything just saved

Movements saved on the previous version stored an apostrophe in front of every text value — 'WINDOW, 'UNIT, '16598. Two protections were being applied where one was needed, and the spreadsheet kept the second one inside the data.

It was not only ugly: a material's identity is built from its category and name, so a row saved that way did not match the same material saved before it, and its stock would have split in two. Saving is corrected, and the check that exists for this now runs the save end to end instead of reading the code — which is exactly why it had let this through.

Also: the exit window no longer opens just to say no. Choosing a material that is locked now answers with the reason before the form, instead of opening it and showing "locked" in small print. And the supplier on a delivery card is now a badge, in one colour for every supplier, instead of text after a dash.

v11.85 Safer

Two people at once can no longer overwrite each other's deliveries

Editing an expected delivery and deleting one were not coordinated. If somebody deleted a delivery while you had another one open, your save could land on a different delivery than the one you edited — and the colleague whose delivery you overwrote would never find out.

The three delivery actions now take turns properly, and the screens wait their turn and retry instead of showing an error. The same lesson was applied to movements in September; this is where it had not been wired in.

Separately, removing a user through the old admin path deleted a whole row of the settings sheet — where each column is a different list — so it could have taken a project, a category, a supplier and a location with it. No button in the app reaches that path, so it never happened; it now clears just the user's own two cells.

v11.84 Fixed

Error messages never show the app's own wiring

When something goes wrong the server sometimes marks the message for the app to read — text like SYSTEM_BUSY| in front of the real sentence. Most screens stripped that marker before showing it. Six did not, including the entry form, whose twin the exit form already did.

None of them could show it yet — the actions those screens call do not produce marked errors today — but the mismatch was the trap, not the symptom. All fifteen error messages now go through the same filter, and a new one written without it fails the checks on purpose, so the decision happens while somebody is looking.

v11.83 Safer

The duplicate-entry bug now has a test that would have caught it

In September a delivery card could be settled and come back, letting the same movement be saved over and over. It was fixed in v11.70 — but there had been a test passing over that bug the whole time, because it checked that the fix was WRITTEN rather than that it RAN.

That save path is now exercised for real: the entry form runs against a stand-in browser and server, and the checks are about what actually happened — the card is settled only after the server accepted the movement, a failed save settles nothing, and the label window comes before "did these arrive too?" instead of both at once. Nothing about how the app behaves changes; what changes is that this class of bug cannot pass unnoticed again.

v11.82 New

Deliveries now say who they are coming from

Every delivery card — in the morning window and on the week's day cards — now reads material - supplier on one line. When the material name is too long it shortens with an ellipsis so the supplier still fits beside it rather than dropping to a second line, and a very long supplier name shortens the same way. Whichever one is actually too long is the one that gives way; a short supplier is never clipped to make room. Hovering either one shows it in full, because shortening on screen must never mean losing what was typed. A delivery with no supplier recorded simply shows the material, with no dangling dash.

v11.81 Safer

Renaming one material can no longer erase another

Renaming, merging or re-categorising anything rewrites a whole column of the archive — including every row you did not touch. On the way through, values that look like a date to a spreadsheet were being converted into one, and a material called "07-6329" would quietly stop counting toward stock. Seven everyday actions did this, and the material that vanished did not have to be the one you were editing.

Text now stays text through those rewrites, and through the packs sheet, the movement-id repair and the diagnostics dump, which had the same gap.

Underneath, two guards became one. The older one only protected against spreadsheet formulas, not against dates, and having both meant every new piece of code was a choice between the strong one and the weak one — forty-six places had picked the weak one. The weak one is gone. One visible consequence: a purchase order typed as "12345" is now stored as text, so it sits on the left of the cell rather than the right. A PO is a label, not a quantity.

This protects what gets written from now on. Values already converted in the past cannot be recovered from the cell — only from other rows that still hold them.

v11.80 Fixed

A locked material now says so where you are looking

Locking a material to a rack held it, but the padlock only showed in two places: the rack drawer and the exit form. Both of them are places you reach after you have already decided to move the material. On the stock table — the screen most people have open — there was nothing.

Now the Location column carries a padlock, and hovering it says which rack, who locked it, why, and what you can still do with it: whether it can be moved to other racks or only to particular ones. A lock on a rack the material has since left is not shown, because it no longer stops anything.

And locking or unlocking now reaches everyone else with the app open, in seconds, instead of waiting for them to reload. Previously a colleague could try to take out material that had been locked minutes earlier and only find out from the error.

v11.79 New

Labels, printed as you receive

Save an entry and Acopio offers to print a 4"×6" label for what you just took in — material, quantity, rack and PO, each one big enough to read from across the warehouse, with space left for a QR or barcode.

One label per rack, not per material: if forty-four units went to two racks, that is two bundles in two places and each one gets its own count. Tick the ones that need a label — not everything does — and set how many copies of each; one is the default.

Nothing else prints. The app disappears for the print and only the labels go to the roll.

v11.78 New

"Did these arrive too?"

A truck turns up with four things on it and you have the paperwork in your hand, so you enter all four at once. Until now only the one delivery you came in through stopped being "expected" — the other three sat there waiting for material that was already on your shelves.

Now, once the entry is saved, Acopio shows the expected deliveries that match what you just entered and offers to mark them arrived. Everything is ticked; untick anything that has not actually turned up.

It asks rather than deciding, and that is deliberate. Nothing links a delivery to a movement, so the match is worked out from the material, the date and the PO — and marking a delivery received while it is still on the road means you stop waiting for something that never came. A tickbox costs a second.

v11.77 Fixed

The bell said 3 and showed 2 — and other screens now notice

The notification count included the orange "arrived, entry not made" reminders. The list you get when you open the bell did not, so the number and the list disagreed. They are in the list now, at the top: material sitting in the warehouse that your stock figures know nothing about is the most urgent thing that list can hold.

And marking a delivery arrived now reaches other people. Expected deliveries were left out of the signal that tells open sessions something changed — so a second window, or a colleague, kept showing the delivery as pending until they reloaded by hand.

v11.76 Fixed

A PO on an expected delivery stayed empty

Type 08-4885 into an expected delivery's PO field, save, reopen it, and the field was blank. Type it again, same thing. Spreadsheets read 08-4885 as a date — month 08, year 4885 — and store that instead of your text; reading it back, the app refused to show a garbled date where a PO belongs, so it showed nothing.

Expected deliveries now store what you typed, exactly as movements already did. Existing rows whose PO was eaten cannot be recovered from the cell — the text is not in there any more — so those need typing in once more, and this time they stay.

Also: the week's window now updates while you work in it. Marking a delivery arrived from the popup and recording its entry left the popup showing the old state until you closed and reopened it — it only refreshed if the Incoming tab happened to be the one behind it.

v11.75 New

A deleted row leaves the moment you press the bin

It used to sit there, dimmed, until the server came back — which on a real warehouse file is about four seconds, because every delete rebuilds your stock totals from the whole archive. Now the row shrinks away as you press, and the rows below it move up. If the server refuses, the row comes back to exactly where it was and says so.

The shrinking is the part that matters. A row that only fades leaves its gap behind, and the rows below jump when the gap finally closes. Shrinking, they rise — and that movement is what reads as "this is happening" instead of as a flicker.

Restoring from the trash uses the same motion. The other places a row disappears — merges, emptying the trash, removing a catalogue value — are next; several of them redraw their whole list today, and a list that redraws cannot show a row leaving.

v11.74 Improved

One fewer wait on every save

Every save and every delete rebuilds your stock totals, and part of that was reading the older-movements sheet even when it was empty. On a real warehouse file that read cost about half a second of doing nothing, every time.

It is skipped when there is nothing there. Installations whose older movements sheet has grown are unaffected — it still gets read, because those rows count.

The measurement behind this is worth knowing if you ever wonder why Acopio pauses: reading twenty-four thousand cells costs the same as reading none. What a spreadsheet charges for is the trip, not the data.

v11.73 New

The User column is a person now

It used to be an email address. It now shows the person's name, with the address in grey underneath. Hover it and a small card offers to email them, open a Google Meet, or put a calendar invite in front of them with their address already filled in.

Anyone whose name is not filled in under Manage Users still shows as an address — the same as before. Names are never guessed from the address itself: "j.castro@" is not proof that anybody is called J Castro, and a made-up name on the record of who moved stock is worse than an address.

The name is looked up when the row is drawn, not copied into it. Fix a spelling in Manage Users and every row that person ever saved says the new name — including the ones from two years ago.

v11.72 Improved

The week's window now shows the whole week

Two things were quietly falling out of it. A delivery that arrived on Monday and still has no entry was gone by Tuesday — not among what is expected, because it arrived, and not among today's, because it is not today. And a delivery that was due on Monday and nobody ever marked left the same way, for the same reason: both lists only looked forward from today.

Now everything this week expects or has received is in the window. What was due earlier and never marked sits at the top in amber; what has arrived shows with the day it came and where it was put. Neither one changes when the window closes — that still happens when nothing ahead is left to deal with.

v11.71 Fixed

"Nothing arriving today", on a morning when three things arrived

The morning window listed what was still expected, and a delivery you marked as arrived did not move down the list — it left it. So the window could tell you nothing was arriving today while three deliveries had turned up before nine.

Today's arrivals now have their own block, and each one says where it was put. If the entry has not been made yet, it says nothing — and that blank is the point: it is how you spot material sitting in the warehouse that the stock numbers know nothing about.

Where it was put is worked out by matching the delivery against the entries, since nothing links the two directly. It errs on the side of silence: a wrong rack would tell you something is recorded when it is not, and that is the one mistake this is meant to catch.

v11.70 Fixed

The reminder card now clears itself when you make the entry

Mark a delivery as arrived and Acopio leaves an orange card in the corner: arrived, entry not made. Saving that entry is supposed to take the card away. On the multi-material entry screen — which is the one every entry actually opens — it did not. The card stayed, and its button re-opened the same form filled in with the same delivery, so the same receipt could be saved again, and again.

That is stock that was never received appearing in the warehouse, which is why this went ahead of everything else. If you have duplicate entries from a card you pressed twice, delete the extra ones from Movements — the numbers correct themselves once the row is gone.

v11.69 New

Five stock numbers become one bar

In Warehouse, Used, Available, Wasted and Reserved were five separate columns your eye had to add up before you understood a material. They are now one bar: what you can take, what is spoken for, what has gone out to a job, and what was written off — each a share of everything that material is accounted for today.

The number stays in front, because in a warehouse the question is "have I got enough for forty?", and a proportion does not answer that. Hover the bar for the exact figures, and for who reserved what and for which job.

One thing worth knowing about the total: it is what the material is accounted for today, not everything ever received. A count correction moves stock without anything arriving or leaving, so "received" would stop adding up the first time anyone adjusts a shelf — and a bar whose pieces do not reach the end is worse than no bar.

v11.68 New

Pick a category and its column steps aside

With the dashboard filtered to one category, every row in the Category column said the same thing. That is not information, it is wasted space — so the column now steps aside and the category is named once, in the heading above the list. On a narrow screen that is about 130 pixels less to scroll sideways.

It comes back on its own the moment you go back to All Categories, and your own column order is untouched. Open the column editor and it is there as usual: you cannot rearrange what is not on screen.

v11.67 Fix

The dashboard basket shows today's numbers

Ticking materials on the Stock Dashboard kept a copy of each row as it looked at that moment. If somebody else saved a movement in the meantime, Exit Selected opened the form with the older quantities — the basket looked perfectly fine and was no longer true.

It now keeps only which materials you picked and reads their current figures when you use them. A material that stops existing while your basket is open — merged into another, renamed — drops out of the basket instead of turning up with numbers from before.

v11.66 Fix

Putting several movements back at once

Pressing Put back on a run of deleted movements looked as if it had forgotten them: the moment the first one finished, every other button reset itself as though it had never been pressed. The work was always going ahead — the list was being redrawn from scratch each time and losing what was still in flight. Rows now stay marked as on their way until they are actually back.

They also go back one at a time and ask for a single refresh at the end, instead of one full read of the warehouse per movement.

v11.66 Safer

Columns added after you installed finally get their names

An installation only ever wrote its column headings once, when it was first set up. Anything added in a later version — the two cost columns, the movement ID — was written by position, and its heading stayed blank. A column with no name looks like leftover junk, and eventually somebody clears it out.

Re-running Setup now fills in the missing names. A heading that already has text is never touched, whatever it says: nothing in the app reads those names, so renaming yours would be a change nobody asked for.

v11.65 Fix

The help bubbles appear where you are looking

Every small i in Settings put its explanation a couple of centimetres below its icon and drew it behind the boxes around it. The cause was one line: the heading of each box is lifted onto its border line with a CSS transform, and a transform quietly becomes the frame of reference for anything positioned inside it.

Rather than remove that lift, the bubble now lives outside every box entirely. Nothing on the page can clip it, shift it or draw over it — not this box, and not whatever gets styled next year. It also fits itself into short windows instead of running off the bottom, and it gets out of the way when you scroll.

v11.65 Fix

Check my data remembers what it found

Closing Settings and opening it again showed an empty box, as if the scan had never run. The findings were never lost — the panel simply rebuilt itself without drawing them. They come back now, without re-scanning.

And applying a run of fixes asks for one refresh at the end instead of one per fix. Twenty fixes used to mean twenty full reads of the whole warehouse.

v11.64 Fix

"System busy" stops looking like something you broke

When two people save at the same moment, one of them waits. That is normal and the system has always handled it — but it used to give up after about thirteen seconds and then print its own internal note on the screen, marker and reference code included. It now waits about three times longer, quietly, with the button simply saying Waiting….

Every screen that writes goes through one queue now, so your own two clicks no longer collide with each other. Six of them previously had no waiting at all — including Check my data, where applying a fix would just fail if anyone else happened to be saving.

And when something genuinely cannot be done, the message says what is actually wrong instead of repeating "busy". Internal markers and the support reference no longer appear on screen; the reference is still recorded, where support can find it.

v11.64 Fix

The tick on a movement clears when you are done with it

Selecting a movement, editing it and saving used to leave the row still ticked, and so did switching to another tab and back. The selection now clears when you save an edit and when you leave the screen — it only survives while you are still on the same page looking at the same list.

v11.63 Fix

A PO number written 07-6329 stays 07-6329

Google Sheets reads what it is given. Handed the text 07-6329 it decided that meant July, year 6329, stored a date, and threw the purchase order away. The number that came back afterwards was one nobody had ever typed.

It showed up when deleting a movement and putting it back from the trash, which is the path that copies a row between sheets. But the same line of code sits in six places — saving, editing, the trash, the restore, and both halves of the nightly archive rotation — plus the stock summaries and the catalog lists. All of them now write text as text.

Two consequences worth knowing. A purchase order typed as digits is stored as text from now on, so it sits on the left of the cell instead of the right: a PO is a label, not a quantity. And rows written before this version are not repaired by it — a value already converted has lost the original in the cell, and the only honest source for it is the other rows that still carry it.

v11.62 New

Several locations at once, and an empty one can be deleted

Setting up a rack no longer means six trips through the same box: type A1A, A1B, A1C and you get three. Names that already exist are skipped and named, rather than cancelling the whole thing.

A location with nothing stored in it can now be deleted outright — a bin appears next to it. Past movements are unaffected: each one keeps the location name it was recorded with, the same rule renaming already follows. A location that still holds material, has a lock on it, or is named as a lock's allowed destination keeps only the archive button, and hovering it says which of the three is holding it.

Under that: the server now enforces the rule instead of trusting the screen. This page has always sent the whole list back, so a location could in principle be dropped without anyone checking whether it was empty. Now it is checked where it cannot be skipped.

v11.61 New

Movements fits on the screen: four columns became two

Type now sits above the date in one column, and the category above the material name in another. Same information, two fewer columns, and the table stops running off the side of the screen — especially with the column editor open, which used to widen everything.

The trade, which is worth knowing: those four can no longer be reordered separately, because they are no longer four. Whatever order you had set is carried over — the merged column keeps the place the first of its pair had.

v11.60 New

Unlocking a material tells you who locked it, and why

Before you unlock something, the confirmation now shows who locked it, when, the reason they gave in their own words, and where it was allowed to be moved to. All of that was already recorded — it just lived in a tooltip on a small icon, which shows it to whoever walks past instead of to the person about to make the decision.

Two messages that were unhelpful also got fixed: being refused for a missing permission now says what you were trying to do, who to ask, and where they turn it on; and the arrivals window no longer says "Good morning" at eight in the evening.

v11.59 New

Tick the movements you want, then act on them

Movements has checkboxes now, the same as the Stock Dashboard, with a select-all in the header that ticks what you can currently see — not the whole archive. Edit and Delete sit on the same line as Columns, greyed out until you tick something. Edit works on one movement at a time and says so; Delete works on as many as you like and sends them all to the trash.

Nothing on that row moves any more. The buttons are always there, the long column help moved into the ⓘ, and the actions column stopped appearing and disappearing — those three were what made the table jump every time you pressed something. Changing a filter clears your ticks, so you can never delete rows you can no longer see.

v11.58 Fixed

Deleting several movements at once no longer says "System busy"

Pressing four bins in a row asked the server for four full stock rebuilds at the same moment, and the last ones came back busy. They now go one at a time, in the order you pressed them: a row waiting its turn is dimmed, it retries by itself if somebody else is saving, and it only speaks up if it genuinely could not get through.

v11.58 New

Empty the trash, and Settings stops re-fetching what hasn't changed

The trash now has its own Empty the trash button, inside the box it empties. It is the one action in the app that really cannot be undone, so it stays admin-only even though deleting a movement no longer is.

And opening Settings no longer re-asks the server for things that haven't changed since you last looked — the deleted list, the movement IDs and the user list are kept until something actually changes them. Settings also has a proper bottom edge now, instead of ending flush against the content.

v11.57 Fixed

Switching movement type left half a row behind

Going from Exit to Transfer carried the quantity across but not the rack it came from, leaving a row that read "? unknown rack" with a real number in it — a row that looks filled in and isn't. The badge at the top counted that row too, so it could show more units than exist.

Now the rack and the quantity travel together or neither does. If the material sits in one rack, both are filled in for you; if it is spread across several, neither is, because the app doesn't know which shelf you walked to. The source-rack dropdown also stopped offering every location in the building and now lists only the racks that material is actually in, with how much is in each.

v11.56 Fixed

The "can delete movements" permission never actually worked

Giving a warehouse role permission to edit movements was supposed to let them fix or delete a saved movement — the switch says so. Editing worked; deleting was still admin-only underneath, so the switch looked on, the delete button appeared, and pressing it said "Admin only." It works now, and the permission still does not open the bulk operations that rewrite a material's whole history.

v11.56 Fixed

"Load Older History" made the Entry and Exit buttons disappear

While a button is working, the ones beside it are hidden so a growing label can't land on top of them. "Load Older History" finishes under a new name, and it changed that name without telling its neighbours it had finished — so + Entry (IN) and − Exit (OUT) stayed hidden until the page was reloaded. Fixed everywhere it could happen, not just where it showed.

v11.55 New

Deleting a movement can be undone

A deleted movement now goes to the trash instead of being destroyed. Settings → System lists what has been deleted, who deleted it and when, and puts any of it back exactly as it was — same quantity, same rack, same date — with the stock totals recalculated straight away.

And the row leaves the screen the moment it is deleted, rather than after the next full refresh. The confirmation no longer warns that this cannot be undone, because now it can.

v11.55 Fixed

Deleting could remove a different movement than the one you clicked

Movements were identified by their position in the list, and positions move: deleting one lifts everything below it. So if somebody else deleted a movement while your screen was open, the one you clicked and the one that went could be two different movements — with no error, no warning, and nothing on screen to suggest anything had gone wrong.

Every movement now carries an identifier of its own, and deleting names that identifier instead of a position. It cannot pick the wrong one. Two people deleting the same movement is also handled honestly now: the second person is told who deleted it and when, instead of being told it worked.

v11.54 Fixed

"GENERIC" no longer appears where a job name should be

Material received into stock without being assigned to a job was written down as belonging to a job called GENERIC. It was the app's own word for "not assigned", and every screen already knew to ignore it — but it sat in the spreadsheet looking exactly like a customer's name, which is not something a movement log should ever invent.

An entry with no job now records no job. Rows written before this update still say GENERIC and still mean the same thing, so nothing changes underneath you; they can be cleared whenever you like.

v11.46 – v11.53 New

Everyone sees the same warehouse

When two or three people work in the app at the same time, a change one of them makes now reaches the others on its own, within about twenty seconds — an entry, an exit, a transfer, a deleted movement. Nobody has to press refresh, and nothing flashes or interrupts what you were typing.

Saving at the same moment as somebody else no longer fails. The app waits its turn and tries again by itself, up to four times, showing "Waiting…" while it does. What used to be an error you had to redo is now a short pause.

Taking out more than there is now corrects the number on screen and explains what happened, instead of only refusing. If someone else took the last of it while your form was open, the form says so — the moment it happens, not when you press save.

v11.52 Fixed

A deleted movement could come back on somebody else's screen

Four movements deleted on one account were still showing on another twelve minutes later, with both screens connected and nothing wrong in sight. Two refreshes were racing each other, and when the slower one had been asked for before the deletion and arrived after it, it painted the older warehouse back over the newer one.

Nothing failed, which is why nothing warned: both answers were correct, they just arrived in the wrong order. An answer that has been overtaken is now discarded instead of drawn.

v11.51 Fixed

Notices off the edge of a phone, and buttons on top of each other

On a phone, the small confirmation notices appeared outside the visible area — so the app was answering where nobody could read it. And a button that changed its label mid-action grew wide enough to sit on top of the one beside it. Both are fixed, and two people unlocking the same material at once no longer leaves one of them with an error for work that was already done.

v11.42 – v11.45 New

Mark a delivery arrived, and its entry is already filled in

The app already knew what was arriving, how much, from which supplier and under which PO. Marking it arrived used to mean typing all of it again on the entry screen — which is exactly where what was expected and what got recorded start to differ.

Now marking it arrived offers to record the entry, and opens it with everything the delivery already knew. The rack is left empty on purpose: the app knows how much arrived, not where it was put, and a filler rack would be stock claiming to sit somewhere nobody looked.

If you don't do it right away, a card waits in the corner of the screen until you do. It stays there if you open the entry and close it without saving — only a saved entry clears it.

v11.44 Fixed

The header stayed put on a phone and scrolled away on a computer

The tabs and the account menu are meant to stay at the top of the screen. On any screen wide enough to show the compact header — which is to say every desktop — they scrolled away with the page instead. Two rules were fighting over it and the wrong one was winning, on exactly the screens most people use.

v11.43 Fixed

The morning reminder never admitted you had dealt with it

The list of this week's arrivals that greets you in the morning stayed on screen showing deliveries you had just marked as arrived. It was deciding whether a delivery was still waiting by looking for a status the app has never produced, so nothing ever counted as done.

It now closes itself when the last one is handled, and keeps showing the rest when there are more.

v11.41 New

The week's deliveries, laid out like a week

Expected deliveries were a list. They are now seven cards — four across the top, three below — all the same size, the way a calendar week looks. A busy day scrolls inside its own card instead of stretching it and pushing the other days out of shape, so the shape of the week stays readable no matter how uneven it is.

v11.40 Terms

What happens if you stop paying, written down plainly

The Terms now say exactly what happens when a subscription lapses and what it costs to come back, instead of leaving it to be decided case by case. Refunds are stated as one timeframe — 5 to 10 business days — in every document that mentions them.

What has not changed, and is worth repeating: your data is yours and lives in your own Google spreadsheet. Stopping payment does not take away your information or the app you have installed. You lose support and updates, nothing else.

v11.39 Fixed

Help text you could not finish reading

The small ⓘ explanations were being cut off at the edge of the window, and near the bottom of the screen they opened downward into nothing. They now stay inside the window wherever they are, and flip above the icon when there is no room below.

Settings also grew: on a large screen it now uses the room it has instead of staying at the width of a small laptop.

v11.37 – v11.38 Fixed

Opening Settings stopped looking like the app was reloading

Every time you opened Settings — and every time you switched tabs inside it — the whole page blanked and rebuilt itself. It now shows what it already knows immediately and quietly replaces only what actually changed. If nothing changed, nothing moves. And if you are typing in a field when fresh data arrives, it waits until you are done rather than redrawing under your hands.

Each area of Settings is now boxed and labelled, so it reads as separate things rather than one long column of controls.

v11.35 – v11.36 New

A daily report of what moved, sent to you every evening

At an hour you choose, the app emails a summary of the day: what came in, what went out, what was corrected, and who did it. If nothing moved it says so in one line rather than sending an empty table — a quiet day is information too.

You pick the hour and who else receives it in Settings → System. Costs are never included, whatever the recipient's role: a report forwarded outside the company should not be the way a supplier price gets out.

v11.33 – v11.34 Fixed

Two ways to lose work, both closed

Selecting rows and opening a form used to drop the selection, so pressing Save recorded nothing and said it had worked. The selection now survives the form and is only released when the window closes.

And while one button is working, the buttons beside it now step out of the way instead of staying pressable. A second press on a neighbouring button mid-save was a real way to record something twice.

v11.33 Safety

A nightly backup that failed silently now says so

If setting up the automatic nightly backup failed during installation, nothing was written down and nothing was shown — the app simply had no backups and no one knew. The failure is now recorded and Settings → System checks all four automatic jobs and tells you which ones are really installed.

August 2026

A full audit of the app, and the first fixes out of it: the evening date, the quantity that was not whole, and a check that counts every door before each release. Plus the movement screens rebuilt as one, packs, and backups that carry your settings.

v11.32 Fixed

A highlight you can actually finish reading

When the app points you at the movements a notice is talking about, it marks them in yellow. Six seconds is plenty for one or two. For eight, the colour was gone before you had scrolled to the third.

The mark now lasts in proportion to how much there is to read — six seconds for one, half a minute at the most — and it holds its colour before fading instead of washing out from the first moment. Past a handful it also tells you how many are marked, so you are not counting highlights while you scroll.

v11.31 Fixed

The links on those notices now go somewhere

Two things you could press on a system notice did nothing useful. Show the movements answered "those rows are in the archived history — use Load Older History", which was true every single time and helped no one: the app knew which rows were missing and knew how to fetch them. It fetches them now, and takes you straight there.

Details opened Settings and highlighted nothing. The highlight was real, but it was looking for the entry before the page had finished loading it, and gave up without a word. It now waits for the page instead of guessing how long the page will take.

v11.30 Fixed

The app was doing its nightly work and never told you

Backups ran every night. Old movements were archived, stock totals were rebuilt, and every one of those was recorded — 27 backups are sitting in Drive right now. But the corner of the screen never showed a card saying so, and Settings → System said nothing automatic had ever run. Both were reading the same list, and that list arrived empty every single time.

One character in one line: the server asked for the signed-in user by a name that does not exist at that point, the error was caught and turned into an empty list, and nothing was ever written down about it. Nothing was lost and nothing was broken — the record simply never reached the screen. It does now, and the error that hid it can no longer hide.

v11.29 Fixed

The window that opened behind the window

From the morning arrivals list, pressing Mark arrived opened the edit window behind it — and the form underneath could not be touched at all. The only way forward was to close the list and lose your place in it.

The app deliberately makes every window but the front one inactive, so a click can never land on a half-hidden form. It was doing that correctly; the morning list had simply been told it was in front of everything, including the windows it opens itself. It now sits behind what it opens.

v11.29 Fixed

A renamed role is now called by its name everywhere

If you rename the warehouse role — to Supervisor, say — the account panel showed the new name on the profile card and the old internal one in the Active Users row directly beneath it. Same person, same panel, two different words.

Every screen that shows a role now uses your name for it. The one exception is deliberate: the first-time setup wizard still says Warehouse, because it runs before there is a custom name to use.

v11.28 Security

What a role may not see is no longer sent to it

Cost figures were reaching every browser and then being hidden by the page that drew them. That is not a permission — it is a preference, and it is not what the feature page promises. Unit cost, total cost and the running average now leave the server only for roles allowed to see them: administrators always, the warehouse role when an administrator has turned See costs on, and read-only users never. Nothing looks different on screen; the difference is that the numbers were never handed over.

The same load was also sending the old list of colleagues' email addresses with their roles beside it, and the owner's address, to every role on every load — in service of nothing, since no screen in the app has read that list since user management moved to its own page. Administrators still get the real list; everyone else no longer gets a staff roster they never asked for.

v11.28 Fixed

A category with a < in it no longer cuts a screen in half

Category names, attached file names and units were being placed into the page without being treated as plain text. A category typed as GLASS <5MM ended the screen right there — no error message, just a page that stopped, and a person reasonably assuming their data was gone.

Categories go through one shared piece of the app, so this was one correction across seven screens: the stock table, the movement list, packs, the material picker, the low-stock monitor and both expected delivery views.

v11.27 Fixed

After six in the evening, the app was writing tomorrow's date

A delivery received at 6:30pm on the 14th was recorded as the 15th. Not rejected, not flagged — filed one day late, every evening, by the app itself. The date box on the movement window opened already filled in that way, so unless somebody noticed and changed it, that is the date that was saved. Month-end totals were off by whatever crossed the dock after six.

The app was reading the clock in UTC instead of in your own time zone. It now reads the day you are actually standing in, everywhere — the movement form, the week's arrivals, the file names on exports. Dates already in your sheet are untouched; correct any evening entries you want to fix with Adjust or by editing the movement.

v11.27 Fixed

Half a sheet is a real quantity, and it was being rounded away

Typing 2.5 into a quantity box saved 2. Typing 0.75 saved nothing at all. No warning appeared, so the only way to catch it was to go back and read the row afterwards. Anyone working in SQ FT, LN FT, sheets or rolls was affected — the units that exist precisely because material comes in halves.

Quantity boxes now accept decimals and keep them, from the entry form through to the sheet. Zero is still refused, with the app saying so in its own words. Whole numbers behave exactly as before.

v11.27 Security

One page of the system's own activity was readable without signing in

The list of what the system did on its own — backups, maintenance, who saved what and when — could be read by someone who had your app's link and a Google account, without the app ever checking who they were. It exposed no stock figures, no costs and no material data, and there is no sign it was ever read. It is closed.

What matters more than the one page: there is now an automatic check that counts every way into the system before each release and refuses to ship if any one of them is left unlocked or unexplained. This gap existed because nothing was counting. Something is now.

v11.25 – v11.26 Fixed

Expected deliveries: the category that disappeared, and the window that would not come back

Opening a delivery to edit it showed the category box empty even though one had been chosen — and saving from there wrote that emptiness back. The cause was a rename: renaming a category updated your movements and your history but left expected deliveries pointing at the old name. It now renames them too, and a delivery whose category is no longer on your list says so instead of silently blanking.

The morning list of arrivals also closed for good once you marked the first item, with a full page reload the only way back. It reopens from This week's arrivals in the account menu, where everyone can reach it rather than only on the Incoming screen. Every item carries its own Mark arrived button, and the layout puts the quantity beside the name with the status underneath it.

v11.24 Fixed

An expected delivery no longer arrives pre-filled with a 1

The quantity box on a new expected delivery opened with a 1 in it, which is a number to plan around whether or not anyone meant it. It opens empty, like every other quantity box in the app.

Leaving it empty is allowed on purpose: a delivery entered from an attached loading sheet often has no count until the truck arrives. Where a quantity was never stated, the app says qty not stated rather than printing a zero.

v11.23 New

Check my data — the same thing written two ways, found for you

Type a supplier's name slightly differently twice and you have two suppliers. Nothing breaks; the totals simply split in half and the reports quietly stop adding up. Check my data, in Settings, reads your whole archive and shows what it found: the same name spelt two ways, materials missing a supplier or a project, and names close enough to each other to be a typo.

The first two come with a Merge button that does the work across every affected row at once. The third — possible typos — deliberately does not: JJF 109 and JJF 110 look almost identical to a computer and are two different jobs. That one is for your eyes only.

v11.22 New

Correcting a count is not the same as recording waste

The app says 40 and the rack holds 38. That does not mean two broke — it means the record is wrong, and filing it as waste would quietly turn your waste figure into "waste plus counting mistakes". Those are two different problems with two different fixes.

Adjust is a movement type of its own, on the movement window and on the rack drawer. You type what you counted, not the difference — the app knows what it believed and does the subtraction — and it tells you what will happen before you save. It costs nothing, it belongs to no job, it needs a reason, and it can go up as easily as down.

v11.21 Fixed

An expected delivery keeps the status you gave it

Entering a delivery as already Arrived, or cancelling one on the spot, saved it as Pending anyway and needed a second edit to stick. Creating and editing disagreed about the same field; they go through one place now.

Deleting one also said nothing at all while it worked, so pressing again was the reasonable thing to do — and the second press hit a row that was already gone and reported an error for a delete that had succeeded. The button now shows it is working, a second press is ignored, and a delivery that is already gone ends as success.

v11.17 New

Editing a movement fills in what the app already knows

A transfer or a write-off is saved with supplier, contract, PO and project empty, because those boxes are hidden on those screens. Open one to correct something and you used to face five blanks for a material whose supplier the app has known all along.

Those boxes now arrive filled from that same material's own history, most recent first — and every suggested one is marked, so you can always see which values are recorded on that movement and which the app is offering. Anything the row already has is never touched.

v11.16 Fixed

A transfer no longer invents a project

Moving material between two racks is not a change of job, but a transfer was being stamped GENERIC — the app's word for "in stock, unassigned" — because the project box is hidden on that screen and nobody filled it in. Filter a material's history and the same job looked like two different stories.

A transfer now keeps the job the material already had, and the app never asserts something it was not told.

v11.13 – v11.15 Safety

Your backup carries your settings — and says so when it can't

A backup copies your spreadsheet, but part of an installation lives outside it. Restore a copy without that part and everything looks right until an attachment refuses to open, months later, with nothing pointing at why.

Every backup now has a tab inside it listing exactly what to put back and what each one is for, in plain words — including the handful that must be replaced with new values after a restore rather than copied. If it cannot be written, the backup says so on the spot instead of looking complete.

v11.12 New

The Terms and the Privacy Policy can be read before you accept them

The consent box asked you to agree to two documents you had no way to open. Both now live on their own tabs inside your own file, linked from the box itself, and they stay there afterwards — so the version you agreed to is always the version you can go back and read.

v11.5 – v11.11 New

The app remembers how many come in a box

The first time you receive a material by the box, case, pallet or bag, it asks once: 1 box of GE SILPRUF = how many units? After that it just says 12 units per box, with an edit link, and it does the arithmetic — including the cost per unit, updated as you type.

Not every material comes in a box and not every box holds the same number, so nothing is assumed: a material with no answer on record is simply counted in units, as before. The whole list is editable in Settings → Materials.

v11.9 Fixed

A cost label that could have made a box price look like a unit price

For four versions, choosing Box as the unit relabelled the cost field "Cost per Box" while nothing divided it — so a price typed for a box of twelve went into the running average as the price of one unit. Cost is worked out as a weighted average, so a wrong figure there affects every later cost for that material, not just the one movement.

The field now names a pack only when the app actually knows how many are in it, and divides by exactly that number. If you priced anything by the box between v11.5 and v11.8, it is worth checking that material's cost.

v11.6 – v11.8 New

The five movement screens are one screen now

Entry, exit, transfer, return and waste had drifted into five different layouts — fields in different orders, quantity boxes in different places, and a "1" already sitting in the quantity on three of them.

They now share one shape: the material in a neutral box, the movement's colour only around the part that says where and how many, nothing filled in when the window opens, and the cursor in the name field ready to type. Switching between them keeps what you have already written instead of making you type it again.

v11.3 – v11.4 Fixed

Two small things that were quietly wrong

Un-ticking the consent box left the consent recorded. And on a delivery with several materials, autofill filled the first line rather than the line you were working on.

v10.8 New

Your own AI key, set from inside the app

Reading a supplier's confirmation email into your expected deliveries runs on a key you own, entered in Settings — not one of ours. It works on your quota, at your cost, and can be removed at any time.

v11.18 – v11.20 Terms

What support covers, and how billing works, is written down

Section 5 of the Terms now says what the subscription includes, how fast an email is answered, what is quoted separately, and what you are expected to do. Section 9 covers cancelling, refunds, how invoices are sent, and what happens if a payment does not go through — including the one thing worth knowing in advance: your data is never withheld over a payment, and the app keeps working.

v10.0 – v10.4 Safety

Two people can change the same thing at once without losing one of them

Stock is worked out from your movement history, so any change that reads it, adjusts it and writes it back can collide with another one happening at the same second — and the old failure was silent: both people saw "saved", and the number was quietly wrong.

Saving a movement was already protected. Now editing a saved movement, renaming a category, renaming or merging a material, and merging projects, suppliers or locations are too. If two changes ever do meet, the second one is told to try again in a moment instead of overwriting the first.

v10.5 New

See how much room your file has left

Settings → System now shows a bar with how much of your spreadsheet is used, and roughly how many years of room is left at your current pace. The estimate is measured over your last 90 days and doesn't appear until you have 60 days of history — your first weeks are mostly the opening inventory, and projecting from those would be wrong.

v10.1 – v10.6 Fixed

Renaming a category now updates everything, straight away

Renaming a category updates every movement that used it, both the recent ones and the archived ones, and the whole app shows the new name immediately instead of after a reload. The same fix reached merging locations, projects and suppliers.

Renaming used to take minutes on a busy warehouse with no sign it was working. It now takes seconds, and the button says what it is doing.

v10.3 Fixed

Settings says which renames carry into your history

Renaming a category updates your existing movements. Renaming a supplier, project or location does not — those are records of what was true at the time, and they're already printed on paperwork that went out. Every tab now says which one it is, instead of one sentence that was only true on one of them.

v9.93 – v9.95 New

Every backup, listed in one place

Settings → System lists every backup that actually exists in your Drive folder, read from Drive itself. Backups also now carry a copy of your configuration inside them, so restoring one brings back your settings and not just your data.

v9.94 New

Mark a delivery as arrived from the morning popup

The "Arriving today" card now takes you straight to the right place to record it. Admins only.

v9.87 New

A warning when a price moves

Enter a unit cost noticeably different from what you have been paying and the app says so before you save, so a typo in a purchase price doesn't quietly become your average cost.

v9.78 – v9.81 New

Costs: what your stock is worth, per project and in waste

Optional weighted-average costing. Enter what you paid on the way in and the app tracks the average, shows the cost of what has gone out to each project, and puts a dollar figure on waste. Every cost is behind its own permission — the people recording movements don't have to see money.

v9.76 – v9.79 New

Decide what the warehouse role can do — and what to call it

Permissions are now switches rather than a fixed wall: give the warehouse role the ability to edit movements or manage the catalog if that is how your shop runs. And if you call them Receivers or Yard, rename the role to match.

Earlier

The foundations, before the versions above.

v9.82 New

The rack drawer, rebuilt

Tap a location on the Warehouse Map and act on what's in it — exit, transfer or waste — with the material, category and source rack already filled in. Tap the "available" figure to fill the quantity.

v9.73 Fixed

Usable with a thumb

A full pass over every screen at phone and tablet sizes: nothing runs off the edge, nothing is too small to tap, no screen scrolls sideways. A warehouse is not run from a desk.

v9.74 New

Low-stock alerts you actually set up

Choose which materials matter and what the minimum is. The dashboard tells you what is below it and what has hit zero.

v9.71 New

Backups you can run and check from inside the app

Turn the nightly backup on, run one now, and see when the last one happened — without opening the spreadsheet or the script editor.

v9.61 New

Paste a supplier email, get the expected deliveries

Paste the confirmation you were sent and the app pulls out what is coming, how much, and when — into the Incoming list, for you to check before saving.

v9.58 New

A health check for the parts you can't see

One command that inspects the half of an installation nobody looks at — folders, permissions, scheduled jobs — repairs what it safely can, and tells you plainly what is missing.