ThreadCount Community edition

Uniform stock management for healthcare linen rooms: the coordinator app, the phone counter and the staff app, for your own server. Built from f976bd5 on 2026-09-15. Licensed under the Functional Source License (FSL-1.1-ALv2).
This commit is contained in:
ThreadCount
2026-09-15 23:18:05 +10:00
commit c89010a4f1
424 changed files with 53588 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
---
title: The counter app
section: apps
order: 1
summary: The linen room's phone app, on Android and in a phone browser. Choosing a server, signing in, the screens, and what it keeps on the phone.
screen: /m
role: Admin or Issuer
keywords: counter app, android, phone, mobile, play store, install, sign in, server, self-hosted, community, offline, camera, stocktake, count, issue
---
The counter app is the coordinator's side of ThreadCount on a phone. You sign in with the same Admin or Issuer account as the portal at `/app` and see the same records, served at `/m`.
## Two ways to open it
- **In a phone browser.** Go to `https://threadcount.tech/m`, or `https://your-host/m` on your own server. You don't need to install anything.
- **The Android app.** It is called **ThreadCount** on Google Play. It shows the same `/m` screens inside the app and adds the phone's own barcode scanner (see [Scanning and browsers](/docs/apps/scanning-and-browsers)).
There is no iOS app. On an iPhone or iPad, open `/m` in the browser.
## The first screen and choosing a server
The first time you open the Android app, the welcome screen shows **Sign in**, **Sign up** and a line reading `Server: threadcount.tech · Change`.
Most rooms use the hosted service and leave that line alone. A room running the Community edition on its own server changes it, once, on each phone:
1. **Tap `Server: threadcount.tech · Change`.** The `Which server?` screen opens.
2. **Choose `Self-hosted`** and type the server's address, for example `uniforms.example.com`.
3. **Tap `Check and use this server`.** The app checks the server's `/api/app-info` to confirm it is a ThreadCount server.
The app refuses an address that doesn't answer over https within 8 seconds, or that answers but isn't a ThreadCount server. Once the check passes, the app saves the address on that phone and opens that server from then on. Choosing `ThreadCount hosted` puts the phone back on `threadcount.tech`.
Once you have tapped `Sign in` or `Sign up` on that phone, the app skips the welcome screen and goes straight to `/m`.
## Signing in and signing up
`Sign in` asks for your work email and password. If your account has two-factor turned on, it then asks for the six-digit code from your authenticator app. A recovery code also works. `Forgot password` emails a reset link that works once and expires after an hour. See [Two-factor](/docs/account/two-factor).
`Sign up` creates a new facility. It doesn't add you to an existing one. The person who signs up becomes the new facility's first Admin. To add someone to an existing facility, an Admin adds them with `Add user` in `Settings People & sign-in` in the portal: see [Users](/docs/account/users).
## The screens
The bar at the bottom has four tabs.
| Tab | What it does |
|---|---|
| Home | Today's issued and returned counts, lines below par, days since the last count, recent activity |
| Count | Pick a shelf and count it by scanning; gaps go to a variance screen before the count is filed |
| Stock | On hand against par, worst first, with a reorder draft |
| Search | People and stock in one box, or scan a garment to find it |
Home also has `Issue to someone`, and `Deliveries, pickups, rounds and more` opens `More`:
- `Receive a delivery`
- `Pickup call list`
- `Delivery round`, with a signature on the phone
- `Reprint a label`
- `Variance over time`
- `Catalogue`, where an Admin can add and edit garments
- `Settings`
Open a person's record from Search or Issue to issue to them, take a return or swap a size. [Issue a garment](/docs/counter/issue-a-garment) and [Exchanges and returns](/docs/counter/exchanges-and-returns) say what each of those writes.
`More Settings` holds only what the phone controls:
- `Beep and buzz on a scan`, which applies to this device only
- `Reason required at`, the count gap that needs a reason. An Admin can change it; an Issuer can only see it.
- links to the privacy policy, the terms and account deletion
- `Sign out`
`People`, `Orders`, `Reports` and the facility's settings are in the portal at `/app`. On a phone, the portal's `More` sheet has a `Counter app` link that opens `/m`.
## What it keeps on the phone
The app keeps only these on the phone:
- your session
- any shelf count you have started but not filed. It is kept under your own account, so the next person on a shared phone never sees it. Filing the count deletes it, and signing out deletes all your open counts.
- the beep setting, the chosen server, and whether the welcome screen has been seen
Issues, returns, stock, the staff register and every other record stay on the server.
## Permissions and connection
The Android app asks for one permission, the camera, and uses it only to read barcodes. No picture from scanning is stored or sent.
The app works online only. With no connection it shows `No connection.` and a `Try again` button, and it won't let you issue or count anything. Nothing is queued to send later. A count you hadn't filed stays on the phone until you're back online.
> **Careful** Signing out deletes any shelf count you haven't filed. File it first, or you'll have to count that shelf again.
+87
View File
@@ -0,0 +1,87 @@
---
title: Scanning and browsers
section: apps
order: 3
summary: USB scanners, the barcode formats ThreadCount reads, which browsers can scan with the camera, the Android scanner, and what happens with a code it doesn't know.
role: Admin or Issuer
keywords: scan, scanner, barcode, usb, camera, counter, stock count, unknown barcode dialog, product lookup, chrome, edge, safari, firefox, iphone, ean, upc, code 128, unknown barcode, bind, label, print
---
ThreadCount reads a barcode from a USB scanner, the camera in a supported browser, or the scanner built into the Android counter app. Wherever you can scan, you can also type the code.
## USB scanners
A USB scanner that types the code and then presses Enter works with any browser. In the portal these boxes accept one:
| Screen | Box |
|---|---|
| `Stock Count` | `Scan to count +1` |
| `Counter`, in `Issue` mode | `Scan a garment, or type a name`, in `Add garments` |
| `Stock Scan to add` | `Scan with a USB scanner, or type the code and press Enter` |
| A garment's `Scan sizes` | The prompt names the size to scan next |
`Scan to add` and `Scan sizes` bind codes, and only Admins see them.
You can also scan with no box selected. The portal takes 4 or more keys, each within 35 ms of the last and ending in Enter, as a scan. A staff number opens that person at the `Counter`. A garment goes into the pickup when the counter has a person open, adds 1 on `Stock Count`, and otherwise opens the garment's page. See [Keyboard and scanner](/docs/reference/keyboard-and-scanner).
## Formats it reads
Camera scanning, in the browser and in the Android app, reads these formats only:
- EAN-13 and EAN-8
- UPC-A and UPC-E
- Code 128, Code 39 and Code 93
ITF and Codabar are left out: with no check digit, a partial read can pass as a different code. No 2D codes are read.
A USB scanner or a typed code isn't limited to that list. In `Scan to add`, a 13-, 12-, 8- or 14-digit code has its check digit tested. If it fails, you see `check digit doesn't match — the scan may have mis-read`.
## Camera scanning in a browser
In a browser, the camera reads barcodes with the browser's built-in barcode detector.
| Browser | Camera scanning |
|---|---|
| Chrome | Yes |
| Edge | Yes |
| Safari on iPhone or iPad | No |
| Firefox | No |
Where the camera opens but the browser can't read barcodes, the screen says `Live reading isn't supported here — type the code instead.` With no camera access at all, it says `This browser has no camera access — type the code instead.` On an iPhone, type the code or use a USB scanner.
## The Android scanner
Inside the **ThreadCount** Android app, the phone's own scanner reads the code. If the app can't load it, the browser detector is used.
When counting a shelf, scanning stays on until you press `Stop scanning`. The same code again within 0.9 seconds counts as the same garment still in view. `Beep and buzz on a scan` in `More Settings` turns the sound and vibration on or off for that phone.
The ThreadCount Staff app has no scanner.
## Codes ThreadCount doesn't know
A code is known once it is bound to one size of one garment. In the counter app, an unknown code gives:
- **Counting a shelf:** `isn't a garment ThreadCount knows. Bind it to a size first`. A known code from another shelf names that shelf instead.
- **Issuing:** `isn't a garment ThreadCount knows.` Nothing is added.
- **Search:** the code is put in the search box.
In the portal:
- **`Stock Count`:** the `Unknown barcode` dialog opens. An Admin binds the code there; an Issuer is told only an admin can bind it.
- **`Counter`:** an Admin gets the same dialog; an Issuer sees `No garment has` and the code.
- **Scanned with no box selected:** the search panel opens with `No person or garment has` and the code, and Admins get `Bind it to a garment`.
- **`Scan to add`:** you can start a new product from the code, or attach it to a size you already have.
With `Look up unknown barcodes in public databases` ticked in `Settings Catalogue & suppliers`, `Scan to add` first asks a public barcode database for a product name. With it off, nothing leaves the server.
A code can be on one size only. Binding one already on another garment is refused unless you choose to move it. See [Barcodes](/docs/stock/barcodes).
## Printing ThreadCount barcodes
For garments that arrived without a barcode, an Admin opens the garment from `Stock` and chooses `Generate barcodes` from its menu. Each size without a code gets one of ThreadCount's own. These are EAN-13 codes starting `29`, a prefix for use inside one business, with a real check digit. A size with a supplier's code keeps it.
`Print labels`, in the same menu, asks you to confirm the count, then prints one label per garment on hand for each size with a code, two to a row on A4. With none to print, the sheet says why. A 13-digit code with a valid check digit prints as EAN-13, and any other code prints as Code 128.
`Reprint a label` in the counter app prints 1 to 24 copies of one size's label. It only lists sizes with a barcode bound to them. It prints from a phone browser. Inside the Android app the button reads `Print it on the desktop`, because that app can't print.
> **Careful** A garment without a readable barcode drops out of every scanned count. Reprint worn labels before the next stocktake.
+76
View File
@@ -0,0 +1,76 @@
---
title: The staff app
section: apps
order: 2
summary: The app for people who wear the uniform. How a staff member gets a sign-in, what they see and can do, what managers get, and how access is removed.
screen: /my
role: Staff
keywords: staff app, details and access, wearer, nurse, self-service, code, slip, activation, sign in, kit, request, waitlist, damage, kit check, manager, approve, ward round, remove access
---
The staff app shows one staff member their own uniform record, and lets them ask the linen room for what they need. It is at `/my` in any browser. On Android it is also on Google Play as **ThreadCount Staff**, which opens the same screens. There is no iOS app.
A staff sign-in is not a coordinator account. It is attached to the person's entry on the register, and it cannot open `/app` or `/m`.
## Getting a sign-in
An Admin gives each person a one-time code from their record in the portal:
1. **Open the person in `People`**, go to the `Details & access` tab and press `Generate a code` in the `Staff app` panel.
2. **Press `Print the slip` or `Copy`.** The code is 12 characters in three groups and is shown once only.
3. **Give the slip to the person.** In the app they tap `I have a code` (or `First time? I have a code` on the sign-in screen).
4. **They enter the code, an email address and a password** of at least 8 characters, and tick the terms box if it is shown.
The code works once. It expires 14 days after it was printed (`SLIP_DAYS` in `lib/compute.ts`), and the panel says when it expires. `New code` replaces an unused code, and `Cancel the code` withdraws it. A person who already has a sign-in cannot be given a code until their access is removed.
The email address is theirs to choose and may be a personal one, but each sign-in needs its own.
A forgotten password cannot be reset by email: the linen room removes access and gives a new code.
| Record | Change | Undo |
|---|---|---|
| Staff record | A code and its print date are stored | `Cancel the code` |
| Staff account | Created with the email and a hashed password; the code is spent | `Remove access` |
## What a staff member sees and can do
The bar at the bottom has `Home`, `Kit`, `Orders` and `Messages`.
- **Home** shows the request furthest along (with its collection code once it is ready), how many garments they hold, and shortcuts.
- **Kit** lists what the linen room has recorded against them. `This isn't right` raises a query about the record.
- **Request** asks for one or more garments with a reason. The request goes to the manager recorded on their staff record for approval.
- **On the shelf** shows each size as `In stock`, `Low` or `None on shelf`, and says when it was last counted.
- **Waitlist** is offered for a size with none on the shelf, showing their place before they join. Accepting a held garment sends it to their manager as a request.
- **Report damage** can also request a replacement. The garment leaves their record only when handed in at the counter.
- **Kit check** is open only while the linen room is running one, and Home prompts for it if they hold garments and haven't answered yet. They confirm how many of each they still have.
- **Orders** lists their open and finished requests, each with a message thread to the linen room.
- **Your sign-in** changes their password. A new password signs them out on every other phone or browser.
## What they never see
- prices, costs or any payment
- stock numbers: the shelf is shown in words only
- anyone else's record, unless that person reports to them as a manager
They cannot delete their own account; only the linen room can remove access.
## Manager functions
A staff member becomes a manager when someone on the register names them as their manager. See [Managers](/docs/people/managers). They get:
- **Approvals.** Home shows how many requests are waiting. They can approve or decline each line, with a reason when they decline.
- **Approve by link.** The email about a new request links to `/my/approve`, which needs no sign-in. The link lasts 14 days and stops working once the request is decided or either person leaves the register.
- **Ward.** Who on their team holds what, with each person's set count.
- **Raise for your team.** A request typed for someone who reports to them. It goes to the manager's own manager for approval.
A person with `On the ward desk` ticked in the `Staff app` panel on their `Details & access` tab also gets `Ward round`, where bags delivered to their ward are signed for. Whoever signs is named on the order. See [Delivery rounds](/docs/counter/delivery-rounds).
## Permissions and connection
ThreadCount Staff asks for internet access and nothing else: no camera, location, contacts, files, photos or microphone. It has no scanner and works online only. With no connection it shows `No connection.` and nothing is saved to send later.
## Having access removed
On the person's `Details & access` tab, an Admin presses `Remove access` in the `Staff app` panel and confirms. The staff account is deleted and every session it had ends at once. Their register entry and issue history stay.
`Deactivate` in the `Register` panel on the same tab also stops their sign-in straight away, with the message `You're no longer on the register at this facility.` See [Deactivating and deleting](/docs/people/deactivating-and-deleting).