Product plan

Tray: a trading toolkit for small gemstone dealers

Product and design plan. No code.

13 October 2026Working draft for AprilPlan only, no code

Working name Tray (placeholder: dealers shoot on trays, and a memo goes out on one). Plan only, no code. Written as senior PM and product designer. Companion file: the seven key screens, drawn with the design system tokens (final design · first prototype sketch).

0. Decisions and assumptions I made so you don't have to answer them

1. The user, the problem, and where I agree or push back on the framing

The user. A one-to-five person shop in or around JTC. The owner holds the stock, the client relationships and the prices in their head. Phone-first, WhatsApp all day, Excel at best. Often not a native English speaker. They are paid on the next sale, not on tidy records, so any tool that adds work before it adds a sale is dead on arrival.

What I agree with, unchanged

Where I push back

  1. "Thousands of dealers" is a ceiling, not a market. JTC is reported to hold roughly 300 shops (a travel-site figure, unverified; the building also houses the AIGS and GRS labs and the Thai Gems & Jewelry Traders Association). Add Mahesak, the Silom and Surawong side streets, and the weekend Chanthaburi traders, and a reachable Bangkok number is plausibly 1,000 to 2,500 trading entities. Count doors in week one. Below ~800 reachable shops this is a good small business, not a venture-scale one; the cross-dealer data is the only part that scales beyond it.
  2. Dealers hide stock from other dealers. In this trade a visible stock list is a negotiating weakness: competitors see what you hold and what you can't move. "Public by default" will scare the very people we want. I keep the brief's default (public specs, no prices) because it is what makes a link shareable, but onboarding asks one plain question per dealer, "Who can see your catalog?", with three icons: anyone with the link / only my clients / nobody yet. The whole-catalog lock is a first-class option, not a footnote, and I expect a third to half of dealers to pick it.
  3. A memo is a legal and trust object, not only a list. In the trade, title stays with the consignor and the stones are usually at the receiver's risk. A slip with photos is a real improvement, but a disputed memo is decided by who can prove what was handed over. So the slip must be a frozen snapshot (section 4) with its own link, and "receiver confirms" is not a later-version luxury; I'd pull a one-tap "I received these" link into phase 3.
  4. $20 to $50 a month is believable only if the first value is free. Dealers will not pay before they have seen it work on their own stones. Pricing is built around that (section 2).
  5. Scope. The v1 list is eight app features plus a client web app with auth. That is a large product. The prototype (section 3) is deliberately a quarter of it.
  6. The biggest copycat risk is not GemCloud. It is WhatsApp Business's own catalog feature (product catalogs and collections, inside the app dealers already live in). It has no stone-specific fields, no per-client price visibility, no consignment, no dashboard, and caps item counts, but it is free and one tap away. We must feel better than that, not just different.

Competitors, assessed honestly (limited web check on 2026-10-13; verify with real dealer interviews before relying on it)

AlternativeWho it servesCostWhy a one-man JTC shop isn't on it
Excel + WhatsAppEveryoneFreeIt is the incumbent. It wins on speed and zero setup. It loses on search, memo tracking, numbers.
GemCloudGem and jewelry businesses; listed availability Canada, Hong Kong, United States; founded 2018; barcode labels, consignment tracking, purchase orders, reportingListed from ~US$90/month (Capterra; FitGap lists ~$76). Treat as unconfirmedAt ~3x our price point, desktop-style workflows built around barcodes and purchase orders, no local presence or onboarding in Bangkok. I found no evidence of WhatsApp-native sharing or client catalog logins; I could not confirm their absence.
Diamond tools (RapNet, GemFind)Diamond dealersSubscriptionBuilt on a grading scale colored stones do not have.
Jewelry ERPs (JewelMate, Jewel360, Zoho, Odoo)Retail jewelers with staffMonthly + setupToo many fields, desktop first, for a business that is one person and a safe.
WhatsApp Business catalogAny small merchantFreeNot stone-aware, no private prices per client, no memo, no numbers. The real benchmark.
MarketplacesBuyers hunting stonesCommissionDealers do not want to be commoditized or have their margins visible; this is the opposite of "my clients, my prices".

2. Business case

Who pays and how much. The dealer pays. Clients never pay.

PlanPriceIncludesWhy this line
Free$0App, up to 50 stones, WhatsApp share cards, consignment slips, basic dashboardWins the camera-roll fight. Everything a dealer does daily is free to start; a free user still shares share cards, which is our marketing.
Pro$29/month (or ~฿1,000)Unlimited stones, web catalog with client logins (up to 30 clients), full dashboard and suggestions, importOne extra sale pays for years. Catalog and numbers are the things dealers would pay for.
Pro Plus$49/monthUnlimited clients, price tiers per client (later), second user (later), custom domain (later)Bigger shops, grows with the product.

Collection: bank transfer or PromptPay QR for Thai dealers, invoiced by hand for the first 20, card (Stripe) for abroad. Do not make Thai dealers fight a foreign card form on day one.

Segment size and revenue, with the arithmetic visible (all assumptions):

ScenarioReachable shopsPaid conversionRevenue/monthARR
Bangkok only, modest1,00015%150 x $32 = $4.8k~$58k
Bangkok only, strong2,00030%600 x $35 = $21k~$250k
Plus Colombo, Jaipur, Hong Kong, Dubai (3 years)6,00020%1,200 x $35 = $42k~$500k

Honest read: a solid lifestyle business, with room to be more only if the data layer (v2 and v3) becomes something others pay for.

First 20 dealers at JTC. This is door-to-door, not ads.

  1. Start with people who already trust you. The GemBridge network has dealers who have already sent you inventory (the Sri Lankan sapphire suppliers with 380 and 24 stone lists, the Mogok spinel and Ceylon sapphire sellers, the emerald dealer). Because Tray shares the GemBridge backend, those stones are already in the database: their first login shows their own stock, with photos still to add. The pitch becomes "your list is already in; here is your catalog link". That is 5 design partners, free for 3 months, in exchange for weekly feedback and permission to use their catalog link as a demo. One care point: those stones were sent to GemBridge for matching, so they start with share_with_network on and the dealer is told so plainly at first login, with the switch right there.
  2. Floor by floor at JTC. Walk with a phone showing a real catalog (a design partner's, with permission). The pitch is 20 seconds: "Someone asks 'what's new?' You send one link." Not "inventory software."
  3. Share cards as the loop. Every share card carries a small "made with Tray" line. Buyers who receive a clean card ask the dealer what it is; this is how it spreads building to building. Dealers who lose a sale because the stone wasn't findable are the best converts, so I ask early adopters about their last lost sale.
  4. Two reference dealers per wing. Trade is word of mouth within a few blocks; a neighbor using it is worth more than any pitch.
  5. Benchmarks for "20". 5 by week 6 (design partners), 12 by week 12, 20 by week 20. Success is weekly use, not installs: at least 12 of 20 open the app on 4+ days a week by week 20. If that fails, we do not scale; we fix.

What stops a bigger player copying it. Features copy easily; these do not:

Why dealers stay. The catalog (their clients are in it), the memo history (the only record of who held what, ever), and the dashboard (once they know their numbers, they won't go back to a feeling).

What the GemBridge link adds. Two things, both real but second-order. First, distribution: around 31 active dealers are already in the GemBridge network, several with inventory on file; they are the warmest list for the first 20 and the import is already half done. Second, a reason to pay that no competitor has: a stone marked "share with GemBridge buyers" can be matched to a real buyer enquiry. That is the one feature GemCloud or WhatsApp cannot copy, and it belongs in Pro, not Free. The order of operations matters though: the toolkit must earn its place on the phone on its own, with the sharing switch off, or dealers will read it as a marketplace in disguise and hide their stock (push-back 2 above).

3. Prototype scope

Prototype goal: one dealer, 30 real stones, one real memo, one real share card, in 14 days of use. Build the smallest thing that can fail that test.

In the prototypeWhy
Add stone: camera first, short form, smart defaults, offlineThe core loop. If this isn't faster than WhatsApp, nothing else matters.
Stock grid, stone detail, search and filter (type, carat, color, shape, origin, treatment, price, status)"Find any stone in seconds."
WhatsApp share card (one stone or a selection, with or without price)The product's marketing, and the fastest visible win.
Clients: add by hand or from phone contacts; a wishlist text fieldMemo needs a client. Wishlist matching waits.
Consignment: pick client, pick stones, date, slip to WhatsApp, per-stone outcomes, sale from consignmentThe sharpest pain; the reason a dealer might switch.
Sale: link stone to client with price, currency, dateCloses the memo loop and feeds the numbers.
Dashboard: Needs attention (overdue and due-this-week memos, stones missing photo or price) and Performance (6 numbers, below). No Suggestions block yet.Two blocks are computable on the device from five tables. Suggestions need enough history to be right; a wrong suggestion on day one costs trust.
Concierge import: I load the dealer's sheet for themTests the value without building the import UI.
Catalog, public only, read-only, generated from the same data; no loginsA static page per dealer shows the "what's new becomes a link" moment and checks the media pipeline. Logins are the most complex part and are not needed to prove the idea.
Out of the prototypeWhy
Client logins, view tracking, price unlockLargest build and security risk; phase 2.
In-app Excel importConcierge first, learn the real messy sheets, then build it.
Suggestions, cross-dealer data, external referencesNeed data and trust first.
Sync, multi-device, backupPhone-only first; backup (not sync) lands in phase 2.
Thai UI, custom domains, multi-userLater.

Prototype dashboard numbers: stones by status, stock value at asking, value out on consignment, sold this month and last, stock older than 90 and 180 days with its value, average days to sell. Everything else waits.

4. Data model

Postgres in Supabase; mirrored by a local SQLite database in the app. Ids are UUIDs created on the device so offline records have stable identity. Every table has dealer_id, created_at, updated_at, deleted_at (soft delete), and version.

Principles that shape it

EntityKey fieldsNotes
dealersid, auth_user_id, vendor_id (→ GemBridge vendors), name, shop_name, slug, whatsapp_number, default_currency, display_currency, catalog_mode, show_prices_publicly, reserved_display, planOne row per shop (v1: one user). vendor_id links the toolkit account to the existing GemBridge dealer record by phone, so one person is one account; a dealer who never joins the network has a NULL there. catalog_mode: public / clients_only / off.
stonesid, dealer_id, ref (short human code, e.g. R-0142), kind, parent_id, species, variety, color_text, color_grade, clarity, shape, treatment, origin, carat, pieces, length_mm, width_mm, depth_mm, cut_quality, certificate_id, status, in_catalog, share_with_network, supplier_inventory_id, notes, added_atkind: single / pair / suite / lot / parcel. A lot's parent_id is NULL; split children point at it. share_with_network is the per-stone GemBridge opt-in, default false. supplier_inventory_id points at the row a stone was seeded from, so the existing 418 stones migrate once and never duplicate. Over time stones replaces supplier_inventory as what the GemBridge matcher reads, through a view limited to share_with_network = true.
stone_pricingstone_id, asking_amount, asking_currency, basis (per_carat / per_piece / total), price_note, price_visible_overrideReadable by granted clients, subject to dealer rules.
stone_privatestone_id, cost_amount, cost_currency, source_name, purchase_date, private_noteDealer-only. Never exposed through any view a client can use.
stone_eventsid, stone_id, type, qty_pieces, weight_ct, ref_type, ref_id, at, device_idTypes: added, split, merged, reserved, released, memo_out, memo_returned, sold, withdrawn. Remaining quantity is the sum of events.
mediaid, stone_id, kind (photo / video / cert_scan), storage_key, derived_keys, width, height, duration_s, sort, is_cover, upload_stateOriginal kept; derivatives generated. upload_state: local / uploading / done.
certificatesid, stone_id, lab, report_number, report_date, verify_url, scan_media_id, summary_jsonLab allowed values below. verify_url built from lab and number where the lab supports it.
clientsid, dealer_id, name, company, kind, whatsapp, email, country, language, notes, source, last_contacted_atkind: dealer / jeweler / collector / broker / other. Phone contact import maps here.
wishlistsid, client_id, species, color_text, origin, treatment, carat_min, carat_max, shape, budget_max, currency, note, activeOne client, many wishes. Matching is a query, not a feature.
client_accessid, client_id, dealer_id, identity_id, status (invited / active / revoked), invited_at, first_login_at, last_seen_at, revoked_at, price_tier (later)One row per dealer-client grant.
client_identitiesid, auth_user_id, whatsapp, email, display_name, buyer_id (→ GemBridge buyer, nullable)The person who logs in. One identity can hold grants with many dealers, so a buyer uses one login across shops. Dealers never see another dealer's data. If the same person is also a GemBridge buyer, the link is by phone and is informational only; a GemBridge buyer never gets catalog access without the dealer granting it.
consignmentsid, dealer_id, client_id, ref, created_at, due_date, note, status (open / overdue / closed), closed_at, snapshot_json, snapshot_hash, slip_tokenoverdue is derived, not stored. snapshot_json is the frozen slip.
consignment_itemsid, consignment_id, stone_id, qty_pieces, weight_ct, price_at_memo, currency, outcome (out / returned / sold), outcome_at, sale_idPer-stone outcomes. Partial returns are normal.
salesid, dealer_id, client_id, sold_at, currency, total_amount, payment_method, note, source_consignment_id
sale_itemsid, sale_id, stone_id, qty_pieces, weight_ct, price_amountA lot sold in part has a sale_item with fewer pieces than the lot.
catalog_viewsid, dealer_id, client_access_id (nullable), anon_session_hash, stone_id (nullable), path, at, duration_sAnonymous rows are counted, never identified.
dealer_metrics_dailydealer_id, date, plus metric columnsNightly snapshot, powers trends.
share_eventsid, dealer_id, stone_ids, client_id (nullable), with_price, atRecords what was sent; feeds "did this client see it?"
suggestion_logid, dealer_id, rule_id, shown_at, acted, dismissed, feedbackHow we learn which suggestions are wrong or obvious.

Allowed values (stored as codes, shown with plain labels and icons; "other" always allowed, and "other" values are reviewed monthly to extend the lists)

AttributeAllowed values
Speciesruby, sapphire, spinel, emerald, tourmaline, garnet, tanzanite, aquamarine, alexandrite, chrysoberyl, zircon, peridot, topaz, morganite, beryl (other), kunzite, opal, moonstone, amethyst, citrine, other
Variety (conditional on species)Sapphire: blue, yellow, pink, padparadscha, orange, green, purple, white, color-change, star. Tourmaline: Paraíba, rubellite, indicolite, chrome, bi-color/watermelon. Garnet: tsavorite, demantoid, spessartite, rhodolite, pyrope, malaya. Ruby: standard, star. Spinel: red, pink, cobalt blue, purple. Beryl: emerald, aquamarine, morganite, heliodor.
Color term (optional free pick)pigeon blood, royal blue, cornflower, vivid, pastel, intense, medium, light, padparadscha-type, neon, mint, hot pink, other (free text)
Clarityeye-clean, slightly included, included, heavily included
Shapeoval, cushion, pear, round, octagon, emerald cut, square, rectangle, heart, marquise, trillion, radiant, cabochon, sugarloaf, bead, free-form, rough
OriginBurma (Mogok), Ceylon (Sri Lanka), Madagascar, Mozambique, Thailand, Kashmir, Colombia, Zambia, Tanzania, Nigeria, Afghanistan, Brazil, Kenya, Vietnam, Tajikistan, Pakistan, Australia, unknown
Treatmentunheated, heated, heated (residue), glass-filled, diffused, oiled (minor), oiled (moderate), clarity enhanced, irradiated, not known/not tested
LabGIA, GRS, Gübelin, SSEF, AIGS, GIT (GIT/Gem and Jewelry Institute of Thailand), Lotus, IGI, Other
CurrencyUSD, THB, EUR, others as free pick; dealer sets default and display currency
Price basisper carat, per piece, total
Statusavailable, reserved, on consignment, sold (derived from events, never typed in)

Cross-dealer readiness. stones stores structured attributes, not free text, so aggregation is possible. A separate market_facts table (built nightly, no dealer id, only buckets) would carry: species, variety, origin band, size band, treatment, weeks-to-sell, view counts, inquiry counts. Dealers opt in through terms of service; any cell with fewer than 5 dealers or 10 events is suppressed.

Per-client visibility later. client_access.price_tier plus a client_stone_visibility table (client_id, stone_id, allow/deny) slot in without a migration of existing tables, because catalog reads already go through one policy.

5. Core flows

5.1 Dealer app

Add a stone (target: under 30 seconds with one photo).

  1. Tap the large camera button on the stock screen (or Home). Camera opens first, on a white-or-black tray preset that sets exposure and crop.
  2. Shoot 1 to 6 photos; optionally one 10-second video. Tap Done. The stone exists immediately and is saved locally. Upload starts silently.
  3. One sheet, big fields, carat first on a custom keypad (decimal, two places). Species is a picker showing the last-used one at top. Shape is an icon row. Color, origin, treatment are chips with the last-used pre-selected (heated sapphire from Ceylon becomes one tap).
  4. Price: amount, currency (last used), basis toggle (per carat / total). Skip allowed; the stone then appears in Needs attention as "no price".
  5. Done. A toast says "R-0143 saved" with "Share" and "Add another" actions. No confirmation screen.

Find a stone. Search field at the top of the stock grid. Typing "ceylon blue 4" parses as a filter (origin, color, carat 4+). Saved filters appear as chips. Pinch changes grid density. Tap a stone for detail.

Share a stone or selection. From the grid: long-press to select, tap Share. Choose "with price" or "without" (remembered per client or last choice). The preview shows the card exactly as WhatsApp will receive it. One tap opens the WhatsApp contact picker with the images and caption attached.

Create a consignment (full loop).

  1. Consignment tab > New. Step 1: pick a client (recent first, search, or add new).
  2. Step 2: the stock grid, available stones only, multi-select with a running count and total weight and value at the bottom. A scan icon accepts reference codes or QR on the parcel envelope (phase 3).
  3. Step 3: due date (default +7 days, quick chips +3, +7, +14, custom) and optional note.
  4. Review shows the slip. Tap Send on WhatsApp: the slip card (image plus a link to the frozen snapshot) goes to the client's chat. Stones become "on consignment, with [client]" immediately.
  5. Days later, the client returns two stones and keeps one. On the consignment, the dealer taps each stone: Returned (stone available again) or Sold (opens a pre-filled sale: client, price from the memo, today, currency) and confirms. Marking "Sold" records the sale and closes that line.
  6. When every line is returned or sold the consignment closes automatically and moves to history. If the due date passes with lines still out, it becomes overdue on Home.

Record a sale directly. On the stone: Sold button > pick client > price (prefilled with asking) > date (today) > Done. Stock updates itself. For a lot: ask pieces or carats sold; remaining stays available.

Add a client and use the wishlist. Add by name and WhatsApp number or import from phone contacts. A wishlist is one sentence in plain words plus optional chips; saving a new stone checks all wishlists and, if matched, offers "Tell Mr. X" in one tap.

Grant catalog access and revoke. Client profile > Catalog access toggle > the dealer taps Send on WhatsApp (pre-written message with the link) > access shows "invited", then "active" when they log in. Revoke is one tap with an undo toast.

5.2 Catalog

Public visitor. Opens a dealer link from WhatsApp. Sees the grid, filters, stone detail with media, certificate, specs. No prices if the dealer hides them. A small "Ask about this" button opens WhatsApp with the stone reference. The visit is counted anonymously.

Full catalog loop.

  1. Dealer adds the client in the CRM (or picks an existing one).
  2. Dealer taps Grant access > Send on WhatsApp. The message carries a personal magic link.
  3. Client taps the link. It opens in the phone browser, validates the one-time token, and signs them in with a 180-day session. A soft prompt offers "Set a password" and "Save to home screen".
  4. Prices appear. The client browses, opens the 3.2 ct ruby, plays the video.
  5. The catalog records one view (client, stone, time, duration).
  6. On the dealer's Home, Needs attention shows "Mr. X looked at R-0142 twice today". One tap opens a WhatsApp chat with a pre-filled "Hi Mr. X, saw you looked at the ruby..." the dealer edits and sends.
  7. Client taps "Ask about this stone" at any time; WhatsApp opens to the dealer with the reference.

6. Screens and pages

App

ScreenPurpose
Home (dashboard)Opens every time. Needs attention, Performance, Suggestions.
Stock gridAll stones as photos; search, filters, status chips, selection mode.
Stone detailFull media, specs, price, certificate, history, actions (share, consign, sell, edit).
Add stone (camera, form)Two steps: capture, short form.
Filter sheetBottom sheet with chips and ranges.
Share previewThe card as it will arrive; with/without price toggle.
Consignment listOpen, overdue, closed.
Consignment new (3 steps)Client > stones > date.
Consignment detailThe slip, per-stone outcome rows, history.
ClientsList with access state.
Client profileWishlist, purchases, open memos, catalog access.
Sale recordQuick sheet.
Catalog settingsVisibility mode, price switch, per-stone overrides, view log.
Import (phase 2)File picker, column mapping, preview.
SettingsCurrency, language, backup, export, account.

Navigation is a bottom bar with five items: Home, Stock, (+ camera), Consignment, Clients. The Catalog settings live under Home's top-right gear and Settings, not in the bar, because dealers touch them rarely.

Catalog

PagePurpose
Dealer home / gridThe catalog as a photo grid, with filters.
Stone detailMedia, specs, certificate, ask-on-WhatsApp.
Sign-in landingToken exchange screen; error states ("link expired, ask the dealer for a new one").
Request new linkA button that notifies the dealer to resend.
Locked stateShown when the whole catalog is locked; explains how to ask for access.
404 for sold stonesFriendly "this stone has found a home" with a link back to the grid.

7. UI and UX decisions, with reasons

Navigation: five-item bottom bar with a raised camera button. One thumb, no hidden menus. The camera button sits in the middle because adding a stone is the most frequent act and must beat "take photo, send". Home is first so every launch shows the day's picture.

Dashboard layout: one vertical page, three blocks in the specified order, no scroll for the first two. Needs attention shows at most 4 items, then "See all". Performance is a 2x3 grid of plain figures, each with one tiny arrow trend. Suggestions show two cards. The test is five seconds: "what do I do now" is the top block; "how's business" is the middle. Cutting anything that does not drive action is a rule, not a preference.

Add stone: camera first, then one guided sheet. Following Carousell and Vinted: the camera is the doorway, and the form arrives after the photo exists. Fields are ordered by what dealers say first (carat, type, shape, color, origin, treatment, price). Defaults carry from the last stone. A weight scale is not connected in v1; entering carat by keypad is fast enough, and Bluetooth scales are a later request.

Search: typing parses natural trade phrases. "unheated Ceylon blue 3 to 5" becomes chips the dealer can tap away. No query language to learn. Always-visible saved filters ("Unheated Ceylon blue", "Under $2k") for repeat asks.

Share card: one image, one caption. Cover photo square with a thin footer strip, specs in the caption, reference and a link to the stone's page (preview card shows the cover photo). See the screens file. Caption template: Ruby 3.21 ct · Mozambique · unheated · oval 9.1 x 7.0 mm · GRS on the first line, color and clarity line second, price line only if toggled on, then Ref R-0142. Short enough to read without opening. Multi-stone shares send an album with one caption per photo, or one contact-sheet image with refs for large selections.

Consignment creation and the slip. Three steps, each one screen: client, stones, date. The slip is a vertical card: dealer name and logo/wordmark, client name, date out, due date, a table of stones with photo thumbnail, ref, carat, price, total weight and total value, a line for the terms (editable default: "Stones remain the property of [dealer] until paid; receiver responsible for loss or damage") and a QR/link to the frozen version. It is meant to be screenshot-able and legible on a phone. Terms text is the dealer's own; we do not draft legal language.

Catalog access control. Three settings in plain language, each with an icon: Who sees my stones (anyone with link / only my clients / nobody), Prices (hidden / shown to clients / shown to everyone), and per-stone "hide this one". Client access is a toggle in the client row with a state chip: not invited, invited, active, revoked. The dealer never has to understand "magic links"; the app shows "Send access on WhatsApp".

Language and icons. Plain verbs: "Add stone", "Send", "Out", "Back", "Sold". Never "SKU", "inventory item", "entity". Icons carry meaning alongside every label: a stone-shape set for shapes, a hand-with-box for consignment, a tag for price.

8. Dashboard specification

Everything computed on the device from local data in v1 (instant, offline). Server snapshots are added only for trends and cross-dealer work.

8.1 Needs attention (ordered by urgency; each row is one tap to the fix)

RuleDataFormulaPhaseWhy they'd act
Overdue consignmentconsignments, itemsdue_date < today and any item outcome = outPrototypeA stone is out past its date. One tap: WhatsApp the holder, or mark returned.
Consignment due this weeksamedue_date within 7 daysPrototypeChasing before it is late is polite; after it is awkward.
Catalog stone missing photo or pricestones, media, stone_pricingin_catalog and (no cover photo or no asking price)PrototypeA catalog stone without a photo or price does not sell. One tap opens the stone.
Reserved too longstone_eventsstatus reserved for more than N days (default 5)Phase 2A reserved stone blocks a sale. Release or confirm.
New stock matches a wishlistwishlists, stoneswishlist criteria match a stone added in the last 7 daysPhase 2The client asked for exactly this; message them now.
Client viewed catalog or stonecatalog_viewsa granted client viewed in the last 3 days; collapsed per clientPhase 2Warmest lead signal there is. One tap opens WhatsApp.
Conflictstone_eventsa stone has two incompatible events from offline devicesPhase 2Prevents the double-sale burn.

Cut: "birthday reminders", "unread messages", anything that is a notification about the app instead of the business.

8.2 Performance

FigureSourceFormulaPhaseWhy they'd act
Stock value at askingstones, pricingsum of remaining qty x asking price, available + reservedPrototypeThe headline number. "How much is on my shelf?"
Stones by statusstonescount available / reserved / on consignment / sold-this-monthPrototypeWhere my stones are, in one glance.
Value out on consignmentconsignment_itemssum of price_at_memo where outcome = outPrototypeThe number that scares them, and should.
Sold this month vs lastsalescount and value, month-to-date vs previous full monthPrototypeThe answer to "how's business".
Average days added to soldstones, salesmean(sold_at - added_at), last 12 monthsPrototypeTells if pricing is right. Shown with its sample size.
Stock older than 90 and 180 days with valuestonesage = today - added_at for unsold stonesPrototypeDead money. Tapping lists them.
Top clients by purchasessalessum by client, last 12 months, top 3Phase 2Who to look after.
Stock cost and margin (optional)stone_privateshown only if cost entered; asking minus costPhase 2Private; some dealers will never enter it, and that is fine.

Each figure has one tiny trend arrow (up/down/flat vs previous period) and nothing else. No charts a dealer must interpret.

8.3 Suggestions

Rules: at most 3 shown; every one has "Why am I seeing this?" (the data behind it) and a dismiss with feedback; none shown until the dealer has 20+ stones, because thin data produces wrong or obvious advice.

IDText patternDataPhaseWhy they'd act
S1 Aged stock"6 stones over 180 days, worth $42k. Consider repricing or sending on consignment."stones agev1A concrete list with a value. Buttons: View list / Reprice.
S2 Wishlist coverage"3 clients want unheated Ceylon blue 3 to 5 ct and you have 2 in stock."wishlists vs stonesv1Immediate sale chances. Button: Share with them.
S3 Fair prep"Hong Kong fair in 3 weeks, 14 catalog stones have no video."hand-curated fair calendar plus mediav1Fairs are when decisions are made. Your own fair calendar already exists.
S4 Win-back"Mr. X bought twice in 2025 and hasn't heard from you in 60 days."sales, clients.last_contacted_atv1A repeat buyer is the cheapest sale.
S5 Unpublished stones"8 available stones aren't in your catalog."stones.in_catalogv1Free visibility.
S6 Demand signal"Buyers on Tray are looking at unheated spinel under 2 ct more than any other category this month."anonymized market_factsv2Tells a dealer what to stock. Requires ≥ 5 dealers and ≥ 10 events per category.
S7 Selling fast"Padparadscha 1 to 3 ct sells in 21 days on average across Tray. Yours: 64."market_factsv2A benchmark that points to price or photos.
S8 Reference price"Similar unheated Ceylon blue 4 ct: guide range $X to $Y."licensed price guidev3Reprice with confidence.
S9 Auction context"Recent auction results for Kashmir sapphire."public auction resultsv3Context for rare stones; not a price.

Cut on purpose: generic tips ("take better photos"), gamification, streaks, anything that sounds clever but has no button.

v3 sources: what is licensable, scrapeable, not

9. Design references

ReferenceWhat I takeWhat I reject
WhatsAppThe simplicity bar. Share flow, contact picker, chat-style history per client (consignments and sales as a timeline).Its lack of structure.
Carousell, VintedCamera first, form after, defaults that do the work.Marketplace chrome, seller ratings.
Square POS (mobile)Big buttons, one action per screen, plain words.Retail-register metaphors (cart, checkout).
Shopify mobile homeThe shape: numbers, attention, tips, each tappable.Its density and charts; we simplify to 6 figures.
Apple PhotosGrid-first browsing, pinch density, instant search.Albums and years; we sort by status and recency.
RevolutLarge tap targets, one field per step when it matters, custom keypad for numbers.Gamified finance features.
Instagram (share card)Image first, short caption.Filters, social counters.
1stDibsBig imagery, calm layout, specs as a quiet table.Auction-house formality and heavy navigation.
RapNet app (counter)Nothing; this is what to avoid: desk tool shrunk onto a phone.All of it.

10. Design system

10.1 Look

Neutral gallery. Near-white and near-black surfaces, no brand color. The stones are the only saturated pixels on screen. The accent is the inverse neutral (black on light, near-white on dark), so primary buttons are plain and strong. Status colors are small and always paired with an icon or shape, so they work for the colour-blind and in sunlight.

10.2 Color tokens

All text-use colors below pass 4.5:1 on both surfaces (measured, ratios in the last column are against surface).

TokenLightDarkContrast on surface (L / D)
surface#FAFAF9#0E0E0F-
surface-elevated#FFFFFF#1A1A1C-
text-primary#141414#F2F2F017.6 / 17.2
text-secondary#5C5C5C#A3A3A06.4 / 7.6
border#D9D8D4#38383Bdecorative
accent (fill)#141414#F2F2F0-
on-accent#FFFFFF#0E0E0F-
positive#1F7A4D#5CC5925.1 / 9.1
warning#9A5B00#E8AE4A5.2 / 9.7
danger#B3261E#FF8A80check at build
status-available#1F7A4D#5CC5925.1 / 9.1
status-reserved#9A5B00#E8AE4A5.2 / 9.7
status-consignment#3550C8#8CA3FF6.4 / 8.1
status-sold#6B6775#A9A5B35.3 / 8.0

Status is shown as a chip with an icon (check, clock, hand-with-box, tag-with-line) and a word, never color alone.

10.3 Other tokens

GroupTokens
Spacing (8pt grid)space-1 4 (half step, allowed only inside components), space-2 8, space-3 16, space-4 24, space-5 32, space-6 48, space-7 64
Radiusradius-sm 8 (chips, inputs), radius-md 12 (cards), radius-lg 16 (sheets), radius-full (pills, avatars)
Elevationelev-0 none (default; borders carry hierarchy), elev-1 0 1px 2px rgba(0,0,0,.08), elev-2 0 6px 20px rgba(0,0,0,.14) for sheets. In dark mode, elevation is expressed by a lighter surface-elevated, not shadow.
Motionmotion-fast 120 ms (taps, chip toggles), motion-base 200 ms (sheets, state changes), motion-slow 320 ms (screen transitions); ease-out for entering, ease-in for leaving; reduced-motion setting turns everything to 0 ms fades

The same names are used as Flutter ThemeExtension fields and as CSS variables (--surface, --text-primary, --status-consignment, --space-3, --radius-md).

10.4 Typography

One typeface: Inter (variable), with tabular figures turned on for every number that is a price, weight or count (font-feature-settings: "tnum" on web, FontFeature.tabularFigures() in Flutter). Thai uses Noto Sans Thai as the fallback, since Inter has no Thai glyphs; the line height is raised to 1.5 for Thai.

StyleSize / weightUse
number-xl32 / 600Dashboard headline figure
number-lg24 / 600Carat, price on stone detail
title20 / 600Screen titles, section headers
body16 / 400Everything readable (minimum)
body-strong16 / 600Labels, buttons
caption14 / 500Metadata only (timestamps, refs); never the only carrier of meaning

10.5 Components and mapping

ComponentFlutter (Material 3 base)Web catalogNotes
Stone card (grid, list)custom StoneCard on Card + Imagesame markupSquare cover, status chip bottom-left, carat and price underneath. List variant adds specs row.
Status chipChip / InputChip themed<span class="chip">Icon + word + status color; never color-only.
Filter chip, filter sheetFilterChip in showModalBottomSheetchips + dialogSame attribute set app and web.
Bottom-sheet formDraggableScrollableSheetmodalAdd stone, sale record, access sheet.
Stepper (carat, price)custom keypad on TextFieldnumeric input + steppers48 px targets; decimal-first keypad.
Photo capture, gallerycamera plugin, PageView + InteractiveViewer<img> carousel, pinch zoomSquare crop default, original kept.
Multi-select stone pickerGridView with selection staten/aRunning totals bar.
Client row with access stateListTile + status chipn/aStates: not invited, invited, active, revoked.
Consignment cardCard with due-date chipn/aPer-stone outcome rows below: out, returned, sold.
Consignment slip (shareable)rendered widget to imagepublic slip pageSame layout in both.
Share card previewwidget captured to PNGOG image generatorThe marketing artefact.
Catalog access sheetbottom sheetn/aToggle + Send on WhatsApp.
Dashboard stat tilecustom StatTilen/aNumber-lg + label + trend.
Attention item rowListTile with trailing actionn/aOne tap = resolve.
Suggestion cardCardn/aText, "why", one button, dismiss.
Primary / secondary buttonsFilledButton / OutlinedButton, 56 px high<button>One primary per screen.
Empty statecustomsameOne icon, one sentence, one button.
ToastSnackBartoastWith Undo where possible.

Web catalog reuses: stone card, filter chips, stone detail layout, status chip, and photo gallery, all from the same token file.

10.6 Iconography and photo treatment

10.7 Seven key screens

Drawn in tray-screens.html: home dashboard, stock grid, stone detail, add stone, consignment detail with slip, share card, catalog grid.

11. Tech stack and architecture

Overview: Flutter app (SQLite on the device) <-> Supabase (Postgres, Auth, Storage hooks, Edge Functions) <-> object storage and image/video delivery on Cloudflare <-> Astro web catalog.

11b. Do we need a model that predicts a stone's characteristics?

April asked this on 13 October. Short answer: yes, but as an assistant that fills the form, never as an authority that describes the stone. And not a model we train ourselves at first.

What a phone photo can and cannot tell you. Be honest about this before promising anything to dealers:

AttributeFrom a phone photoHow we'd get it instead
ShapeReliableVision model suggests, dealer confirms with one tap
Species / varietyPlausible guess (red oval on a tray: ruby, spinel, garnet or glass). Useful as a pre-selected chip, dangerous as a factSuggest the top 2, dealer picks
Colour descriptionRough. Phone white balance and the tray change everythingSuggest a trade term, dealer edits
Carat, dimensionsImpossible from a photoKeypad, or a Bluetooth scale later
ClarityWeakDealer picks from 4 chips
Origin, treatmentImpossible from a photo. Only a lab can say this, and even labs disagreeRead it off the certificate, or the dealer says it
PriceNot from the photo. From the dealer's own history and, later, cross-dealer dataSection 8, S1/S7/S8

Where a model earns its place, in order of value

  1. Certificate reading (phase 2). Dealers photograph the GIA/GRS/Gübelin/AIGS/GIT report that is already in the parcel. A vision-language model reads lab, report number, weight, dimensions, species, origin, treatment and colour comment, fills the form, and the dealer confirms. This is the single biggest typing saver, and the extracted values are the ones that matter for price. Start with Claude's vision through the API; reports are printed documents and this works well today. Store the extracted fields next to the scan, and link the report number to the lab's verification page so the buyer can check it themselves.
  2. Natural-language search (prototype or phase 2). "unheated ceylon blue 3 to 5" → filters. A small prompt to a language model, or a rules parser for the trade vocabulary we already control. Cheap, high daily value.
  3. Photo-to-form suggestions (phase 2). Shape and a species guess, pre-selected as chips. The dealer keeps the last word, always, and the UI never says "detected ruby"; it says "Ruby?" with a tap to change. Wrong guesses cost one tap; wrong facts cost trust.
  4. Wishlist matching (phase 2). Structured match first (species, size band, treatment, origin). A model only for the free-text wishes dealers type ("something special for a man's ring, blue, under 10k").
  5. Pricing help (v2/v3). Not a model that "predicts value". A reference range from the dealer's own sell-through, then anonymised cross-dealer data, then licensed guides, each labelled with where it came from. A number with no source is a number a dealer will ignore or, worse, trust.

Why not train our own model now. We have no labelled photos: GemBridge holds 418 stones and, as of the October audit, zero photos. Tray fixes that: every stone added is a photo with dealer-confirmed labels (species, shape, colour term, treatment, origin, lab, price, and whether it sold). After a few thousand stones across dealers that is a dataset nobody else has, and the moment to consider fine-tuning a vision model on it, or selling the insight back as market intelligence (section 8.3, v2). Until then, a hosted vision model plus the dealer's thumb is cheaper, better and faster to ship.

Guardrails, because dealers' reputations ride on descriptions

12. Build phases and milestones

I build; you steer. Durations are my estimate of calendar time with you testing weekly; they move if the prototype test says "change it".

PhaseWhatWeeksDone when
0. PrepareCount doors at JTC. Interview 8 dealers about their last lost sale and last memo. Pick 5 design partners. Draft tray-screens as clickable tap-throughs.1 to 25 dealers agree to a test; a real sheet from each.
1. PrototypeSection 3 scope. TestFlight and APK to the 5. Concierge import. Public catalog (no logins).4 to 55 dealers: 30 stones each in the app, at least 3 real memos created, at least 10 real share cards sent, in a 14-day test. Decision: keep going or change.
2. Core v1Sync and backup, in-app Excel import, client logins and view tracking, wishlists and matching, reserved/attention rules, price controls, top-clients metric.8 to 1012 dealers live. 4 client logins per dealer. The full catalog loop works end to end.
3. Paid launchThai UI, payments (PromptPay and card), onboarding polish, slip receipt confirmation, QR parcel scan.420 dealers; first paying; weekly active target met.
4. IntelligenceCross-dealer market facts (v2 suggestions), fair calendar, performance polish.later100+ dealers and consent terms in place.
5. Reference dataLicensed price guides, curated auction context.laterLicense signed.

Later versions, one paragraph each.

13. Risks and open questions

RiskWhat could go wrongWhat I'd do
TrustDealers will not put stock, cost prices and client lists in someone's cloud. Sharper now: that cloud belongs to a company that also talks to their buyers. A dealer who learns GemBridge can see his cost price or his client list deletes the app and tells the floor.Phone-first (data stays on the phone until they press publish), cost price in its own private table, an export button on day one, a plain-language privacy page, no sale of identifiable data, ever. Be physically present in the building. Plus, because of the shared backend: sharing with the GemBridge network is per stone, off by default, and visibly so in the app; and the policy tests in section 11 are what make that promise true. The umbrella brand should be announced only once the toolkit stands on its own; "GemBridge wants to see my stock" is the wrong first impression.
Client credential leakage and screenshotsA client forwards the link or screenshots prices to a competitor.We cannot stop a screenshot. Mitigate: watermark prices with the viewer's first name (dealer toggle), per-client session cap, access log, one-tap revoke, price-tier later. Culturally, the trade already accepts this risk on WhatsApp.
Multi-currencyTHB and USD both everywhere; the same stone sold in two currencies.Store each price in the currency it was entered, plus an optional dealer-set rate; dashboards show a single display currency with the rate used; never silently convert historical sales.
Thai and EnglishEnglish-only loses Thai dealers. Spelling and units differ.Strings externalised from the first commit; Thai in phase 3; photo and icon-led screens reduce the load either way.
Wrong or obvious suggestionsThe suggestion block teaches dealers to ignore Home.Few rules, shown only with enough data, "why" on every one, dismiss with feedback, suggestion_log to kill rules nobody acts on.
Memo disputes"That stone was not on the slip."Frozen snapshot with a hash and public link, per-line outcomes with timestamps, and receiver confirmation in phase 3. Not legal advice.
Offline double-editsTwo devices change one stone.Event log, per-field merge, conflict item on Home (section 11).
Dealers lack time to enter dataThe first week is typing, and they stop.Concierge import, camera-first, defaults, 30-stone starting goal, the free tier works with 5 stones.
WhatsApp policy / template costsMeta restricts automated messages (your own number was blocked from some countries before).Everything sends from the dealer's own WhatsApp via share, not from a business API. No automated outbound messaging in v1.
Scope creepEight features plus a web app is too much.The prototype list is firm; every feature must pass "will a dealer touch it weekly?"

Open questions (none block the prototype; ask in this order)

  1. Which 5 dealers do you trust to be design partners, and will they let us use their catalog as a demo? The GemBridge dealers with inventory on file are my first candidates.
  2. Do any dealers already use GemCloud or similar? One user interview would change the competitor table more than any web search.
  3. What does a Thai dealer say when asked: "Would you let your clients see your stock, and your prices?" This sets the catalog default.
  4. When does the umbrella brand go public? My recommendation: not before 20 dealers use the toolkit weekly.
  5. Who pays the first costs (developer accounts, media storage)? Supabase is already paid for; the new line items are small, but they need an owner.

Decided: Tray shares the GemBridge Supabase project and auth, and will sit under one umbrella brand later (April, 13 October 2026).