← all work

Ataya

A phone case on a shelf in Sharqia stays on the books until a hand takes it off the shelf. Ataya is the one-process system that holds that line for a small shop: a counter that sells offline, a cash-on-delivery web store, a repair desk and an in-shop kiosk, on one append-only ledger. Built in eight days.

Role
Sole developer and designer: Bun, SQLite, Preact, the «صنعة» design system
Span
Sep ’26 — now
Kind
Self-initiated · product
Status
ongoing
Stack
Bun · SQLite · Hono · Preact · PWA · Offline-first · FIFO costing · Cash on delivery · RTL

A case on a shelf

Tracking systems answer where is it now? for something that moves: a driver, a parcel, a route. Ataya asks the same question about something that doesn’t move until somebody picks it up, a phone case on a shelf in Sharqia. The answer is the same kind: it is where the last real event put it.

Behind that is one Bun process and one SQLite file, serving four surfaces: the counter, a Preact PWA that sells offline; the web store, in Arabic, cash on delivery, with no account to create; a repair desk; and a kiosk tablet inside the shop, turned to face the customer. 52 commits between 2026-09-18 and 2026-09-25, 17 migrations, 259 tests, eight decision records. Live at ataya.kariem.dev.

Every screen below comes from a demo shop: the product’s own sample catalogue on a scratch database, driven through its own API. The names, numbers and prices are made up; the behaviour is the real code.

The customer starts with their phone

A shop like this doesn’t sell “cases”. It sells a case that fits this phone. So the store’s first question isn’t a category, it’s the customer’s phone.

The store's home page on a phone: an offer banner with a brass seal, the question «موبايلك إيه؟», what's your phone?, with a ruler drawn under it and buttons for All, Android and iPhone, then three promises: we call you first, you pay on delivery, you can exchange in the shop.
the store · first screen
The iPhone page: the heading «حاجات ايفون», iPhone things, the line «دي كل حاجة بتركب على موبايلك», everything that fits your phone, and product cards each tagged original or copy.
after picking iPhone
The ruler, المسطرة, sits under «موبايلك إيه؟», what's your phone?, and nowhere else. Pick iPhone and the whole shelf narrows to «كل حاجة بتركب على موبايلك», everything that fits your phone. Every card also says whether the piece is original or a copy, because customers here ask that before they ask the price.

The customer gets a promise

In Egypt most web orders are paid on delivery, and many never happen. The number doesn’t answer, the customer changes their mind on the call, or the parcel is refused at the door. So the first thing Ataya gives a customer is not a shipment. It is a promise, and the screens say so.

A product page: a drawn icon of a phone case labelled «رسم توضيحي — مش صورة الصنف», illustration, not a photo of the item, then the name, a price of 90 EGP, and «متاح — يتشحن بعد ما نكلمك», available, ships after we call you.
before · the product
The page right after ordering: a brass seal reading «وصلنا», we got it, the heading «تمام، أوردرك عندنا», the amount due on delivery, and six steps joined by a dashed stitch, only the first one done.
after · the order
Left: there is no product photo yet, so the page says so, «رسم توضيحي — مش صورة الصنف», illustration, not the item, and promises to describe it exactly on the call. Right, after ordering: the page promises a call to confirm the item and the address before anything ships. Nothing has left the shelf yet.

The store also refuses promises the shop can’t keep. A first order paid on delivery is capped at 1,500 EGP, checked on the server. Past that, the customer pays part up front, because a shop this size can’t absorb a large refused parcel from someone it has never met.

The link on that page is the customer’s receipt. There’s no account and no password. It carries a token, and it keeps telling the truth as the order moves:

The tracking page for an order out for delivery: a dark header reading «في الطريق ليك», on its way to you, the amount due on delivery, and five of six steps done, the current one annotated with a reminder to have the cash ready.
with the courier
The same page for a delivered order: the header reads «وصلك — بالهنا», it reached you, enjoy it, and all six steps are filled.
delivered
The step with the courier carries one practical line: have the amount ready if you're paying cash. Delivered reads «وصلك — بالهنا», it reached you, enjoy it. It's the only step where the shop books the revenue.

Nothing crosses the shelf but hands

Most stock systems take the item out of stock the moment an order is placed. Here that would fill the ledger with events that never happened. The fourth decision record draws the line instead:

“A placed order creates reservations (24 hours), not moves. The stock move happens at packed, when someone takes the item off the shelf.” “Every ledger move matches a physical event.”

The accented edge is the design. Nothing crosses the shelf line except hands.

The seller sees the line from the other side. A confirmed web order is still a promise, so each piece in it shows how many are on the shelf right now, next to what the courier has to collect:

A confirmed web order open on the counter tablet: two items, each with a small line «على الرف 25» or «على الرف 40», on the shelf, the delivery fee for other governorates, a total of 155 EGP, «المندوب يحصّل 155 ج», the courier collects 155, and buttons for the shipping label and a gift copy.
a confirmed web order · counter tablet
«على الرف 25», on the shelf: 25. The count doesn't go down when the order is placed or confirmed. It goes down at packed, when someone picks the piece up.

Below the line the order is a fact, a signed row in a table the database won’t let anyone edit:

the ledger, migration 001
CREATE TRIGGER trg_stock_moves_no_update
BEFORE UPDATE ON stock_moves
BEGIN
SELECT RAISE(ABORT, 'stock_moves append-only: UPDATE ممنوع. سجّل حركة تصحيح جديدة بدلًا من التعديل.');
END;

An edit is refused with its own instruction: record a correcting move instead. A mistake stays visible, next to its correction. On-hand is SUM(qty), so there is no stored number that could drift away from the history.

Cost follows the same rule. Each supplier receipt is a FIFO layer, and a sale consumes the oldest layers first and records which ones it used. A refused parcel goes back into the layer it came from, not into a new one at today’s price. The layers aren’t hidden in the database; every item shows them:

An item's history on the counter tablet: under «الدفعات اللي على الرف (الأقدم بيتباع الأول)», the batches on the shelf, oldest sells first, two rows, 30 of 30 from supplier A at 9 EGP and 10 of 15 from supplier B at 8 EGP, then the item's last moves, each a signed quantity with a time: sale −1, receipt +30.
one item · its cost layers and its moves
«الأقدم بيتباع الأول», the oldest sells first. Five pieces of the 8-EGP batch are gone and the 9-EGP batch is untouched, so profit is a query over things that happened, not an average. Below the layers are the item's moves, signed, each one a real event.

At the counter, the count is blind

The counter is where money and stock move together, so every screen here is about not letting the two drift apart.

The counter's sell wall on a tablet: shelves of product tiles grouped like the physical shop, three tiles outlined in brass with a count badge, a cart listing them, and a brass button «تحصيل 500 ج», collect 500.
the sell wall
The wall is laid out like the shop's shelves, so the seller taps where the piece physically is. Brass appears on exactly two things here: the pieces going into the cart, and the button that takes the money. Brass is for money, and nothing else on the screen uses it.
The collect sheet: a dark panel with the amount due, 500 EGP, a field for what the customer handed over, filled with 600, quick buttons for 500 and 600, a green box «رجّع له 100 ج», give back 100, and the button «استلمت 600 ج — سجّل البيعة», received 600, record the sale.
collecting · change due
The customer hands over 600 for a 500 sale. The sheet does the subtraction, «رجّع له 100 ج», give back 100, and the button says what it's about to record: received 600. The seller doesn't do the arithmetic while the customer waits.

Nothing sells until a shift is open, and opening one means counting the float:

The open-shift sheet: «افتح الشيفت», open the shift, a field for the float in the drawer now, quick amounts 0 to 500, and the hint «الرقم ده بيتطرح من عدّ آخر اليوم — لو غلط، التقفيل هيطلع فيه فرق», this number is subtracted from the end-of-day count; if it's wrong, closing will show a difference.
opening the shift
The hint under the field says what the number is for: it gets subtracted from tonight's count, so a wrong float becomes a visible difference later instead of a silent one. The cart stays ready behind the sheet; the first collect opens the shift.

At closing the seller counts the cash. If the screen shows the expected amount first, the count tends to match it: a miscount gets “found”, a shortfall gets covered, and the variance stops meaning anything.

Closing the drawer, step one of three: one row per Egyptian note and coin from 200 down to 1, three rows filled, five 200s, one 100, one 50, with a running total of 1,150 on the button, and no expected amount anywhere on the screen.
closing · step 1 of 3, the count
One row per Egyptian note, 200 down to 1, so the seller counts instead of estimating. The only total on the screen is what they counted, 1,150. The expected amount isn't here, and it isn't in the server's reply either.

So closing is two calls, in order. submitBlindCount() takes the count and the PIN of someone allowed to close the drawer, closes the shift, and replies with nothing but the closing time. Only after that does revealExpected() compute what the drawer should hold:

Closing, step three: a red panel «الدرج ناقص −605 ج», the drawer is short 605, then the working: opening float 300, plus cash sales 1,655, minus drops 200, expected 1,755, counted 1,150; below it «إيه سبب الفرق؟ (إجباري)», what caused the difference? (required), with reason chips such as float short, unrecorded sale, cash refund, unrecorded drop, miscount.
closing · step 3 of 3, the reveal
The difference comes with its working shown: float, plus cash sales, minus drops, against what was counted. A difference can't be closed without a reason; the sheet refuses with «ده اللي بيبني سجل الأمانة», this is what builds the honesty record.

The counter sells whether the network is up or not. Sales wait in an outbox and sync one at a time when it comes back, and two unique keys on the server, (device_id, local_id) and (device_id, local_seq), make a resend harmless. A flaky connection can’t turn one sale into two.

A phone left at the desk

Half of a phone shop’s trust is the repair desk: someone leaves a phone that holds their whole life and walks out with a paper slip. Ataya gives them a link instead of the slip, and a rule before any work starts.

The repair page on a phone: a brass seal reading «صيانة», repair, the heading «صيانة الموبايل», the line «سيب موبايلك عندنا. هتاخد كود تتابع بيه، وهنكلمك قبل أي مصاريف», leave your phone, you get a code to track it, and we call you before any cost, then four numbered steps and a form.
asking for a repair
A repair ticket's tracking page: ticket ص-1003, Oppo Reno 8, a dark header «جاهز — تعالى استلمه», ready, come and get it, with the cost 250, paid 100, remaining 150, then the steps with times and the warranty terms.
the ticket, ready
Left, the rule: «هنكلمك قبل أي مصاريف», we call you before any cost. Right, the customer's own ticket: what it costs, what they already paid, and what's left, 150, before they walk in.

Inside the shop, the same ticket holds what the technician needs and hides what nobody else should see:

A repair ticket open on the counter tablet: iPhone 12, customer name and phone with call and WhatsApp-receipt buttons, a four-step rail with received and fixing done, then the fault, the passcode shown only as «•••• اظهره», show it, the IMEI, the condition on arrival «خدش في الضهر», scratch on the back, what was left with it, a starting price, the deposit and a 30-day warranty.
the ticket at the desk
The phone's passcode is stored because the technician needs it, and shown only as «•••• اظهره», show it, until someone taps. Next to it: the IMEI, the condition it arrived in, and what the customer left with it. Those are the three things a dispute is about.

What the owner reads at night

The book, الدفتر, is what the owner opens after closing. It doesn’t show a dashboard of everything. It answers one question first: did today pay for itself?

The book's day page: today's sales 1,100 EGP in a dark panel, money in and out, then a green panel «غطّيت يومك», you covered your day, with a full bar, goods sold 1,100, today's profit 453, web orders still on the road 645, and a small line showing how the daily target is computed.
the book · today
«غطّيت يومك», you covered your day. The target isn't a number someone typed in. The small line shows the working: the month's expenses, divided by its days, divided by the shop's margin. Web orders on the road are listed apart, because they aren't money until they're delivered.
The book's month page for September 2026: the net for the month so far, −2,120 EGP, in a dark panel with a stitched edge, a bar per day with today highlighted in brass, then sales 5,450, minus the cost of the goods sold 3,420, goods profit 2,030, minus the month's expenses 4,150.
the book · the month
The month says the uncomfortable number in the largest type: −2,120 so far, because rent and power are already paid and the month isn't over. Under it is the working: sales, minus the FIFO cost of exactly what sold, minus expenses.
The stock room's summary: goods on the shelf at wholesale cost, 25,130 EGP, which sell for 41,275, 281 pieces in 23 items; boxes for sold out, running low, and items without a photo; and a sand-coloured box «530 عليك للموردين», you owe suppliers 530.
the stock room
The shelf, valued twice: what it cost, 25,130, and what it sells for, 41,275. Next to it is what the shop owes, 530 to suppliers from a receipt that wasn't fully paid. Stock bought on credit isn't really the shop's yet, and the screen doesn't let the owner forget that.

Every mark has one job

The design system is called «صنعة», craft, and its first law is that nothing on screen is decoration. There are three marks, and each has one job:

  • The seal, الختم, is trust. It goes on three things only: an original product, a real photo from the shop, and an item checked before shipping. It is on the order and repair pages above. If the shop isn’t sure, there is no seal.
  • The stitch, الغرزة, is the divider. It’s the seam of a leather case, and it joins the order steps on the tracking pages. It is never a frame.
  • The ruler, المسطرة, is fit. It appears in one place, under «موبايلك إيه؟», what’s your phone?, because the phone model is the first filter everywhere, before the category.

The colours are named after materials in the shop: coffee-brown ink, kraft paper, sand, brass. Brass is kept for trust and money: the seal, and the button that collects cash. The palette is brown because the street’s colours are taken: Vodafone red, Orange orange, Etisalat green, WE purple, and the chains’ corporate blue.

This site’s card for Ataya is built from the same system: the product card’s 18px radius, a one-pixel ring instead of a shadow, a 2px lift on hover, and the stitch above the role line. The signature, the letter ع inside a phone case, is rendered from the product’s own font file, and the script that renders it is in this site’s repository.

What is not done

  • The shop is small, and the numbers say so. On 2026-09-25 the live database held 82 products, 8 counter sales, 1 delivered web order and 5 shifts. That’s a real shop starting on a real system, not a system at scale.
  • The photos aren’t real yet. Every product shows a drawn icon labelled «رسم توضيحي», illustration, because the brand forbids implying a photo that doesn’t exist. The shoot spec is written: kraft paper behind the product, daylight from a window, square, a hand holding it for scale.
  • Fit data is coarse. Compatibility still says “iPhone · Android”. The ruler needs a brand and model on every case and screen protector to keep its promise.
  • No e-receipts. Egypt’s tax e-receipt system isn’t connected. The shop isn’t registered with it yet, and the integration isn’t built.
  • The repository is private. It’s ready for open source, with fake test data, a public-safe seed, docs and decision records, but it hasn’t been published.