MirrorFactory OS
A shop-floor board I built for a framing workshop's iPads. The floor kept its TV, and that taught me more than the board did.
Archived. Its pipeline synced the workshop's orders to Airtable from 1 July to 2 August.
- my part
- Designed and built, self-initiated
- when
- January to July 2026
- context
- The Framing Project, Sydney
- team
- Solo, with coding agents doing much of the typingThe Framing Project photographs of the workshop and the mirror
- status
- Archived

outcomes
1
paid sync service marked cancelled in July, the month my pipeline began syncing orders to Airtablereported · sourceMy operations guide for the workshop (the TFP Operations Guide, v2.1, 'Business at a Glance' table, as read on 1 July 2026: the bridge's monthly cost struck through, 'Cancelled (July 2026)'), and the MirrorFactory OS commit of 1 July 2026 that made the Airtable sync a step of the pipeline.
3 s
Firestore query behind the mirror portal's data, replaced by a lookup cached in memorymeasured · sourceTimed by my AI assistant, and recorded in my MirrorFactory OS commit of 3 June 2026 (ffde98b): its message gives the 3 s, and its code comment says the mirror orders are now served from an in-memory cache instead of a 3 s Firestore query. The commit keeps no benchmark.
On 10 February I had my assistant check nine overdue invoices against a copy of the workshop’s order data. None had a glass order recorded, and one was still a quote. Out on the floor, mirrors got moved from trolley to trolley again and again while someone searched for stock. I’d been building the system behind it since 12 January, first as an invoice reader for the workshop’s Airtable, and by May it was a board for the floor’s iPads. This is what I designed into it, what the floor did with it, and the lesson I took into Reframed.
February: nine overdue, no glass
Nine overdue invoices · glass order on record?
- Invoice 1: no glass order on record
- Invoice 2: no glass order on record
- Invoice 3: no glass order on record
- Invoice 4: no glass order on record
- Invoice 5: no glass order on record
- Invoice 6: no glass order on record
- Invoice 7: no glass order on record
- Invoice 8: no glass order on record
- Invoice 9, still a quote: no glass order on record
0 of 9
The workshop tracked its orders in Airtable, and when one changed, nobody on the floor was told. Stock got ordered twice, jobsheets were out of date, and the CNC sat three metres from the spray area in the same room, so its dust landed on frames waiting for paint.
After the check I set up an alert that messaged me at 8 each morning with the jobs due within two days. It ran for four mornings, and on the 14th I asked for it to stop. When the ownership changed later that month, my assistant drafted the floor up from what I told it as a four-phase proposal: a partition for the dust, an inventory overhaul, jobsheets and assembly, and a print and cut station.
One card, one line
decision 1
One card for each line of an order
First board: a card per part
- Mirror glassOrdered
- Metal shelfOrdered
- Flat barOrdered
- LED stripQueue
- LED driverQueue
qty shown5ordered 1
Rebuild: a card per line
Metal-framed LED mirror
line 1 · copies 1
5 parts underneath: glass, shelf, flat bar, LED strip, driver
My first board made a card for every part. A metal-framed LED mirror is glass, two frame parts, an LED strip and a driver, so one mirror put five cards on the board and the job got lost among them. The count was wrong too: it added up the parts and showed 5 where the order said 1. On 31 May I rebuilt the board around the line of the work order, with the parts folded underneath and the count taken from the order’s copies. That’s how the floor already talked about a job.
5 → 1cards per order linereported · sourceMirrorFactory OS HANDOFF.md, resolved bugs, 31 May 2026: '5 BOM cards per sub-line', fixed by production units, '1 card per sub-line'; 'Quantity showing 5 instead of 1', fixed by line_copies = max(Copies).after the rebuild of 31 May
decision 2
The glass moves the card

the glass moves the cardsourcelib/productionUnits.ts, pickDriverComponent: the glass if there is one, else the frame
flange and polessourcescripts/consolidate.py: a suspended mirror's parts include a flange and poles, made in the workshop
the glass moves the cardsourcelib/productionUnits.ts, pickDriverComponent: the glass if there is one, else the frame
flange and polessourcescripts/consolidate.py: a suspended mirror's parts include a flange and poles, made in the workshop
Each card follows the route for its kind of mirror. A suspended one like this goes Queue, Cutting, Welding, Fitting, Spraying, Assembly, Pack Together, Ready. One part decides where the whole card sits: the glass if there is any, else the frame. The glass comes from a supplier, so a mirror’s card waits at Ordered until it’s in, and the manager’s view keeps a tab for jobs with an item on hold, cancelled or not yet ordered.
on the photograph
- the glass moves the cardlib/productionUnits.ts, pickDriverComponent: the glass if there is one, else the frame
- flange and polesscripts/consolidate.py: a suspended mirror's parts include a flange and poles, made in the workshop
Here is that card as a drawing you can use, with sample data and the same rules as the code. Claim it when the glass is in, and every move after that writes a row to its history. At Ready the floor’s own flag goes on and the job joins the office’s queue. Its quote, ordered or invoiced status doesn’t change, because finishing a mirror and invoicing it are different facts.
Its route
- Ordered
- Queue
- Cutting
- Welding
- Fitting
- Spraying
- Assembly
- Pack Together
- Ready
Suspended mirror, black frame
line 1copies 1driver: glass
- floor complete
- no
- order status
- ordered, not invoiced
Its history, one row a move
- nothing yet: waiting on the glass
The size is in the words
decision 3
Read a mirror's size from its description
The order line, as the office typed it
Suspended mirror, 600mm wide x 1500mm high, silver, rounded corners, black metal frame, on two ceiling poles
The size fields
- source
- framing software
- trusted
- not for a mirror
The parser, first
- width
- 600 mm
- height
- 1500 mm
The batch pass AI
- shape
- rectangle, rounded
- tint
- silver
- lighting
- none
- then
- it can go to a review tab
Mirrors were quoted in framing software, so the size fields on a mirror’s order couldn’t be trusted. The real size was in the description, written as 1400mm wide x 1000mm high. My pipeline reads the size from those words, and a batch AI pass reads shape, tint and lighting from the same line. Anything the AI read is badged AI in the mirror list. From 3 June the board had a review tab where someone in the office could check a mirror’s specs and confirm them, and a read with low confidence was flagged there with its percentage.
A dusty floor, and no hover
decision 4
Big targets, and nothing that waits for a hover
At the start of June I ran a floor audit over the mirror portal’s code. It put its selects at about 24 pixels high, going by their styling, against a 44 pixel minimum, and its line on the Wi-Fi was blunt: when it drops, the screen goes blank and the worker goes back to paper. Finding a job meant typing its number into another view. On 2 June most of the portal’s controls, the selects the audit named among them, got a 44 pixel minimum and the mirror list got a stage column. The manager’s Done button was the same kind of mistake, and I’d fixed it on 31 May: it only appeared on hover, and an iPad has no hover.
24 → 44pxreported · sourceFloor UX audit, June 2026 (.understand-anything/proposals/phase-c-workshop-ux.md: selects at about 24 px, estimated from their CSS classes, against a 44 px minimum); commit of 2 June 2026, 'Touch: min-h-touch/min-w-touch across all mirror-portal components', min-h-touch = 2.75rem in tailwind.config.js.a supplier select in the mirror portal, from the June audit's estimate to the minimum I set
The floor kept its TV
The framers never trialled the board. The mirror team had an iPad and the framing team had the TV. On 30 June my own operations guide for the workshop listed the iPads as not in use. The framers worked from an Airtable view on that TV, where they saw new jobs and picked their own. A paid monthly service copied orders from the quoting software into that Airtable.
On 1 July the sync became a step of my pipeline: scrape the orders, consolidate them and push them to a new table in the workshop’s Airtable, at 6, 12 and 4 every day. The TV’s Airtable view read from that table, so my pipeline fed the floor’s TV. My operations guide marks the paid bridge as cancelled in July. The pipeline ran until 2 August, and the board’s service is switched off now.
To 30 June
- Quoting software
struck out: A paid bridgea monthly feemarked cancelled in July- Airtable
- The TV on the floorframers pick their own jobs
From 1 July
- Quoting software
- My pipelinescrape, consolidate, sync · 06:00, 12:00, 16:00
- A new Airtable table
Beside them
- My board, built for iPadsiPads “not in use”, 30 June · pipeline paused after 2 August
I built the board for iPads on a floor that already had its screen. Mine was one more place a job lived, beside the quoting software, the CRM, Airtable and the TV. That’s the lesson I took into Reframed, which is built to put the floor on the same record as the order.
