# Change Log — CEERP (Colorado Envelope ERP)
Build timeline (one line per day since the project started; every date below is the real commit date)
- **Day 1 — Wed 2026-10-22**: v0.1.0 prototype (dies, estimator, quotes, price adapter) → v0.2.0 production-site mirror and theme → v0.3.0 orders/schedules → v0.3.1–v0.3.3 Kelly Spicers prices and Spicers Inventory → v0.4.0 editable POs/vendors/customers/site tables → v0.4.1 Users page.
- **Day 2 — Thu 2026-10-23**: v0.4.2 tailnet URL and auto-start at logon.
- **Day 5 — Mon 2026-10-26**: v0.14.1 CEERP moves to its own Google Cloud server at https://ceerp.pressforgetechnologies.com/.
- **Day 4 — Sat 2026-10-25**: v0.8.0 the name is CEERP, Dev Chat → v0.9.0 die homes, movements, cabinet map, machine maintenance → v0.10.0 dashboard tiles, floor view, reports → v0.11.0 job cost, inventory, estimate → order, nightly backup → v0.12.0 floor plan pre-wiring, one-button sync → v0.13.0 floor devices → v0.14.0 mark complete, tablet layout, global search.
- **Day 3 — Fri 2026-10-24**: v0.5.0 per-user passwords → v0.5.1 dieline verification by die → v0.6.0 production orders and schedules editable here → v0.7.0 out tables, customers, vendors, machines editable; e-mail; site table look → v0.7.1 vendors page like the site, contacts open to all → v0.7.2 vendor suggestions → v0.7.3 price-guide freshness, five dies verified, demo estimate → v0.7.4 customers page: wide search, no-wrap dates, delete on the button row, every table sortable → v0.7.5 page-by-page match of the production site (sidebar, title strip, schedules, View Schedules, PO list, customers, machines, site tables, out tables).
## v0.14.1 — 2026-10-26 — CEERP is in the cloud
Changed
- CEERP now runs on its own server in Google Cloud under PressForge's account, at **https://ceerp.pressforgetechnologies.com/** (until that name is switched on, the same server answers at https://ceerp.136.113.11.105.sslip.io/), so Don, Cullum and anyone they give a password to can open it from any browser on any network. The connection is HTTPS only; names and passwords carry over unchanged because the database moved with the app.
- Nightly backup moved with it: every night at 02:00 Mountain time the database and the private files are zipped on the server and copied to a private storage bucket, 30 nights kept.
- The workstation copy (the tailnet address) is retired: its at-logon task and its backup task are switched off so there is one live copy of the data. Field Guide "Reaching the app" rewritten; `docs/HOSTING.md` now describes the real server instead of a plan.
## v0.14.0 — 2026-10-25 — Finish a job; works on a tablet; one search box; prototype leftovers gone
Added
- **✓ Mark complete** on every production order (and **✓ Done** on every schedule row): Location becomes Complete, the order leaves every schedule list, and CEERP keeps a real completion stamp — who and when — which the site never had. **Reopen** puts it back on Converting Unscheduled. Reports now count a job in the week it was completed here, falling back to date due only for jobs without a stamp. Schema 019.
- **Global search** in the top bar: an order number, PO number, estimate number, die number or tag goes straight to the record; anything else shows matches across orders, POs, customers, vendors, dies, estimates, inventory and devices.
- **Tablet and phone layout**: the sidebar slides in from a ☰ button instead of disappearing below 900 px; grids stack; tables tighten. The die-room walk-through and the scan box work on an iPad now.
- **Grouped sidebar**: Production, Schedules, Machines (collapsible, with the count), Floor, Tables, Estimator, Dev Chat. Groups remember whether you left them open; the group holding the current page always opens. "Straight Knife Unscheduled" no longer wraps.
- Field Guide has a table of contents; the audit log has a search box (who, object, action, any value in before / after).
Changed
- Customers and Vendors load their Contacts / Edit pop-ups on demand instead of rendering 300 of them into the page (the customers page drops from ~400 KB to ~60 KB).
- Login is also throttled per device (15 failures in 15 minutes) on top of the per-user lockout.
- Prototype leftovers removed: the SAMPLE customer, the three SAMPLE paper stocks and their prices, the three test estimates and their quotes; the yellow prototype banner is off (`ENABLE_DEMO_DATA=false`). The Dev Chat welcome note now speaks for the app, not for a person.
## v0.13.0 — 2026-10-25 — Floor devices: readers, cameras, sensors feed CEERP
Added
- **Admin › Devices**: register a Raspberry Pi reader, camera or sensor (name, kind, role, machine, location); each gets its own bearer token, shown once, rotatable, disable-able. Online / offline (silent 15 minutes), last seen, events today. A device gets a pin on the Floor Plan.
- **`POST /api/ingest`**: devices post events — `tag`, `count`, `state`, `heartbeat`, `image`, `other` — with their token. Rules: a tag read at a **die-room** reader checks the die out if it was in and returns it home if it was out; at a **machine** reader it checks the die out to that machine (or moves it there if it was out elsewhere); unknown tags are logged so they can be assigned on Die Locations. Counts and states are stored per device. Everything lands in Die Movements, the job-cost card and the floor plan without anyone typing.
- **Event stream** (`/events`, sidebar): every event with what CEERP did about it, filter by device and kind, refreshes every 30 s. Dashboard tile for devices offline once any exist.
- `scripts/pi/ceerp_reader.py`: the Pi agent (serial / USB reader or stdin, on-disk spool, heartbeat, systemd unit in the header). `docs/HARDWARE.md`: what to buy (UHF readers + on-metal tags for dies, current clamps for machine state, photo-eyes for counts, cameras for derived numbers only), the rules, and the lines we do not cross — no faces, no people-tracking, no audio; post a notice. Schema 018; `ce/devices.py`; tests.
## v0.12.0 — 2026-10-25 — Floor plan pre-wiring; one-button sync from the production site
Added
- **Sync from production site** (Admin): one button runs the whole read-only copy — the five server-rendered pages, every "get-*" JSON read and machine schedule, the parse, print views of any new purchase orders, every customer's contacts, then the load — in the background with a live log. Rows edited, created or deleted in CEERP are kept; the site's version shows as a badge. Last-sync time and counts are recorded; the site is never written to. So Don can keep entering work on the site he knows while CEERP stays current. `ce/sync.py`.
- **Floor Plan** (sidebar): pre-wired for the LiDAR scan. An admin uploads the to-scale plan (SVG / PNG); every machine, die cabinet and inventory item already has a pin waiting, lit by its live status — queue and service flag, dies out or misfiled, stock low — plus door / other pins you add. **Place pins** mode: pick a pin, click where it stands; drag to move. Pins are stored as fractions of the image so a better scan can replace the picture without re-placing anything. Until a plan exists the page says what the scan needs to produce. Schema 017; `ce/floorplan.py`.
## v0.11.0 — 2026-10-25 — Job cost, inventory, estimate → production order, nightly backups, chat digest, hosting plan
Added
- **Job cost** card on every production order: quoted value, what was bought for it on purchase orders (PO lines whose Production Order# names the order; a line total is computed only when the price text is unambiguous — "/M", "/ea" or a bare amount — and the total is marked approximate otherwise), margin and % of quoted, bought cost per M, hours the die was out for this order, and the schedule lists it sits on. `ce/jobcost.py`.
- **Inventory** (sidebar under Purchase Orders): items on hand — sheets, rolls, cartons, window film, supplies — with unit, reorder point and location; every change is a move (received from a PO, used on an order, physical count, adjustment) with who / when / ref, so the number is always explainable. A dashboard tile counts items at or below their reorder point. Schema 016; `ce/inventory.py`.
- **Create production order** on an estimate: makes an order on the Converting Unscheduled list carrying customer, job, quantity, size, stock, due date and the die in the site's own out-table fields, links it back to the estimate, and opens its edit page. An estimate can be turned into an order once; the button then links to that order.
- **Nightly backup**: `scripts/backup_db.py` copies the live database with SQLite's online backup and zips it with the private files (dielines, quotes, chat screenshots, documents — not the re-downloadable site copy, price guide or client source zip) to `C:\Claude\ceerp-backups` (30 kept; optional second copy to `BACKUP_DIR_2` when that share is reachable). Scheduled Task "CEERP Nightly Backup" at 02:00.
- **Dev Chat digest**: `scripts/chat_digest.py` e-mails everything posted or marked done since the last digest to `DIGEST_TO`; it sends nothing until SMTP and `DIGEST_TO` are set in `.env`. Scheduled Task "CEERP Dev Chat Digest" at 07:00.
- `docs/HOSTING.md`: the plan for moving CEERP off the workstation (one small VM, HTTPS in front, SQLite stays, backups to a bucket) and the client decision it waits on.
## v0.10.0 — 2026-10-25 — Dashboard tiles, floor view, reports; Dev Chat edit / delete
Added
- **Dashboard tiles**: jobs not complete, due in the next 7 days, past due, dies checked out, dies misfiled, machines overdue for service, POs not entered, open Dev Chat items, estimates needing review — each a link to the page that fixes it, coloured when it needs a hand.
- **Floor view** (`/floor/<schedule>`, button on every schedule page): a full-screen, dark, large-type, read-only schedule for a TV on the floor; refreshes itself every minute; due dates go yellow today and red past due. F11 for full screen.
- **Reports** (sidebar): on the floor now by machine (jobs, pieces, next due, past due), throughput by machine per week for the last 8 weeks, orders / pieces / value / not-invoiced by month, and top customers this year. Throughput is bucketed by date due because the copied "date complete" is not a real completion stamp (the site fills it on every row).
- **Dev Chat**: edit and delete your own messages and replies (admins any); edits show "(edited)" and keep the old text in the audit log; deleting a message removes its replies and screenshot. Schema 015.
## v0.9.0 — 2026-10-24 — Die home locations, check-out / return, cabinet map; machine maintenance
Added
- **Die home location**: every die now has a structured home — cabinet · shelf · side · slot — plus a note ("bottom drawer, behind the 9 x 12s") and an optional scan tag. Cabinet letter and slot were seeded from the cabinet workbook ("A12" → cabinet A, slot 12); shelf and side are the shop's to fill in. The die page shows the home in words, flags "shelf / side missing", and has an edit form with an "I am standing in front of it — mark verified" tick. **Die Locations** (sidebar) walks every cabinet in slot order with one editable row per die so the whole die room can be captured in one pass. **Cabinet map** draws each cabinet by shelf and side with every die as a chip, marked when it is out or misfiled.
- **Die Movements**: Check out (who, when, for which order, on which machine) and Return (where it was actually put). A return anywhere other than the home is flagged **misfiled** until someone records it moved home or changes the home. The board shows Out now, Misfiled and Recent activity; a scan box accepts a tag id or a die number — any barcode / RFID reader that types and presses Enter drives it, so the RFID step later is a label printer, not more software. Schema 014; `ce/dieloc.py`; tests.
- **Machine maintenance**: a log per machine (date, type — scheduled / repair / inspection / calibration / other — what was done, who, downtime hours, cost, next due) and a **Maintenance** overview with last service, next due and overdue / due-soon flags. Reached from the Machines page (Maintenance button, "Maint." on each row) and the sidebar. `ce/maintenance.py`; tests.
## v0.8.0 — 2026-10-24 — The software is CEERP; Dev Chat
Named
- The application is **CEERP — Colorado Envelope ERP** (Jordan, 2026-10-24). Footer, page titles, PDFs, print views, guide and docs say so. The workstation's Scheduled Task keeps its old name "Colorado Envelope Estimator" so the auto-start does not break.
Added
- **Dev Chat** (`/chat`, menu entry at the bottom of the sidebar): one in-app thread where Don and anyone logged in post questions, change requests, bugs and notes to Jordan, optionally with a screenshot and the page it is about. The round 💬 button at the bottom right of every page opens the chat with that page filled in. Replies hang under their message; Done / Reopen per message; Open / Done / All tabs; a red unread count on the menu entry for messages by other people since you last opened the chat; the page refreshes itself when someone else posts. Everything stays inside the app and in the audit log. Times show in Colorado time (`SHOP_TZ`, default America/Denver). Schema 013; `ce/devchat.py`; tests.
## v0.7.5 — 2026-10-24 — Page-by-page match of the production site
Every public page of production.coloradoenvelope.com was rendered in a headless browser and compared with ours; the differences below were closed. (Production Orders and the order edit form are behind the site's login and could not be compared.)
Changed
- Schedules (unscheduled lists and every machine): compact read-only rows in the site's column order (Job # / Customer / Qty / Sz1 / Sz2 / Description / Prtg / Location / Stk Due / Order / Date Due / Status), long text clipped with an **Expand / Collapse** button as on the site, dates as MM-DD-YYYY. The pencil turns one row into inputs (order number, location, dates, machine) with Save / Cancel; **Move to…** stays on every row. Renumber and Add-order moved to the left of the toolbar, Print and Expand / Collapse to the right like the site.
- View Schedules: three columns — Jet, Folding, Straight Knife — each machine's queue as a small table, grouped by the machine table's category exactly as the site's page does.
- Purchase Orders list: the site's columns (PO# / Todays Date / Date Required / To / For / Action) with pencil, print and trash buttons per row, "+ Add" at the top right, US dates. **Delete** is new: a deleted PO leaves every list and count but keeps its items, documents and audit trail, and a sync does not bring the site's copy back (schema 012).
- Customers: laid out like the site — # / Name / Actions with Edit, a **Contacts (n)** pop-up (contact table, edit / delete, add form) and Delete; Search and Add Customer in the card header. Status and dates live on the customer page.
- Purchase order form: Save at the top right as on the site; Phone / Extension / Fax / Cellphone on one row under the vendor and contact pickers, as on the site.
- Out tables: rows in the site's own order, the Flat Size column only when a row has one, "+ Add" like the site.
- Machines and Site Tables: see v0.7.4 (pop-ups, tabs, add rows).
## v0.7.4 — 2026-10-24 — Customers: search by PO# / job# / contact; every table sortable; dates never wrap; site look
Added
- Site Tables: a **Used on** column per value — how many copied production orders carry it in the order fields that table feeds — with a click-through to those orders (`/production/orders?field=…&value=…`). The field mapping was derived by matching every lookup value against every field of the 7,889 copied orders (it is in `ce/production.py` `LOOKUP_FIELDS`). Window Film's values appear in no order field. Test added.
- Page-title strip on every page like the site's: the title on the left and the breadcrumb on the right, on the grey page background under a plain white top bar.
- Customer search matches the customer name, any contact's name / email / phone, or any of the customer's production orders by customer PO#, job#, order# or job title. The result note says what was matched against. Test added.
Changed
- Every table on every page is sortable by clicking a column heading (the tables that opted out — contacts, machines, categories, out tables, site tables, schedules, PO items, dieline lists, users — now sort too).
- Short cell values such as dates, ids and statuses never wrap onto two lines (the Since column on Customers was breaking at the hyphen).
- Production order edit form: the Folding Box Size dropdown now lists the Ctn Size table (the copied orders carry #577 / 9 x 12 / R-24 in that field); it listed the Box Size table before.
- Machines page laid out like the site: one full-width table with "+ Add" and "Categories" buttons at the top right, both pop-ups (Add Machine form; the category table with edit / delete and its add row). The two add forms that used to sit under the tables are gone.
- Site Tables: tabs drawn like the site's (blue, active tab boxed), the "Add Location" heading and "Location:" label in the site's plain style.
- Inline forms (Site Tables "Add …" row, the in-row edit forms, PO and schedule row forms) have a gap between label, field and button again; the icon-button change had removed the spacing.
- Customer page: Save, All production orders, New estimate and the red Delete button sit on one row instead of Delete hanging underneath. The Delete button is the same height as the full-size buttons beside it (40 × 40) rather than the 32 px table-row size.
- Sidebar logo is the site's white-on-transparent version (`logo-light.png`), so "COLORADO" no longer disappears into the purple; the purple version stays as the browser-tab icon.
- Sidebar restyled to the production site's measured values (read from its stylesheet and a rendered screenshot): 195 px wide, 79 px logo box in the darker purple (#482165), 14 px regular Rubik links with 14 × 15 px padding, sub-menu and machine blocks at 20 % black over the purple (#522674), the Production Orders group black as on the site, and hover / current page turn the text blue (#009efb) instead of darkening the row.
## v0.7.3 — 2026-10-24 — Price-guide prices stay valid for 45 days; the five drawn dies verified; demo estimate
Changed
- A paper price read from the published Kelly Spicers price guide is dated by the guide's cover date (10/13/2026 for the current book), so it no longer goes "stale" after 7 days like a live store crawl would. Price-guide prices stay valid for 45 days (`KELLY_SPICERS_PRICEBOOK_STALE_AFTER_DAYS`); crawl prices keep the 7-day window. Estimates and quotes on guide prices no longer carry the STALE note and no longer drop to "Price needs review" for that reason alone. Test added.
- Delete buttons are filled red with a white trash-can icon, the same weight as the filled blue pencil.
Data
- All 56 dieline PDFs for dies 148, 162, 323, 340 and 370 are verified and each die has its plain 1-up as the current drawing that quotes append. Each drawing was rendered and its own "Die: N" line, envelope size and flat size read against the die record first; two flat-size disagreements between drawing and spreadsheet (dies 340 and 370) are listed in docs/KNOWN_GAPS.md for Don.
- Demo estimate EST-00004 (Mittera, die 323, 10,000 pieces, 2-up on 25 x 38 Williamsburg Offset 60# at the Spicers list price) and its internal quote CE-Q-00004 with the verified dieline attached.
- docs/CLIENT_REVIEW_GUIDE.md rewritten for Monday's walk-through (login with a password, what is real, what to try, what we still need).
## v0.7.2 — 2026-10-24 — Vendor address / contact suggestions; uniform action buttons; trash-can deletes
Added
- Vendors page: when a vendor has no address or no contact, a suggestion shows in red italics with ✓ accept / ✗ reject. Sources: the vendor's purchase orders (address cell, print-view contact fields) and recorded lookups, each labelled with where it came from. Accept writes it into the vendor and audits it; reject hides it for good. "look it up ↗" opens a web search when nothing is on file anywhere. A sync re-scans purchase orders for new suggestions. Schema 011; `ce/vendor_suggest.py`; tests.
Changed
- Vendors page actions are four equal-width buttons: Edit Vendor, Contacts (n), POs (n), Delete. Contacts is amber at zero contacts and green at one or more.
- Every Edit button is a filled blue square with a white pencil icon and every Delete button a filled red square with a white trash-can icon (32 × 32, the height of the small text buttons, icon centred, Bootstrap Icons shapes) with a plain "Are you sure?" confirmation (vendors, contacts, customers, machines, categories, out tables, site tables, documents, estimate lines, PO item rows).
- Vendors and customers sort case-insensitively (lower-case names such as "accent wire tie" sat after Z before). Vendor names are matched with spacing collapsed and case ignored when syncing, so the site's "BEST CUTTING DIE (BCD)" and the PO list's single-spaced spelling are one vendor; the live copy's duplicate was merged (118 vendors, same as the site).
## v0.7.1 — 2026-10-24 — Vendors page laid out like the site; contacts open to everyone
Changed
- Vendors: # / Name / Address / Actions, with Edit Vendor and Contacts as pop-ups on each row (contacts: # / Name / Email / Telephone / Telephone Ext / Fax / Cellphone, every row editable, add form) and Delete, Add Vendor at the top. Search and the PO count stay as small extras.
- Contact details (vendor and customer contacts, bill-to / ship-to blocks, the Contact section of a production order, the raw order fields and the order API) are visible to every logged-in user, as on the production site, which has no roles. Admin still gates imports, approvals, the audit log and the Users page.
## v0.7.0 — 2026-10-24 — The rest of the site's editing, PO e-mail, and the site's table look
Added
- Out tables (Diagonals, MO Booklet, MO Catalog, Side Seam, Adjustable, WEB-RA): Add row, Edit, Delete on every row; die numbers matched to the die library like the site's rows.
- Customers: Add Customer, edit name / status, Delete, contacts add / edit (admins) / delete; renames flow to the estimator record and the orders.
- Vendors: Delete vendor, Edit contact (admins).
- Machines page rebuilt: add / edit / delete machines and machine categories; the sidebar and schedules read the machine table (disabled machines drop off the sidebar; a rename follows through to the schedule and the jobs).
- Purchase order e-mail from the server when SMTP_HOST / SMTP_FROM (and optional SMTP_USER / SMTP_PASS / SMTP_PORT / SMTP_TLS) are set in .env: PO as HTML body, its documents attached, note on top, audited. Without SMTP the Email box still opens your mail program. `ce/mailer.py`.
- Sync keeps every local change: edited rows are skipped, deleted site rows stay deleted (tombstones), rows created here survive. Schema 010; `ce/site_edits.py`; 6 tests.
Changed
- All tables now use the production site's look: bordered cells, alternating row shading, bold header, hover highlight; a row being edited is tinted.
- Customers list: status reads Active / Inactive, Edit link per row, Add Customer button.
- Machines page lists machines in the site's order (by its database id) with the row number in the # column, as the site does; the site id shows as a tooltip. Machine categories: the site's nine (Converting, Cutting, Jet, Latex / PS, WR, MOW, Straight Knife, MO, RA) with # / Category / Routing Yes-No / Edit / Delete, status shown as Enable / Disable text like the site.
## v0.6.0 — 2026-10-24 — Production orders and machine schedules are editable here
Added
- Production order edit page (`/production/orders/{n}/edit`, Edit buttons on the list and detail pages, Previous / Next): the site's edit form rebuilt from the order record's own 190 field names in nine groups, with the Site Tables and machine list as dropdowns; Contact fields admin-only. Update recomputes pieces / extended value with the loader's code and audits each changed field. **Add New Production Order** (numbered after the highest on file, placed on Converting Unscheduled). Documents per order (upload / open / delete). `GET /api/production/orders/{n}`.
- Schedules are editable: per-row Save (sequence, machine, location, stock due, date due, ship via), Move to another machine or unscheduled list, Add order to list, Renumber 1…n, Print view. New sidebar entries Latex / PS Unscheduled, Straight Knife Unscheduled and **View Schedules** (every machine on one page, like the site's).
- Sync keeps local work: orders edited or created here are skipped by `prodsite_load.py` (upsert, no more delete-and-reload), and a schedule list with any local change is kept whole. Badges say which rows differ from the production site.
- Schema 009 (origin / edited_at on orders, edited_at on list rows, `prod_order_documents`); `ce/orders.py`; 5 tests.
Changed
- "Read-only copy" banners on orders and schedules are gone; the production site remains the system of record until the cut-over, so edits made here do not reach it.
## v0.5.1 — 2026-10-24 — Dielines grouped by imposition; verify a whole die at once
Added
- Die page: the dieline list is now one row per imposition (1-up on 24 x 17 7/8, 2-up on 25 x 38, 2-up on 28 x 40 …) with the artwork variants (plain, face bleed, full bleed, inside tint) as chips beside it, each with open / status / Verify. A summary line says "N drawings of this one die, V of N verified" so 12 files no longer read as 12 unrelated matches. The preview picks the current file, else the plain 1-up.
- **Verify all N as die #…** button (die page and Admin › Dieline matches) for admins: verifies every filename-suggested PDF of that die in one click and makes the plain 1-up the current file that quotes append (if nothing was current). Each file still gets its own audit row; unmatched, rejected and other dies' files are untouched. `POST /api/admin/dielines/die/{die_id}/verify-all`.
- Admin › Dieline matches is grouped by die, then by imposition, with the verified count per die.
- Reference sheets (printed number-out workbooks) are labelled "not dielines" on the die page; they never counted as dielines in the app's numbers and nothing about them needs verifying.
- `ce/dielines.py` (grouping, preview choice, verify_all); 3 tests.
## v0.5.0 — 2026-10-24 — Per-user passwords
Added
- Login is now name + personal password. Passwords are stored as bcrypt hashes on `app_users`; at least 10 characters, 72 at most, no leading/trailing spaces (`PASSWORD_MIN_LENGTH` in `.env`).
- Admin › Users: **Set temp password** per row (logs the person out everywhere, forces them to choose their own at next login), **Unlock**, **Log out** (ends every session of that user), password state badges; **Add user** now takes a temporary password. `PATCH /api/admin/users/{id}` accepts `password`, `logout`, `unlock`. API and page never return the hash.
- **Choose / change your password** page at `/account/password` (click your name top right). A user with a temporary password is held on that page until they set their own. Changing a password re-issues this browser's cookie and logs out every other device.
- Login throttle: five wrong passwords lock the name for 15 minutes (`LOGIN_MAX_FAILED`, `LOGIN_LOCK_MINUTES`); unknown names get the same generic message and cost the same bcrypt check.
- Session cookie carries the user's `session_version`; any password change or forced log-out invalidates every cookie of that user. Cookie gets the `Secure` flag when `APP_BASE_URL` is https or `APP_COOKIE_SECURE=1`.
- First-time setup: while no active admin has a password, `/login?setup=1` accepts name + `APP_ADMIN_PASSWORD` + a chosen personal password and creates the first admin. After that the `.env` password is inert.
- `scripts/set_password.py` — workstation recovery: list users, set a temporary password, create a user, switch admin on; for when every admin is locked out.
- Schema 008 (password_hash, must_change, session_version, failed_logins, locked_until, password_changed_at); 6 user/password tests (103 total); `bcrypt` added to requirements.
Changed
- Name-only login is gone. Every existing user needs a password: the first admin bootstraps with the setup password, then sets temporary passwords for everyone else on Admin › Users (or `scripts/set_password.py`). Cookies issued before 0.5.0 are rejected, so everyone logs in again after this update.
- Field Guide: new "Logging in and passwords" section; Users section rewritten.
## v0.4.2 — 2026-10-23 — Reachable from any tailnet machine; starts itself at logon
Added
- The app is published on the Hampden Tailscale network by `tailscale serve` on the workstation: **http://ag-thinkcentre.tailb0bdf2.ts.net/** (also `http://ag-thinkcentre/`). Tailnet members only; nothing is opened on the LAN or the internet, and the app itself still binds 127.0.0.1:5150. Use the name, not the IP (the proxy answers 404 to a bare-IP request).
- Scheduled Task "Colorado Envelope Estimator" starts the app hidden at logon (`pythonw scripts\run_service.py`, log in `data\app.log`), so the link works without a terminal open. `scripts\run_service.py` is new; `run.cmd` is unchanged for a visible console.
- Field Guide: "Reaching the app from another machine" section. No schema or app-code change.
## v0.4.1 — 2026-10-22 — Users page with a per-user admin switch
Added
- Admin › Users: every name that has logged in is listed with an ADMIN / estimator switch and an Active ON / off switch, plus an Add user form. Switches apply on the person's next click (no re-login); switching Active off ends their session and refuses the name at login. The last active admin cannot be switched off. Every change is audited (object type `user`).
- The admin password in `.env` is now only the bootstrap: logging in with it makes that user an admin permanently; afterwards admin is granted or removed on the Users page. `GET /api/admin/users`, `PATCH /api/admin/users/{id}` {is_admin, active}.
- Schema 007; 2 tests (99 total).
## v0.4.0 — 2026-10-22 — Purchase orders, vendors, customers and site tables are editable here
Added
- Purchase orders: open any PO (or Add New Purchase Order) and edit the same fields as the production site's edit page — Today's Date, Date Required, To (vendor), Address, Contact, Email, Phone, Extension, Fax, Cellphone, Shipping Company, Ship (Pickup / Ship Via), For, line items (Production Order#, Quantity, Description, Price, Received, Date), Ship To, Comments, Entered checkbox. Previous / Next, PO number jump list, Print, Copy, Email (opens your mail program), Documents (upload / open / delete). Local print view matches the site's layout. `GET /api/production/purchase-orders/{n}`.
- Line items, Ship To and Comments for every existing PO come from the site's public print views (`scripts/prodsite_po_print_fetch.py`, read-only). Site rows are the baseline; a PO saved here is stamped "edited here · differs from production site" and later syncs leave it alone. POs created here are numbered after the highest site PO; a later site PO with the same number is logged in the audit, never merged.
- Vendors: full records (address, contacts) on the list, a vendor page with Add New Contact / delete, editable address and main email/phone, Add Vendor, "New PO to this vendor". Searching matches contacts too.
- Customers: contact lists pulled from the site's customer records (`scripts/prodsite_customers_fetch.py`, 297 of 300 have contacts); click a customer for contacts, bill-to / ship-to from the latest order, totals and the full order history. The estimator's customer records got their first contact filled in.
- Site Tables: laid out like the site — one tab per table, ID / value / Actions with Edit and Delete on every row and an Add form under each table. Deleting a site row leaves a tombstone so a sync does not bring it back; additions and edits are kept too.
- Every table in the app sorts by clicking a column heading (numbers, dates and text); the arrow shows the direction.
- Schema 006; 8 tests (97 total).
Changed
- The read-only PO detail page and the "Edit on production site" / "Print ↗" links to the site are gone; edit and print happen here.
- Vendor and customer contact email / phone remain admin-only on screen (names, addresses and companies show to everyone).
## v0.3.3 — 2026-10-22 — Spicers Inventory page
Added
- `/spicers`: every Kelly Spicers stock with search (SKU, name, weight, size like "23 x 35"), section filter (incl. "paper sections only"), shown/hidden filter, paging.
- ON/off toggle per stock and "Show all / Hide all matching" for the current filter. Hidden stocks disappear from the estimator's paper dropdown and the materials API; the flag survives catalog reloads; every change is audited with who/when.
- Catalog upload: drop a new pricebook .zip or .pdf on the page (signature-checked, stored privately) and it is parsed and loaded like the download; "Download latest Denver guide and reload" button too. `POST /api/admin/prices/pricebook` for scripts; `PATCH /api/materials/{id}` {active}.
- Materials now carry section, pack, price kind (LIST) and guide date (schema 005).
- 4 tests (89 total).
## v0.3.2 — 2026-10-22 — Kelly Spicers price guide loaded (2,639 SKUs)
Added
- Price-guide parser (`ce/pricing/pricebook.py`): downloads the published Denver pricebook zip (no login), reads every SKU with size, basis weight, M-weight, pack, color, family, section and list price per unit; creates/updates a material per SKU and records prices as observations dated with the guide (10/13/2026).
- "Sync Kelly Spicers pricebook now" button on Paper Prices (admin), last-run summary; materials list capped at 200 without a search.
- Estimate page: stock picker now shows stocks on the chosen parent sheet (either orientation) with a search box over the whole guide; prices labelled as list prices.
- 5 tests on the real guide (85 total).
Changed
- The store crawler button is marked experimental (product pages error on the store side).
## v0.3.1 — 2026-10-22 — Kelly Spicers collector + sync button
Added
- Built-in Kelly Spicers price collector (`ce/pricing/kellyspicers.py`): walks the online.kellyspicers.com catalog with a browser session cookie (`KELLY_SPICERS_COOKIE` in .env; no password stored), parses product pages (SKU, size, weight, price, unit basis), saves sample pages for tuning, creates materials per SKU and ingests prices as observations.
- "Sync Kelly Spicers prices now" button on Paper Prices (admin) with live progress and last-run summary; `scripts/kellyspicers_sync.py [--limit N]` for the command line; status API.
- 6 tests on synthetic WebSphere pages (80 total).
Known
- Not yet run against the real store (needs the cookie and a person to run it); parser expected to need tuning after the first trial. See docs/pricing_feed.md.
## v0.3.0 — 2026-10-22 — production orders, out tables and schedules copied
Added
- Jordan ran `scripts/prodsite_sync.py`; the loader now uses the site's real field names. Loaded: 7,889 production orders (2022-2026, full records), 3,000 number-out rows across the six Out tables, 21 machines with sidebar groups, 118 vendors with contacts, 14 schedule lists in the site's order.
- Production Orders page (search by customer, job, PO, die #, description; filters Not complete / Not invoiced / year), order detail with every site field (contact fields admin-only), Not Invoiced as a real list (579).
- Schedules and Unscheduled lists show the site's jobs in their order, linked to our die pages.
- Die page: production history (count, date range, pieces, last orders, quoted prices) and the site's number-outs for that die. Estimate page: what this die has run before (last quoted prices per M) and the customer's recent orders.
- Dashboard charts computed from the orders (pieces by due month, extended value, this week). Quantity-in-thousands and per-M price semantics documented (docs/production_site_mirror.md).
- Schema 003/004 (prod_orders, prod_order_lists, qty_m / pieces / extended_value); tests updated (74 total).
## v0.2.0 — 2026-10-22 — production-site mirror and matching look
Added
- Read-only copy of production.coloradoenvelope.com (authorized by the project owner): 2,665 purchase orders (2,453 "Not Entered"), 114 vendors, 300 customers (now also in the estimator's customer list), 15 machines, 15 site lookup tables (462 values), window double-die list (238, from the client's .xls). Scripts: `prodsite_parse.py`, `prodsite_load.py`, `prodsite_sync.py` (the POST data reads — not yet run, see docs/production_site_mirror.md).
- New pages mirroring theirs: Purchase Orders (+ Not Entered, search, vendor filter, detail with print/edit links to the live site), Vendors, Customers, Machines, Site Tables, Window Double Die, Out Diagonals / MO Booklet / MO Catalog / Side Seam / Adjustable / WEB-RA (site rows when synced, otherwise the same view built from our imported yields), Converting / Jet Unscheduled and per-machine schedules, Production Orders and Not Invoiced placeholders (those two need their login).
- Dashboard with their three charts (Monthly Total Envelope, Gross Sales, This Week) plus a purchase-orders-by-month chart that is live now.
- Estimate page: "Recent paper purchase orders for <sheet>" — what was really bought for that parent sheet, from the PO copy.
- Theme switched to the production site's: Rubik, purple sidebar #673091 with their menu order and logo, white top bar, #edf1f5 content, white cards.
- Schema 002 (prod_* tables), 3 new tests (74 total), docs/production_site_mirror.md.
Known
- Out tables, schedules, live machine list, vendor records and the dashboard schedule charts fill only after `scripts/prodsite_sync.py` is run; Production Orders / Not Invoiced need the site login.
## v0.1.0 — 2026-10-22 — October 22 prototype
First working build, from the client files delivered 2026-10-21 (Don.zip) and the Oct 20 discovery meeting spec.
Added
- Die library imported from 18 number-out workbooks, the cabinet list, the by-number list and the "For Website program" tab. Die identity = number + finished size + family (open side / open end / diagonal), so same-size dies stay distinct and the same number in several lists merges correctly.
- Exact fraction handling (4 1/8, 13/16, 220mm x 320mm); original source text kept beside every normalized value; "Not provided" never shown as 0.
- Cabinet slots from the cabinet workbook win over number-out "Loc-" cells; a die stored in several slots lists all of them; disagreements are logged.
- Number-outs per parent sheet (plain stock), "0 out" recorded as a does-not-fit note, conflicting yields between sources flagged and the die marked needs-review.
- 56 dieline PDFs registered from filenames as *suggested* matches (dies 148, 162, 323, 340, 370); 20 number-out PDFs attached as reference sheets; admin verify / make current / re-assign / reject with audit trail.
- Estimator: quantity ÷ known number-out with ceil, setup sheets added once, optional waste as fraction or extra blanks; paper cost per sheet / per M / per CWT (needs M-weight) / per ream, otherwise "Unit conversion needs review"; manual paper price with reason; manual cost lines with reason; Not-configured placeholders for every other cost category; markup and margin both shown and labelled.
- Legacy quoting workbook: all 14 "Prices" tabs imported (cached values + formulas, 83 broken #REF!/blank cells reported) and a transparent re-creation of the QUOTES macro (convert, print, stock ÷ number-out, waste, negs, latex, peel & seal, flat charges) with the source cell beside each number — shown as an unverified cross-check, never added to costs.
- Quote revisions with immutable snapshots (die, yield, material, price observation + timestamp, cost lines, formula version); internal worksheet and customer PDFs; PROTOTYPE watermark until approved; dieline appended + ZIP package; missing/unverified dieline clearly stated.
- Supplier price adapter (fixture | file | http) with schema validation, deduplication, freshness, SAMPLE labelling and an unmatched queue. Kelly Spicers crawler NOT connected — see docs/pricing_feed.md.
- Local auth (name + optional admin password), CSRF on forms, upload signature/size/encryption checks, private file storage, audit log, import dry-run.
- Field Guide (/guide), this change log (/changelog), docs/ set, 71 automated tests.
Known
- No converting/printing/labor/consumable costs; no validated spoilage rule; no real supplier prices; approval disabled. See docs/KNOWN_GAPS.md.