Pembaruan
Yang baru di Epidom.
Setiap peningkatan, perbaikan, dan fitur baru — dirilis cepat dan didokumentasikan di sini.
Catatan rilis ditulis dalam bahasa Inggris.
3.38.1 — Perbaikan
- Newsletter unsubscribe links now go straight to www.epidom.fr, so the Unsubscribe button in mail apps such as Gmail works without depending on a redirect.
3.38.0 — Fitur
- Every Epidom newsletter now ends with an Unsubscribe link in the reader's language (French, English or Indonesian), and mail apps such as Gmail show their own one-click Unsubscribe button. One click stops all Epidom newsletters for that address; emails about your account (receipts, security) keep coming. Signing up again from the newsletter form on epidom.fr turns them back on.
- Admin → Newsletter (internal): newsletters can now be written ahead and scheduled for a chosen day and hour, in Paris, Jakarta or your own time zone, so several weeks or months can be prepared at once. Each one can be edited, moved back to drafts, duplicated, deleted or sent right away until it goes out.
- Each newsletter picks its audience, in any combination: newsletter sign-ups, sales-page leads, free customers, trial customers and paid customers (or everyone), plus a language filter. Live counts show how many people that is today. Free, trial and paid are worked out again when it sends, and someone in two audiences gets it once.
- The newsletter editor has bold, headings, lists, links, call-to-action buttons and images, a "Pasted HTML" mode for designed emails, a preview of the exact email, and a test send. The Scheduled tab groups newsletters by week; the Sent tab shows how many were sent and lets a failed send resume without emailing anyone twice.
3.37.2 — UX
- The video on our sales pages has a new cover. Its play button now sits over the presenter and the "5 minutes" caption in the bottom-right corner, so neither hides the cover's headline, on phones too.
3.37.1 — UX
- New accounts are greeted with "Congratulations on choosing Epidom!" at the top of the first setup step, right after the email is confirmed (link or 6-digit code) or Google creates the account. It can be closed, and it goes away once the first step is saved.
- After paying for a plan or starting the free trial, the confirmation page now congratulates you on choosing Epidom. Paying a price quoted by our team shows the same congratulations on the Billing page.
3.37.0 — Fitur
- POS, with the Active Queue switched off: Save Bill becomes Hold. It sets the current sale aside on this till and empties the cart for the next customer, with no dialog, and it works offline. The cart's Order Queue button then becomes Resume hold: one held sale comes back with a tap, several open a list (newest first, with who it's for and its total). If the cart already has items when you resume, that sale is held in its place, so nothing is lost. A held sale stays on this device only and isn't an order until you charge it.
- Order Queue page, with the Active Queue off: the page opens on the Log, and the POS and Online ordering tabs are greyed out (there's no queue to show). The cart's button reads "Order Log" then.
- Staff → Page Access: "POS Cashier" and "Order Queue" are now one item, POS & Orders, which grants both. A staff member who had only one of the two keeps it, shown as half-ticked, until you tick the item.
3.36.0 — Fitur
- Sign-up: the verification email now gives two ways to confirm your address. Tap the button, or type the 6-digit code from the email on the "Check your email" page or the sign-in page. The code works even when your email app has turned the button off (Gmail does this in the Spam folder) and when you read the email on another device.
- The code is also in the email's subject line, so you can read it without opening the email. It lasts 1 hour, and if you ask for a new email, only the newest code works.
- The "copy and paste this link" address in the verification and password-reset emails is now a link you can tap.
- Names in our emails are shown exactly as typed, as plain text.
3.35.1 — UX
- Homepage, in French: the headline now leads with the benefit, « Le moyen le plus simple de gérer votre coffee shop », with « Caisse en temps réel avec une marge transparente » under it. English and Indonesian are unchanged.
3.35.0 — Fitur
- Build Your Own menu items: a new switch, off by default, on products (Data → Products) and on items in Storefront → Menu. When it's on, the item's options become numbered steps, for example Base, Protein, Sauce.
- Products: each choice in a step can take a raw material from stock (pick the material and how much one serving uses), so every sale deducts exactly what the customer chose. Turning Build Your Own on also sets the product to "made fresh when ordered".
- Cashier: Build Your Own items have a badge and open a step-by-step picker that shows each step's limit and a running total. A full step won't take another pick, and a skipped required step is highlighted.
- Storefront: customers build the item themselves, step by step, with a progress bar and the total on the Add button. Build Your Own prices show as "From …".
- Online orders take every option's price and stock link from your menu, never from the customer's phone.
- An online order with the same item twice (two bowls built differently, say) is no longer refused as "unavailable".
3.34.1 — Perbaikan
- Clock In / Out selfie: the camera works again in Chrome, Arc, Edge, Android Chrome and the desktop app. A site-wide security header was switching the camera off on every Epidom page, so the browser never asked for permission and "Try again" could not help. Clock-in location was blocked by the same header and now works too.
- Selfie camera: "Try again" asks the browser for the camera right away, every time you tap it, and the camera starts by itself once you allow it in the browser's site settings.
- Selfie camera: shows "Waiting for camera access…" while the browser's permission prompt is open, and tries every camera on the device when the default one is busy.
- Selfie camera: clearer messages when another app is using the camera or the page isn't allowed to use it, with the photo upload always available as a backup.
- Sales page 2: everything below the phone mock-up, including the newsletter and footer, now gets its styling on desktop too. A CSS block that was never closed had limited those styles to phones.
3.34.0 — UX
- POS Order Queue: fullscreen toggle now visible on all screen sizes (was hidden below 640 px), making it easy to enter distraction-free mode on any device.
- POS Order Queue: source-tabs row (POS / Online / Log) is more compact on tablet, reducing wasted vertical chrome.
- POS Order Queue: split view (status rail + list) now activates at the
mdbreakpoint (768 px) instead oflg(1024 px) — portrait tablets get the two-column layout with a left status rail rather than a horizontal chip row. - POS Order Queue: queue-number column hidden on phones (< 640 px) where it is redundant, giving the remaining columns more breathing room.
- POS Order Queue: inner content padding reduced on tablet (
p-4at md,p-6at lg+) so more orders fit without scrolling. - POS Mode shell: bottom tab bar hides in landscape + md orientation; a slim 48 px icon-only side rail appears on the left instead, reclaiming ~56 px of vertical space on landscape tablets.
3.33.0 — Fitur
- Smart import: AI now detects "Build Your Own" (BYO) menu structures from CSV/spreadsheet uploads — group headers (BASE, ADD, SAUCE, etc.) and option/price rows are parsed and saved directly as
MenuItem.modifiers, creating a "Build Your Own" category automatically. - POS & storefront: BYO items show a running total in the options dialog footer, updating as the customer picks base + add-ons + sauce; a hint line explains the three-step flow.
- Newsletter signup: mobile layout fixed everywhere — form now stacks vertically on small screens with full-width input and button, no more horizontal overflow or edge clipping.
3.32.0 — Fitur
- Newsletter admin: "Sync users to Resend" button — iterates all app User records and adds them to the Resend newsletter audience in batches of 10 concurrent requests (
POST /api/admin/newsletter/sync-users). - Newsletter admin: Test send — type an email address and send the current subject + HTML as a test before broadcasting (
[TEST]prefix on subject). - Newsletter admin: Send history table — every broadcast, custom and test send is now logged to
newsletter_sendsDB table and shown in a history table at the bottom of the newsletter page with subject, type, recipient count, sender, and timestamp.
3.31.0 — UX
- Admin nav grouped into Finance / Marketing / Community dropdowns; standalone pages (Users, Activity) remain as plain links.
- Sales Pages admin: lead submissions (popup leads + newsletter subscribers) now shown below the A/B report — no separate Leads page needed.
- Newsletter admin: HTML preview toggle; recipient selection (Resend audience, popup leads, newsletter subscribers with individual select/deselect); individual Resend
/emailssends when a custom list is chosen. - Removed
/admin/leadspage (leads now accessible under Sales Pages and Newsletter).
3.30.0 — Fitur
- Newsletter system: email-only subscribe form in site footer and at the end of all 4 sales pages (FR/EN/ID).
- Sales page footer: "Request a personalized demo" button re-opens the lead capture popup even after dismissal.
- Admin
/admin/leads: table view of all sales leads and newsletter subscribers (grantable section). - Admin
/admin/newsletter: compose and send Resend broadcast to the newsletter audience (grantable section). - Newsletter + Leads sections added to admin access registry; Aaron can be granted access via Admin → Users.
3.29.0 — Fitur
- Lead capture form — after 4 cumulative minutes of VSL playback, a modal appears asking for First Name, Last Name, Phone, and Email. A second trigger fires on desktop when the cursor leaves toward the address bar (exit intent) and on mobile on a rapid upward scroll near the top.
- `SalesLead` model — leads are stored in a new
sales_leadsDB table (page, locale, source, name, phone, email). - Newsletter integration — when
RESEND_NEWSLETTER_AUDIENCE_IDis set, each submission is also added to the Resend audience as a contact. - All 4 sales pages (FR/EN/ID — 12 files) now load
/sales-pages/lead-capture.js. A localStorage flag prevents re-showing after submission or 3 dismissals.
3.28.0 — Infra
- Removed Fonnte. WhatsApp API sending via Fonnte has been removed. Customer receipts use the "Open in WhatsApp" (wa.me) link instead. Merchant alerts continue via MagicBell.
- Removed `@ai-sdk/google`. The Google Gemini SDK was unused after the AI profile analyser was moved to OpenAI Vision.
- SheetJS from CDN tarball. Replaced the unmaintained npm
xlsx@0.18.5with the SheetJS LLC CDN tarball (xlsx@0.20.3). - Session location from Vercel headers. Replaced the ipapi.co third-party lookup (one outbound HTTP call per page load) with
x-vercel-ip-city/x-vercel-ip-countryheaders stored at session creation.schema.prisma: addedSession.location String?. Migration:20261019145420_add_session_location. - Cloudflare R2 storage adapter.
CloudflareR2Adapternow implementsStorageAdapter; setSTORAGE_PROVIDER=cloudflare-r2plusR2_MEDIA_BUCKET+R2_MEDIA_PUBLIC_URLto switch uploads to R2.next.config.tsaddsmedia.epidom.frtoimages.remotePatterns. - `isOwnUploadUrl` helper. Centralises "is this URL from our upload infrastructure?" — accepts both Vercel Blob and R2 media hosts. Seven Blob-host checks across schemas, services, and utilities now use it.
- Blob→R2 migration script.
scripts/migrate-blob-to-r2.tsrewrites the 7 image-URL columns from Vercel Blob to R2. Dry-run by default;--applywrites. CSV rollback map generated. - `docs/OPERATIONS.md`. Complete secrets inventory (every secret, where it lives, who rotates it) and ops runbooks for deploy, backup, restore, env audit, and dependency audit.
- 6 GitHub Actions ops workflows.
ops-deploy,ops-migrate-status,ops-backup-now,ops-restore-to-dev,ops-env-audit,ops-deps-audit— allworkflow_dispatch, production environment gate. - AGENTS.md Operations section. Maps every production task to its workflow.
3.27.0 — UX
- Custom date range. The Sales Pages report, the Meta Pixel report and the partner page now end their range tabs with Custom: pick a first and last day on the calendar and the figures cover exactly those days. The range stays in the page address, so it can be bookmarked or shared, and switching language on the Sales Pages report keeps it.
- Sales-page links without ".html". "Open" on the Sales Pages report now opens the page at its public address (epidom.fr/sales-page-4, /en/sales-page-4, /id/sales-page-4), and the French link opens French whatever your browser's language. Old links to the page files (/sales-pages/sales-page-4.html) now lead to the same public addresses.
- The video fills its frame. On every sales page, the playing video now covers the whole box, edge to edge, like its thumbnail, with no black bars. Full screen still shows the whole picture.
- No more read-only partner pages. The option to let someone see a partner's page read-only is gone from the Access dialog and the partner's admin page. Admins given the Referrals page follow every partner from there.
3.26.0 — Fitur
- Payout details on the partner page. Each partner adds the account Epidom pays their commission into (account holder, bank, IBAN or account number, BIC/SWIFT) on their own page, in French, English or Indonesian. Until they do, the page asks for it. Once saved, the account number shows masked, with an Edit button.
- Private. The details are stored encrypted. Only the partner and full admins see them; an admin given only the Referrals page sees whether they were provided, and someone viewing a partner's page read-only doesn't see them at all.
- Paying is quicker. On a partner's admin page, full admins see the details with copy buttons, and Record payout shows who to transfer to. Partners without details are marked "No bank details" in the Referrals list.
- Partner page without a store. A partner who has no store of their own no longer sees a "Back to stores" button that only led back to their page.
3.25.0 — Fitur
- Video thumbnail. The video box on all four sales pages, in every language, now shows the video's thumbnail with the play button over it. Tapping anywhere on it plays the video. The image is light (64 KB) and loads with the page.
- One "Access…" button per user. In the users list, the Manage menu now has a single Access… entry instead of three. It sets full admin, the admin pages someone may open, whether they're a referral partner (ticking it opens the partner form for that user next) and which partners' pages they can see read-only.
- "Admin" label. Users with admin access who aren't one of the founding admins now carry a yellow Admin label (full admins and those given some admin pages alike). Founders keep the red Master label; the Partner label is now green.
- Pick the partner, or invite them. The New referral partner form no longer asks you to type an email: pick the person from the users list, or invite them by email. An invited person receives an email, creates their Epidom account (or signs in) with that address, and lands straight on their partner page. A pending invitation shows on the partner's admin page, with a Resend invitation button.
- Sales pages in "Link opens". Where the partner's link goes is now one choice: the sign-up page, the home page, or a sales page they own (counted from a date you pick; no code and no customer discount for a sales page). The referral code now fills itself from the partner's name until you type your own, replacing the "Suggest a code" button.
- Partners land on their page. A partner without a store of their own goes to their partner page after signing in, rather than the store setup. Their page has a Sign out button.
3.24.0 — Fitur
- One video, four pages, three languages. The video boxes on /sales-page-1 to /sales-page-4, in French, English and Indonesian, now play Epidom's 5-minute presentation video. Nothing is downloaded until the visitor taps play, so the pages load as fast as before; the video then plays inside the box with the usual controls. Page 4, which had an empty frame, now has the same gold play button.
- Captions. The placeholder captions ("X minutes to…") now say 5 minutes.
- Served privately. The video stays in Epidom's private storage: each visitor gets a link that works for a few hours and only for this video.
3.23.0 — Fitur
- Referrals in the admin panel. A new Referrals page turns an Epidom account into a referral partner: their referral code, their commission (30% by default) on what each customer pays during their first months (3 by default), and the discount their link gives (10% off the first payment by default). A deal can be edited, paused or ended; changes count for new sign-ups only, and customers keep what they signed up with.
- From the users list. A master admin can make any user a referral partner from their Manage menu ("Make referral partner…"), choosing in the same step the sales pages they own, or open the partner's page if they already are one.
- Sales pages owned by a partner. A sales page, in all its languages, can be given to a partner from a "credit from" date. Every sign-up from it counts for them, including earlier ones from that date, with the payments they already made.
- Referral links with a discount. epidom.fr/register?ref=CODE, or the home page if the partner prefers. Visitors see "Bob's referral: 10% off your first payment" and the discount is taken off their first payment at checkout; with the POS free trial, off the first payment after it.
- Commission, refunds and payouts. Each payment a credited customer makes during their window earns the partner their share: a monthly plan for its first 3 months, a yearly plan for 3/12 of the year, a free trial nothing. Each commission waits 30 days before it can be paid, and a refund takes its share back. A full admin records each bank transfer to a partner, currency by currency.
- A page for each partner, and nothing else. A partner opens only their own page, "Partner dashboard" in their account menu, in French, English or Indonesian. A sales-page owner sees the report of their own pages (visitors, page views, how far visitors read, button clicks, sign-ups, sign-up and click rates, over 7, 30 or 90 days or all time) and the commission details: each payment, the part that counts, the commission and when it can be paid. A referral partner sees their link with copy and WhatsApp sharing and a link per channel. Both see their customers (business name, plan, ad, what each earned: never emails), their commission totals and their payouts.
- Someone else can follow a partner's page. On a partner's admin page, "Can view this partner's page" lets another account see exactly that partner's page, read-only: the same report, customers and commission details. They change nothing and earn nothing; the commission stays the partner's.
- Admin access page by page. An account can be given only some admin pages (Sales Pages, Partners, Meta Pixel, Feedback and others) from the users list. The users list and the activity log stay with full admins. All admin pages now share one menu at the top that lists only the pages you can open.
- Safer admin accounts. An admin added from the panel can no longer reset the password of, open a temporary access to, wipe or delete the account of one of the founding admins.
3.22.0 — Fitur
- Where each sign-up came from. When someone creates an account (email or Google), Epidom records the last sales page or partner referral link they opened in the 30 days before, the campaign tags of the ad or link that brought them (utm_source, utm_medium, utm_campaign, utm_content, utm_term), whether it was an ad click on Meta, Google or TikTok, the page they landed on and the site they came from. The account stays credited to that page or link.
- Tracked sign-ups in the Sales Pages report. The admin report now lists each tracked sign-up with where it came from, its ad and campaign tags, the plan it's on now (free, checkout not finished, trial, paying, payment failed or canceled) and whether it has created a store, plus a summary by campaign with sign-ups, trials and paying accounts.
- Sales-page visits last the full 30 days on iPhone. Safari kept the sales-page cookie for only 7 days, or 24 hours after an ad click, so a sign-up made later wasn't credited to the page. It's now also set by the server, which keeps it for 30 days.
- Policies updated. The cookie policy lists the new epidom_source and epidom_touch cookies, and the privacy policy says what is recorded about how you found us, in French, English and Indonesian.
3.21.1 — Perbaikan
- Changelog. Names such as epidom_consent_mode and epidom_sales_page were shown with part of the name in italics and the underscores missing. They now appear as written, on this page and in the app's "What's new".
- Meta Pixel report. Page views and automatic consents recorded on the sign-up and login pages now carry the visitor's language, like the rest.
3.21.0 — UX
- Only in Europe. Visitors in the European Union, the European Economic Area, the United Kingdom and Switzerland still see the cookie banner and choose first. Everywhere else there is no banner: analytics and advertising measurement are on from the first visit. The country comes from the visitor's internet connection; when it can't be told, the banner is shown.
- Still your choice, everywhere. "Manage cookies" at the bottom of the website and of the presentation pages turns analytics and advertising measurement off at any time, in every country.
- Policies updated. The cookie policy, the privacy policy and the GDPR page explain where the banner is shown, and the cookie policy lists the new epidom_consent_mode cookie.
- Meta Pixel report. The admin report counts visitors whose cookies were on by default separately from those who answered the banner.
3.20.0 — Infra
- Meta Pixel on the sales pages. /sales-page-1 to /sales-page-4, in French, English and Indonesian, are the ads' landing pages, but they had no pixel. They now have one. Once a visitor allows marketing cookies, Meta receives the page view, the sales page viewed and every click on a sign-up button. The ad's click id and campaign tags are carried on to the sign-up page, so a sign-up made there still counts for the ad.
- The cookie bar on the sales pages, sign-up and login. These pages had no cookie bar, so a visitor arriving there from an ad could never allow the pixel, and their sign-up never reached Meta. The bar now appears on them, in the page's language, with the same choices as the rest of the site. An answer given on one page is remembered on the others. A "Manage cookies" button at the bottom of each sales page reopens the choice, so it can be changed or withdrawn there too.
- Trials and subscriptions have a value. "Checkout started", "Trial started" and "Subscribed" now tell Meta the plan's price and currency, so Ads Manager can show what the ads bring in. A trial counts the first charge after the trial. Reloading the confirmation page no longer counts a subscription twice, and changing plan from the pricing page while already subscribed (which opens Stripe's billing page) is no longer counted as a checkout.
- WhatsApp contacts reach Meta. Tapping WhatsApp on the contact page, the home page, the pricing page's Enterprise plan or the Billing page now counts as a contact for Meta.
- Meta Pixel report in the admin panel. A new page lists every event the site sent to Meta over the last 24 hours, 7, 30 or 90 days: totals with their values, which pages sent them, a day-by-day table, how visitors answer the cookie bar on the site and on the sales pages, and how many new accounts Meta can see. Nothing in it identifies a visitor.
3.19.0 — Infra
- Sales page 4. /sales-page-4, /en/sales-page-4 and /id/sales-page-4 present Epidom on a single screen: a headline, a space for the video, the "Commencer gratuitement" button and three figures that count up when you reach them. It has the same language switcher as the other sales pages, and its button opens sign-up in the language you were reading.
- In the Sales Pages report. Page 4's visitors, reading, button clicks and sign-ups are counted like the other three's. The report in the admin panel now shows the four pages side by side on a wide screen, two by two on a tablet.
- Cookie policy. The description of the epidom_sales_page cookie now includes /sales-page-4.
3.18.0 — Fitur
- Approve overtime before it's paid. On the Schedule page's Hours tab, the owner can turn on "Overtime needs approval before it's paid". From then on, each day's overtime for each staff member waits with Approve and Reject buttons. Only approved overtime is added to pay. Waiting overtime is listed but not paid, and rejected overtime is never paid. Turned off, which is the default, overtime is paid automatically as before.
- Approve everything at once, or filter to what's waiting. "Approve all" approves every waiting day in the dates and staff you're looking at, after asking first. A new Show filter lists only days with overtime, or only overtime still waiting. Each decided day shows who approved or rejected it and when, and can be undone.
- Managers approve, but not their own. A manager can approve or reject anyone's overtime except their own, which goes to the owner. Only the owner can turn approval on or off. A day still on the clock, or still missing its clock-out, can be decided once the clock-out is recorded. If a corrected clock-out later adds more overtime to a day you already approved, only the extra waits for a new decision.
- Pay shows what's waiting. The Salary tab, the printed salary report and each staff member's My Pay tab at the till show approved overtime in the pay. Overtime that's waiting or was rejected is listed separately, and each day shows its status.
- Finance's labour cost is the payroll. Finance's Labour tab now uses the same calculation as the Salary tab: base pay, allowances and paid overtime, with a column for each. It used to be a separate estimate that left out allowances and overtime. Overtime waiting for approval is shown with a link to approve it, and isn't in the labour cost until it's approved. The Excel export has the same columns.
3.17.3 — Perbaikan
- Switching stores on a phone works. In the menu that slides in from the right, tapping the store name opened its list behind the menu, where you couldn't see or tap it. The list now opens on top of the menu.
- Sending feedback from the phone menu works. The bug icon in that menu had the same problem: the feedback form opened behind the menu. It now opens on top, along with its dropdowns and its delete confirmation.
3.17.2 — Infra
- Faster dashboards and reports. The dashboard alerts, the finance summary, margins by item, sales by shift, the shift report and your list of stores now fetch their data at the same time instead of one after another. The prep list reads every product's outstanding production in one go instead of one product at a time.
- Faster checkout at the till. Completing a sale checks the items, the customer and the table together. Changing one line on the bill no longer redraws the other lines, and adding an item no longer redraws the whole menu.
- Lighter storefront. The phone number field in the order and reservation forms loads when it is needed (it starts as soon as the first item goes in the basket), so a menu opens with less to download.
- Housekeeping. Unused files, images and one unused library removed; the project documentation brought up to date.
3.17.1 — Perbaikan
- The store picker is a plain list. Tapping the store name, in the phone menu or in the top bar, now just opens your stores to tap. There is no search box any more, so the keyboard stays down and the list stays in view. On a computer, the arrow keys and the first letter of a store's name still jump through the list.
- The website starts drawing sooner on mobile. Each page used to load two font files it never uses, competing with the page's own styles on a slow connection. They are no longer fetched up front.
3.17.0 — UX
- A picture for every shared page. A guide, a calculator, a blog article or a comparison shared on WhatsApp, Facebook, LinkedIn or X now shows its own title in its own language, instead of the same homepage picture for every link.
- Bigger dropdowns. Dropdowns that were meant to be easy to tap (in the POS dialogs, Fees & Taxes, the store form, the finance filters, the table picker and the product list) now really are 40 to 44 pixels tall. A styling rule was keeping them all at 36.
3.16.0 — Infra
- Pre-built pages. Every page of epidom.fr (home, pricing, features, guides, comparisons, blog, docs, legal) is now built ahead of time in each language and served from the CDN, instead of being put together on our server for every visit. Pages that used to take 0.3 to 1.5 seconds to start arriving now start in a few milliseconds.
- Missing pages say so. An address that doesn't exist on the blog or in the docs now returns a proper "not found" to search engines, instead of an empty page.
3.15.0 — Fitur
- Every article rewritten and expanded. The eight existing articles are now full guides, with steps, worked examples and mistakes to avoid. They also no longer say online ordering is free or that setup takes five minutes: the storefront page and its QR code are free, and online ordering is part of the POS plan (14 days free).
- Five new guides. In French, how to build a recipe card (fiche technique) and what to buy to open a café. In Indonesian, how to set a menu price that keeps you profitable and what a small café needs to start. In English, how to price a café menu, with a worked example.
- Tools and guides under each article. Each article now links to the calculators and guides in its language.
- Revision dates for search engines. A rewritten article tells search engines when it was last revised.
3.14.0 — Fitur
- Recipe cost calculator. Work out what one portion of a dish costs from your ingredients, and the price that keeps your food cost where you want it: calcul du coût de revient in French, kalkulator HPP in Indonesian, recipe cost calculator in English. Your numbers stay in your browser.
- Delivery-app commission calculator. A page of its own in each language, built on the homepage calculator: how much moving some of your delivery-app orders to your own ordering link would save.
- Guides for your business. New pages for cafés, restaurants and bakeries, and for the features owners ask about: online ordering without commission, the QR code menu, working offline, the kitchen display, stock and recipes, staff scheduling and receipt printers. Each one says which plan includes what, with its price.
- Ten new comparisons. Epidom next to L'Addition, Lightspeed, Innovorder, Popina and Zettle in France, and Pawoon, Qasir, Olsera, ESB and Kasir Pintar in Indonesia. Every fact about another product comes from its own website, with the date it was checked.
- Corrected comparisons. The existing comparison pages said online ordering was part of the free plan and that setup took about five minutes. Online ordering is part of the POS plan (14 days free), and those lines now say so.
- Guides & tools in the footer. Every page now links to the guides and calculators in its language.
3.13.0 — Fitur
- Your storefront in your language. Your public page, menu and order pages now open in your shop's language (Indonesian for a rupiah shop, French for a euro shop), so Google and first-time visitors read them the way your customers do. A visitor whose phone is set to another language still sees theirs, as before.
- Dishes on your storefront page. Your page now shows a few dishes with their prices under "On the menu", featured dishes first, with a link to the full menu.
- Better search results for your shop. Your page's title in Google now says what visitors get, for example "Ellens Bakery — Menu & Pesan Online", with your city when you have entered one. Your menu page lists real dishes in its description and is now offered to search engines too.
- Put your menu on Google Maps. Storefront → Google Reviews has a new card with your menu link and the three steps to add it to your Google Business Profile, so people who find you on Maps can open your menu.
- A lighter website. Each page of epidom.fr now loads only the language you read, about a third less JavaScript on English pages, and the browser-tab icon went from 580 KB to 8 KB.
- "Powered by Epidom" on storefronts now opens Epidom's site in the language the visitor is reading.
3.12.1 — Perbaikan
- Clearer search results. The homepage, Pricing, Features and Compare pages now say what they offer in their titles, in each language: "aplikasi kasir cafe" in Indonesian, "caisse" and "commande en ligne" in French, "POS" in English. The Pricing title shows the monthly price in your currency.
- Your storefront in Google once its menu is ready. A storefront page is offered to search engines once it has at least three available menu items. Test pages never are. Anyone with the link can still open the page either way.
- No duplicates in search. The sales pages, and the Careers, Press and Partners pages (which have no content yet), are no longer offered to search engines.
- Language links in the footer. The footer now links to the French, Indonesian and English sites.
- Plans and breadcrumbs for search engines. Search engines now get each plan's monthly price in the page's currency, and a breadcrumb trail on articles, guides and comparisons.
3.12.0 — UX
- See the POS before you sign up. A new section on the homepage, right after the headline, and on the Features page shows the real cashier, the order queue, the customer screen and an order placed from a phone. Tap a view to switch.
- The three spaces have pictures. The Storefront, POS Mode and Back Office cards on the homepage each show a real screenshot.
- A new sign-in and sign-up panel. The slideshow next to the form now shows the cashier, the order queue, the customer screen and the stock page, with its text in French, English and Indonesian.
- A new link preview. A link to Epidom shared on WhatsApp, Facebook or LinkedIn now shows the homepage headline, and the app's install prompt shows the real cashier.
3.11.0 — UX
- A shorter headline. The homepage now leads with "Realtime POS", with "transparent margin for cafés" underneath, and says everything runs from one website link, on a phone, a tablet or a computer.
- "Already have a POS?" starts with your country. The guide first asks whether your business is in France, Indonesia or another country, then lists the tills used there: L'Addition, Pawoon, ESB POS, Qasir, Square, Toast and Clover join the lists. "Switch fully to Epidom" is offered to every business outside France, whatever language you read the site in, and for four outlets or more the WhatsApp button goes to our team in France or in Indonesia, depending on where you are.
- Help one tap away. Under the headline, "24h online support" opens our two WhatsApp numbers, France and Indonesia, and "Last updated" now shows the year and opens this changelog. The free-storefront line is gone from there.
- Clearer contact in the FAQ. "Still wondering?" now lists WhatsApp France, WhatsApp Indonesia and our email addresses, one per row, each with its icon.
- The Features page opens on your problem. "Which problem costs you the most?" moved from the homepage to the top of the Features page, in place of its title section. Its "Calculate my commissions" button takes you to the homepage calculator with the platform's 30% already filled in. On the homepage, the three spaces now come straight after the headline.
- No more Sources list. The numbered footnotes and the Sources list at the bottom of the homepage are gone.
3.10.0 — Infra
- Three languages. Each sales page is also at /en/sales-page-N and /id/sales-page-N, with prices in dollars and in rupiah. The French page stays at /sales-page-N, and a visitor whose phone is set to English or Indonesian is taken to their language, as everywhere else on the site.
- The site's language switcher. The same FR · ID · EN switcher as on the website sits at the top right of every sales page, and a language picked there sticks on the rest of the site. Sign-up opens in the language the visitor was reading.
- Results per language. The Sales Pages report in the admin panel can show one language at a time, so the pages are compared on the same audience.
- Cookie policy. The epidom_sales_page cookie now holds the page's language as well as its name.
3.9.0 — Infra
- Three sales pages. /sales-page-1, /sales-page-2 and /sales-page-3 each tell the story a different way, in French. Every "Commencer gratuitement" button opens sign-up.
- Which page wins. Each page counts its visitors, how far they read and which button they press. The count uses no cookie and stores no IP address. When a visitor then creates an account, by email or with Google, the sign-up is credited to the page they came from. The comparison is in the admin panel, under Sales Pages.
- Cookie policy. It now lists the epidom_sales_page cookie, which holds only the name of the sales page you opened, for 30 days.
3.8.0 — Fitur
- Online orders are paid at the cashier. Customers still order from your storefront and the order still goes to the kitchen, but every order is now placed unpaid and paid at the counter. Online payment (QRIS, e-wallets, bank transfer, card) is switched off for now. The customer's order page shows the number to give the cashier and turns to Paid once they have paid. "Pay at Cashier" orders used to be recorded as paid the moment they were placed, before anyone had taken the money. They now stay unpaid until the cashier marks them paid. While online payment is off, the website (homepage, features, pricing and the guides) says customers pay at the counter.
- The till hears about it straight away. The notification bell is now on the POS too, with the same notification switch and sound settings. It rings the moment an online order arrives and marks the ones still to be paid. With notifications switched on for that device, it also gets a notification while the POS is in the background.
- Payment taken at the till counts in the till. An online order paid at the counter joins the open shift, so its cash is part of that shift's expected cash instead of the "cash sales not on a till" line. The customer's WhatsApp or email receipt is sent when it is paid (the same now goes for a Pay Later tab). An order marked paid from the Back Office stays off every till, and its payment fee follows the way it was actually paid, such as QRIS or card. A cancelled order can no longer be marked paid.
- Full screen for the till. A new button in the POS top bar, next to the bell, puts the POS in full screen with no tabs or address bar, the same as the customer display's. It shows on tablets and computers. iPhones don't allow full screen, so it isn't shown there.
- Only the person who opened a shift can end it. On the POS, Finish shift is locked for everyone else and says who can end it. The owner can still close any open shift from Shifts in the Back Office, after counting the drawer, and it shows as closed by the owner.
- Your guest WiFi on the receipt. Add the WiFi name and password under Profile → Receipt & WhatsApp. They print on every customer receipt and show on its receipt link, never on kitchen tickets or a bill. Each outlet has its own.
3.7.0 — Fitur
- See what each person has earned. The Schedule page's Log & History has a new Salary tab for the owner. For any period it shows each staff member's base pay, allowances and overtime pay, worked out from their clock-ins, and the total for the store. Open a row to see how it adds up and the days behind it, or print the whole report.
- Allowances for each staff member. Each person's Contract card on the Staff page now takes allowances, such as meal money for every day worked or a monthly position allowance, and an overtime rate per hour. A monthly salary or allowance is counted pro rata for part of a month. No deductions are made for absences: they are listed so you can decide.
- Staff see their own pay at the till. POS Mode's Operational page has a My Pay tab with what you have earned this month so far, and in earlier months. It only ever shows the pay of the person signed in.
- Hours show how far over or under. The Hours tab now shows each day's clock-in and clock-out times, the hours expected, the hours worked, and the difference, such as +1h 2m or −2h 30m. Expected hours come from the published roster for that day, or from your standard hours when there is no roster. Days someone was rostered but never clocked in, reported absences and days still missing a clock-out now appear too. They used to be left out.
- Overtime follows the roster. Overtime is time worked past the shift someone was rostered for, not past the standard day, so a 4-hour shift worked for 5 hours shows an hour of overtime. Hours worked on a rostered day off all count as overtime.
- Closing a forgotten clock-out asks when they left. Closing a clock-in someone forgot to end used to record the clock-out at the moment you corrected it, so a correction made two days later counted two days of work. The correction now asks for the time they left, starting at the end of their expected hours.
- Suspiciously long days are flagged. A day with more than 16 hours between clock-in and clock-out, usually a clock-out done much later, is marked on the Hours tab and in the salary breakdown so you can check it. Its hours still count as recorded.
- Open a selfie. Tap a clock-in or clock-out selfie in the log to see it full size, with who, when and where, and step through the others. The same works in your own history on POS My Schedule.
- Only the owner can change staff details. Adding, editing or deactivating a staff member, including their pay, now needs the owner's own login, not a staff persona on the owner's device. Pay rates, allowances and contract details are shown only to the owner, too.
3.6.0 — Fitur
- Every report has its totals. By Channel, Payment Method, Top Items, Item Margin, By Category, By Shift, By Shift Block, Daily and Waste now end with a total row (a totals card on a phone), and the Department split shows its total and each department's share. Top Items shows the top 20, everything else, and all items sold. By Category goes from the items subtotal to your revenue through one line for order discounts, service charge, tax and delivery fees.
- A P&L that adds up. The statement now reads Gross revenue → Discounts → Revenue → Refunds → Tax → Net sales → COGS → Gross profit → Processing fees → Platform commission → Waste → Net profit, every subtotal is the line above it minus what's in between, and each line shows its share of net sales. Below net profit come your recorded expenses, down to profit after expenses.
- Gross profit no longer counts tax as profit. Gross profit and margin are now measured on net sales (after refunds, without tax). Before, a store charging 11% tax saw its margin overstated. Net profit is unchanged except for the next two points.
- Delivery-app commission is in net profit. What GoFood, GrabFood and the other platforms keep (estimated from each platform's standard rate) is now subtracted from net profit, as it already was on the By Channel report.
- Tax on a refunded sale is no longer taken off twice. A refund already gives the tax back, so the Tax figure is now what you owe after refunds.
- New: Expenses. Record rent, electricity, wages paid, marketing and other running costs per day; the P&L subtracts them to show profit after expenses. Managers and owners can add, correct and delete them.
- New: Sales Patterns. Revenue by order type (dine-in, takeaway, delivery), a day-by-hour heatmap in your own time zone, your busiest hour and day, and revenue per guest.
- New: Discounts & Refunds. Discounts by reason or coupon, refunds by reason, cancelled orders and the most voided items, to spot leaks.
- New: Tax. Tax grouped by rate, with the taxable amount, tax charged, tax refunded and tax owed, ready for a PPN or TVA return.
- New: Labour. An estimated labour cost from each person's pay setup and the hours they clocked, with labour % and prime cost (COGS + labour) as a share of net sales. Each person's pay rate and cost are shown to the owner only; managers see the totals.
- More on the cards. The eight headline cards follow the P&L: revenue with orders and average ticket, net sales, gross profit with margin, net profit, COGS, tax owed, fees & commission, and waste. Orders still waiting for payment are flagged.
- Item Margin includes modifier costs (extra shot, toppings), as COGS always did, and labels each item Star, Workhorse, Puzzle or Dog (menu engineering).
- Each report says which filters it ignores. For example, By Channel isn't filtered by channel; the report now says so instead of quietly showing unfiltered figures. The Department split follows the channel and payment filters like the cards above it, and the department filter can pick your custom product line.
- Fixed: the PDF left out the last day of the date range (by default, today). It now covers the same dates and filters as the screen, and adds the Payment Method and Item Margin sections.
- Fixed: the Waste tab failed whenever a till session was picked.
- Excel export has the same totals, the new reports as their own sheets, and the P&L with each line's share of net sales.
- On a phone, the sixteen reports are picked from a list; on a wider screen the tab bar wraps instead of scrolling. The open report is kept in the address, so a refresh or a shared link opens the same one.
3.5.0 — Fitur
- Choose how to fill your Data page. A store with nothing in its lists now opens the Data page on a choice instead of empty tabs. Import your menu from a photo or a PDF with any AI assistant: the fastest way, and the one we recommend, because the POS sells from your menu. Or build everything step by step from raw materials: slower, but stock, costs and margins then add up from purchase to sale. Each option says how it works, what you get and what to know first. You can skip the choice and go straight to the lists.
- Menu import on its own screen. Choosing the photo or PDF option opens the three import steps with Products already selected. Once the import is saved, the page tells you your menu is on the POS and your storefront, with a button to open the POS and one to add the raw materials and recipes behind it.
- A step-by-step guide. The other option shows raw materials, recipes and menu items as three steps. Each one ticks itself off as you add items, the next one is highlighted, and the menu step is marked "Needed for the POS".
- The POS sends you to add your menu. Opening the POS with an empty menu now takes you to the Data page, or to the storefront's Menu tab on the POS plan, with a note saying why. As soon as your first item is saved, the note offers to open the POS. Staff who can't edit the menu see who to ask instead of an empty screen.
- Imports reach the POS sooner. After a Smart Import, the POS menu refreshes straight away instead of at its next 30-second check.
3.4.0 — Fitur
- A homepage about your margin. It opens on a full-screen photo of a real café counter with one line, "from the order to the margin", the 14-day POS trial and a margin calculator side by side, a way in for anyone who already has a till, and three plain facts: the free storefront, WhatsApp support and the latest Epidom release.
- Pick the problem that costs you most. Commissions, margin, stock, internet drops, queues, staff or too many apps: pick one to see what it costs, how Epidom fixes it and which plan does it. Every figure links to its source at the bottom of the page.
- A margin calculator. Enter your revenue, your share from delivery apps and their commission to see what moving some orders to your own link could save each month and each year, after payment fees and the plan price. It works in your currency, saves nothing, and says plainly when Epidom would cost you more.
- Already have a till? Three quick questions (what you use today, how you want to start, how many outlets) recommend a plan and say what you keep, what changes and what to know first.
- The plan you pick comes with you through setup. Choose POS or Operations on the pricing page or the homepage before you have an account: sign-up and store setup follow, with that plan already ticked on the last step. Publishing your store then goes straight to checkout for it. Ticking POS or Operations during setup does the same, and the last step says so before you publish. Leave both unticked to stay on Free.
- The 14-day POS trial: add a card, pay nothing today. Checkout saves your card and charges nothing for 14 days; cancel from Billing before then and you are never charged. The page after checkout now says your free trial has started and nothing was charged, instead of "Payment Successful!", and takes you on to your new store. If checkout doesn't open, or you come back from it, the "Your store is live" screen keeps a button to try again.
- Leaving checkout is not a failed payment. Backing out of checkout now reads "Checkout canceled" instead of "Payment Failed", "Try again" reopens the plan you were buying, and an owner who was setting up their store goes on to it.
- Already set up? Signing in from a plan button opens that plan's confirmation on the pricing page, on the same monthly or yearly billing.
- Clearer about the Free plan. Free is your storefront and its QR code; online ordering and payment are part of the POS plan. The pricing page and its FAQ now say so too.
- The website menu on an iPad held sideways. At exactly 1024 pixels wide the header showed neither its links nor the menu button; it now shows the links. On phones the menu button is bigger and easier to tap.
- The POS menu: the stock count no longer covers the item name. On items without a photo, the "5 counted" tag sat in the corner on top of a long name like "Cream Cheese Honey Chilli". It now sits next to the price, and moves under the price when a tile is too narrow for both.
- Removed: the old homepage sections (how it works, before and after, feature lists, use cases, setup steps, dashboard preview) and the closing email box. Smooth scrolling is now off for anyone whose device asks for less motion.
3.3.7 — Perbaikan
- A sale is kept when the internet drops mid-sale. If the wifi was connected but the internet wasn't, or the connection died while a sale was being saved, the till said the order failed. The sale is now saved on the device and sent when the connection is back, and it can't be recorded twice.
- Offline sales land on the right day and the right shift. A sale made offline was recorded at the moment it synced, so one rung up at 23:50 and synced the next morning counted towards the next day. It never counted towards the shift's cash drawer either. It now keeps the time it was rung up, and counts towards the shift it was made in while that shift is still open.
- Nothing is deleted from the device without you. A sale or production log the server refused five times used to be deleted, even when the device had only lost its connection five times or its sign-in had expired while offline. Connection problems no longer count, an expired sign-in asks you to sign in again, and an entry the server really refuses is kept and marked "Needs attention". Review it from the offline banner, the POS menu or Offline & Sync: try again, download a copy, or discard it.
- The offline banner tells the truth. It now uses the app's own connection check, so it also shows when the wifi is up but the internet is down.
3.3.6 — Perbaikan
- The customer screen finds the till again on its own. When a till tab closed or reloaded, including the automatic reload after an update or a staff PIN switch, the customer screen could stay on "Customer display is off" with the setting still on. The order and the WhatsApp button stayed hidden until the cashier next changed the cart. The screen now keeps checking for the till and comes back by itself within a few seconds.
- "Off" only when it is off. When no till is driving it, the screen now says it is waiting for the till and what to open, instead of claiming it was switched off. When it really is switched off, it says where to turn it back on.
- Asking for the customer's number wakes the screen. "Ask customer for details" now brings a customer screen out of standby and opens the number pad, where it used to be ignored.
- Clearer setup. The till now says the customer screen works on the same computer and in the same browser as the till, and the screen's browser tab is named "Epidom — Customer display" instead of showing a bare link.
3.3.5 — Perbaikan
- You pay in the currency on the page. Checkout for a plan picked its currency from where you were connecting. Someone in Indonesia was charged in rupiah even when their store and the Billing page used euros, and was offered card only. Checkout now uses the currency the Billing page quotes (your store's), or the one the pricing page shows for your language. PayPal works in euros and dollars but not in rupiah, so it can now appear on a euro or dollar plan once it is turned on.
- One checkout at a time. A checkout you opened earlier and left open is closed when you start a new one, so you can't end up paying for two subscriptions.
3.3.4 — Fitur
- Paying for a plan is no longer card-only. Checkout for POS or Operations, or for a price we quoted you, only ever offered a card. It now shows the payment methods Epidom has turned on with Stripe, so PayPal appears as soon as Stripe enables it for subscriptions. Your plan then renews on the method you paid with.
- The page after a cancelled or failed payment no longer assumes a card. It said "Your card hasn't been charged" even if you paid another way. It now says you haven't been charged.
3.3.3 — Perbaikan
- Recipe cards show their orders again. The number of orders in the last 30 days on each recipe card never loaded, because the request behind it failed every time. It now loads.
3.3.2 — Perbaikan
- From a photo or a PDF to your lists. The Data page now opens with three steps: copy a ready-made prompt, give it to any AI assistant (ChatGPT, Claude, Gemini and others) with a photo or PDF of your menu, then import the file it returns. There is a prompt for products, raw materials, recipes and suppliers, in your language, and the same prompt is one tap away inside Smart Import.
- Smart Import works for a menu with no SKU column. Importing products from a sheet that had no SKU column imported nothing and still said the import had succeeded. Each new product now gets a code made from its name, such as PATE-A-BANANA, which you can change afterwards. A SKU in your sheet is kept as it is.
- Smart Import tells you what went wrong. The result screen lists the rows that were not imported and the reason for each, and counts rows that had no name. An import where nothing went in is shown as failed. A file that could not be read now says so on the upload step, where it used to return there without a word.
- Prices written "10.000" import as ten thousand. In a store whose currency has no cents, such as rupiah or ariary, a price with dots between the thousands was read as a decimal: Rp 10.000 became Rp 10. Currencies with cents and quantities are unchanged: 1.500 kg is still one and a half.
- Rows go where you said. A row with only a name, such as a menu item with no price, was imported as a supplier. It now follows the type you chose in Smart Import.
- Recipes import one by one. A recipe whose first row gave no yield was merged into the recipe above it. In a store that does not use rupiah, ingredient prices and batch costs from an imported recipe were saved without converting the currency.
- The till fixes a cart that holds items no longer on the menu. When you charge or save a bill and an item has since been removed from the menu or switched off, the till no longer just shows an error. It updates the cart from the current menu, takes out what can no longer be sold, tells you what changed, and you charge again. An item that was deleted and added back is recognised by its name and charged at its current price.
- Each store keeps its own cart. The cart used to follow you from one store to another, where none of its items could be charged. A cart you leave in one store is now waiting there when you come back.
- Items from your second product line can always be charged. One hidden from the online menu still appeared at the till but was refused at checkout.
- A sale made offline is kept when the menu changes. If an item was removed from the menu before the till came back online, the sale was dropped. It is now recorded at the price charged, with a note on the order.
- Plain messages at the till. When the server has a problem, the till says the order could not be created instead of showing a technical message.
- Pages, filters and sorting on the Data page respond straight away. On Products, Materials and Recipes, going to the next page or changing a filter or the sort order could keep showing the first page for up to half a minute. The Recipes tab also showed only 20 recipes for a moment when a store had more.
- Product cards. "How it's made" was shown in English whatever your language, and a product with no cost showed a margin of 100%. It now follows your language, and the margin stays empty until a cost is entered.
3.3.1 — UX
- The Epidom logo on the log-in page takes you home. After a session had ended, clicking it could send you straight back to the log-in page instead of the homepage.
- A tidier Orders header. The POS, Online ordering and Log tabs are now sized to their labels, and the Active Queue switch sits on the right, the same as on the Kitchen & Bar page.
- Status counts under the search. On the Orders page, the status tiles now sit below the search, filter and sort controls.
- POS Mode's space switcher matches the app icon. In the POS Mode menu, each space's icon sits in a frame styled like the Epidom app icon, in gold for the space you're in.
- Enterprise on the pricing comparison. The feature table now lists what only Enterprise includes: custom development, custom integrations and custom SLAs.
3.3.0 — Fitur
- Set up your store in three steps. Setup now asks for what matters first: your store's name, country, city and what kind of place it is. Then your storefront, with your logo, colour, a short tagline and your first three items, next to a live preview. Last, what you want Epidom to help with. Each step is saved as you go: close the page and you pick up where you left off, with a Back button on every step.
- Your country sets up the rest. Choosing your country sets your store's currency, payment methods, delivery platforms, time zone and language. A store in France now starts in euros, with card, cash, cheque, bank transfer and Titre-Restaurant, and Uber Eats, Deliveroo and Just Eat at the till. Before, every new store started in Indonesian rupiah and on Jakarta time. Prices you type during setup are in your own currency.
- Fill it in from Instagram. A screenshot of your Instagram profile can still fill in your name, tagline, logo and colour. It's now a shortcut on the first step instead of a screen of its own.
- Your store is live. When you publish, you get your store link, a QR code to print, a WhatsApp share button and a one-minute tour.
- A tour of Epidom's three spaces. A short tour explains the storefront, POS Mode and the Back Office, which plan each part needs, and how to move between them. You can replay it from Help.
- A setup checklist on your dashboard. New stores get a "Get your store ready" list that ticks itself off as you go: publish, add items and photos, add your logo and WhatsApp number, get your first visitor. On the right plan it adds your first sale, shift, tables, stock, suppliers, staff and schedule. Parts your plan doesn't include say what they would add. Hide the list whenever you like.
- Help inside the app. A new Help page, at the bottom of the sidebar and under Help & what's new in POS Mode's menu, has the guides, the latest updates, WhatsApp support and a button to show page tips again. Four new guides cover POS Mode, stock and supplier orders, staff and schedules, and printers and the scanner. They're also on the website's Docs.
- Page tips. The first time you open Stock, Schedule, Shifts, Finance, Customers, Data, the storefront editor, the Order Queue, Kitchen & Bar, Tables, Operational or Hardware settings, a short card explains what the page is for. Got it hides the card.
- Creating another store starts with what matters. The Create a store dialog now asks for the name, country and city first, and can copy the currency and payment settings of one of your other stores. The photo, address, phone and email are under More details. A new store now gets its payment settings, a draft storefront and you on its staff list straight away, instead of starting in rupiah. When you reach your plan's store limit, the button and the message name the plan that allows one more.
- Set your business time zone. Profile → Business info now has the time zone used for attendance, shifts and reports. It used to be fixed to Jakarta.
- Signing up takes you to setup. After you confirm your email you go straight to setup, instead of the website's home page, and the "check your email" page opens instead of sending you to the login page. New Google accounts go to setup too.
- Security. An old card-check promotion that could still grant the POS plan for free is removed. The scrambled copy of the owner PIN is no longer sent to the browser, and guessing the owner PIN or its reset code is limited to a few tries a minute. Removing an uploaded image now only ever deletes your own files.
- Image uploads explain themselves. When a logo or photo can't be uploaded (too big, wrong type, no connection), the upload box now says why, in your language. Replacing an image keeps the old one until the new one has uploaded, so a failed upload no longer leaves a broken picture.
- Clearing a store's details works. Emptying the address, phone or email in Edit store now clears it. Before, the old value quietly stayed.
- The Order Queue opens on the current shift. While the till is open, Active and History start with every order since the shift was opened, storefront and delivery orders included, and Back to current shift returns there. With no till open they open on today, as before.
- Removed. The AI logo generator, which only ever produced a placeholder, and the AI menu suggestions, which only knew Indonesian dishes. Setup is now fully translated into French and Indonesian.
3.1.2 — Perbaikan
- Card payments on your storefront go to your own Stripe account. If your store hasn't connected Stripe yet, customers are asked to pick another payment method instead of the card payment landing with Epidom. Card totals are also sent at the right amount.
- A plan you switch in the billing portal stays switched. Your plan now follows the price you actually pay, and refreshing your billing no longer marks a trial or an overdue subscription as cancelled.
- Subscribing again replaces the old subscription completely. An overdue or trial subscription you replace is cancelled, so you are never billed twice. A late payment on a subscription that has ended no longer reopens it, and a delayed or repeated notice from Stripe can no longer cancel the subscription you have now.
- Your billing record stays linked. A brief Stripe outage can no longer detach your account from its billing history.
3.2.0 — Fitur
- Financial reports on Operations. Finance, with its P&L, margins, fees and cash reconciliation, is now part of the Operations plan instead of Enterprise. It's where your recipe costs and multiple outlets pay off.
- All your outlets in Finance. The Owner dashboard is now the All outlets view of Finance: switch between This outlet and All outlets at the top of the page. Each outlet shows revenue, gross profit, margin, waste and net profit for the same dates, with a total row, an Excel export and a link to that outlet's full report. Old Owner links open it.
- Outlet figures match their own report. An outlet's row in All outlets now shows the same net profit as its own Finance page, after refunds, tax and card fees. When your outlets use different currencies, each one is shown in its own currency and money isn't added across them.
- Full report from the dashboard. The dashboard's Analytics section links to Finance for the same dates.
- Operations covers up to three outlets. A fourth outlet needs Enterprise. Your Billing page shows how many stores your plan allows and which plan allows more. Taking over a store from another owner counts too.
- Enterprise is for chains and custom builds. Enterprise covers four or more outlets, your own system designed and built with our team on Prionation, custom integrations (accounting, delivery apps, payments) and dedicated support. Talk to us on WhatsApp. Custom Development requests are now open on every paid plan.
- Compare plans without leaving the app. Billing now lists POS, Operations and Enterprise with their price, what each one includes and a Monthly / Yearly switch. You no longer need to go to the website.
- Change plan from the card. Upgrade, downgrade or start the 14-day POS trial from Billing: new subscriptions go to Stripe to pay, and subscriptions you already pay for open Stripe's billing page. Enterprise opens a WhatsApp chat with us.
- Prices you'll actually pay. Plans show the exact price in your store's currency (euros, dollars or rupiah) instead of a converted estimate.
- Unlimited stores say so. Store usage on an unlimited plan now reads "2 / Unlimited stores" instead of "2 / stores".
- Database backups on demand. Admins can start a backup from the Capacity page with Run backup now, and watch it finish in the backup history, instead of waiting for the nightly job.
3.1.1 — Perbaikan
- Your plan switches on when you pay. After checkout, POS or Operations now turns on straight away. Before, Epidom couldn't read Stripe's current payment notices, so a successful payment could leave the account on Free.
- Missed and recovered payments are tracked. A declined renewal now marks the subscription as past due, and paying it puts it back to active.
- Cancellations you schedule stay scheduled. Choosing to cancel at the end of the period now shows on your Billing page until the period ends.
3.1.0 — Fitur
- POS System and Operational. The More menu now opens POS Mode's two spaces. POS System is the till with its four tabs (Cashier, Orders, Kitchen & Bar, Tables). Operational puts your shift, your schedule, the team's schedule and clocking in on one page, without the bottom tabs.
- Team Schedule in POS Mode. Owners and managers see the week's roster and schedule photos published in the Back Office, read-only, with previous and next week. Drafts never show.
- Clock in and out on the page. Clocking in is a tab of Operational instead of a pop-up, with Back buttons on every step.
- Hardware settings. Printers and the barcode scanner for this device, in one place from the More menu. You can test your scanner from any POS screen.
- A tidier More menu. The sync state sits right under Sync sales, the customer display's open-window button sits beside its switch, Send feedback moved down, and the printer button only shows on the till screens.
- New customers save themselves. When a new customer finishes on the customer display, the till saves them and adds them to the sale. The cashier no longer taps Save.
- Typing feedback on every on-screen keypad. Keys press and ripple on the PIN pads and on the customer display's keyboard and number pad, and typed characters pop in.
- Your Stores, redone. Each card shows the store's storefront cover, logo and colour, its slogan, currency, total revenue, customers, staff and market. Tapping a store asks whether to open the POS System or the Back Office, picking up where you left off. The POS Cashier shortcut on the card is gone.
- Signed-in pages stay in the app. On Your Stores and Profile the logo no longer leads to the website and the back arrow is gone. Log out to see the website.
- More French. Clocking in, My Schedule, the Team Schedule and the More menu's account options are now in French.
3.0.0 — Fitur
- POS Mode is for the counter. Cashiers and kitchen staff get the till, orders, the kitchen and bar display, the customer screen, tables and clocking in, with large buttons and no management pages in the way.
- The Back Office is for running the business. Owners and managers get stock, the menu, staff and schedules, finance, the storefront and settings, grouped by task. Enterprise stores can see all their outlets side by side without leaving the Back Office.
- The till is rebuilt. It has a live receipt-style bill, customers and loyalty points, discounts, split and merged bills, online delivery orders, up to four printers and a screen facing the customer.
- One shift for the whole store. Every cashier and device shares it, and past shifts are kept in a shift history.
- Staff sign in with their own account and go straight to their store's till.
- The website is available in English, French and Indonesian on every page, including the legal pages.
- Compare is in the website's top menu, between Pricing and Contact, and stays highlighted on each comparison page. On laptop screens the menu no longer runs under the logo or squeezes the Try Epidom button onto three lines.
- Meet us. A new button at the bottom of every website page opens Prionation's booking page, where you can set up a meeting with the team.
- Our address is now Canggu, Bali at the bottom of every website page, in every language.
- A new logo. The grocery-bag icon and the ÉPIDOM wordmark replace the old square icon and lettering on the website, the sign-in page, the Back Office top bar and the till's menu. Printed reports, the customer screen and staff invitations show the new wordmark.
- Start Trial. The button at the top right of the website now says Start Trial instead of Try Epidom.
- The Status page is gone. It was never connected to live monitoring. Old links to it now open the Contact page, where you can report a problem.
- The Features page describes Epidom 3. It opens on the three spaces (the storefront, POS Mode and the Back Office) and now covers what the last releases added: customers and loyalty points, the customer screen, up to four printers, one shift for the whole store, rosters and clock-in, supplier orders and the stock log. Each feature names the plan it needs. Claims the product doesn't back up are gone: allergen and nutrition labels, tax-authority exports (FEC), auto-translated menus, next-day payouts, story-ready product cards and reorder alerts by WhatsApp. The sample bills, recipes and reports now show euros in French, rupiah in Indonesian and dollars in English.
- Footer tidy-up. "Build Your Own" now reads "Build your app", and the Prionation credit shows its name without the icon.
2.104.0 — UX
- Item | Delivery Order. The tabs at the top of the Stock page are now Item and Delivery Order. Delivery Order holds what was under "Reorder & Deliveries", so the Items / Reorder & Deliveries switch under the title is gone.
- History is now the Log. It has its own tab, set apart on the right of Item | Delivery Order.
- Grid, Columns or List. A switch next to Select All changes how items are laid out, like the till's menu. Grid shows full cards, Columns shows smaller cards with more to a row, and List shows one row per item. Cards are as tall as their content. The Stock page remembers your choice on each device.
- Details in a pop-up. Tap an item to open its details: stock information, expiry date, and the Adjust Stock, Record Waste, View History and Reset to 0 buttons. The checkbox on each item still selects it for Bulk Adjust without opening it.
- The reason is optional when adjusting stock. Adjust Stock and Bulk Adjust no longer ask for a reason before saving. Leave it blank and the adjustment is recorded without one.
- The Log records every stock change. Stock set when adding or editing a product, and stock brought in by a CSV or Smart Import, now write a Log line, as materials already did. Materials used in production show as a reduction, not an increase.
- More in each line. Each line says what happened (Sale, Delivery, Produced, Used in production, Adjustment, Waste, Returned), the reason, the time (now on phones too) and the stock left afterwards.
- The whole history. Load more goes back through everything. Search and Oldest first now cover the whole history, not only the last 50 lines. A date range now includes its last day.
- Re-importing products keeps their stock. A product sheet without a stock column used to reset every product it matched to 0. Now their stock is left as it is.
- Bulk Adjust works. It used to open with "No items selected" and could not be saved, which also blocked Adjust Stock for products. It now opens with the items you picked.
- Adjust Stock works for every material. It only knew the 50 newest materials, so saving an older one did nothing. It also showed the previous material's name when opened for another one.
- Till fixes. Switching store from the More menu with items in the cart no longer freezes the till behind the menu. A table picked for a saved bill is marked free again once the bill is paid without the kitchen display.
- Finance reports show Just Eat's commission as 14% instead of 14.000000000000002%.
2.103.0 — UX
- Creating an order places it. The "Orders to Place" list and its "Mark as Placed" button are gone. A new order goes straight to Awaiting delivery. From the "Order created" screen you can print it or send it to the supplier by email or WhatsApp.
- The status updates itself. Each open order shows "Arrives tomorrow", "Due today" or "Late by 3 days", worked out from its expected delivery date. There is no status to pick any more. Late orders are listed first.
- One tap to receive. Received shows what goes into stock ("Flour +5 kg"), and Yes adds it and records today as the received date. There is no date to pick. Not yet leaves the order in the list, where it turns late on its own.
- Expected delivery defaults to tomorrow in the create and bulk-order dialogs. You can still change it.
- Other actions are in the ⋯ menu: send to supplier, print, change the date or notes (for a delivery the supplier pushed back), and cancel an order that will never arrive.
- Delivery history (received and cancelled orders) is folded away under the list. Tap it to open.
- Stock can't be added twice. A double tap or a second device on Received used to be able to add the same delivery to stock twice. Now only the first tap counts. Receiving also no longer overwrites stock that a till sale changed a moment earlier.
2.102.0 — UX
- More sits next to your name, top right. It is the Epidom logo, right after the staff name button, with no gap between them. The bottom tab bar now holds only the till's pages.
- The name button shows the name only. It no longer repeats the role ("Owner · OWNER"). On a narrow phone a long name is shortened instead of pushing the buttons off the screen.
- A side menu instead of a pop-up. It slides in from the right: 40% of the screen on a tablet or computer, most of the screen on a phone. The Epidom logo heads it, above a card showing who is signed in: name, role, store and, on your own account, your email.
- Sync sales, and the sync state at a glance. A Sync sales button at the top sends any sales saved while offline and refreshes this device's copy of the menu and orders. A strip along the bottom says "All sales synced", how many offline sales are still waiting, or that the till is offline.
- One row per action. Shift, My Schedule, Clock In / Out, Send feedback, the customer display, language, dark mode, zoom and the account actions each have their own row. The page you are on is highlighted.
2.101.0 — Fitur
- Dine In | Take Away | Others. A third option sits next to Dine In and Take Away. "Others" lists the delivery platforms of your market: GoFood, GrabFood and ShopeeFood in Indonesia; Uber Eats, Deliveroo and Just Eat in France; Uber Eats, DoorDash and Grubhub elsewhere. "Other platform" is always there for anything else. The market is the one set in Back Office → Fees & Taxes. Pick a platform to key in an order that came through it.
- Each platform in its own report row. Platform orders are recorded against their platform. The daily and shift report's "By sale type" block gets a line per platform (GoFood, GrabFood, …). The finance Channels report and its channel filter count them with the platform's commission taken off. Receipts, kitchen tickets, the order queue and order history name the platform too.
- Pax, table and customer in one dialog. The pax chip left the order-type row. Tap "Add Customer" (or the attached customer) to set the number of guests, with − / + in the middle and a row of numbers 1–30 to tap underneath. The same dialog holds the table and, optionally, the customer. The row shows the pax and table at a glance.
- Tables from your Tables page. The table field is a dropdown of the tables you registered, with their seats and status. Picking one links the order to that table and marks it occupied. "Custom…" still lets you type any table that isn't registered.
- The customer can type their own details. The customer section has the Customer display switch and an "Ask customer for details" button. It opens the WhatsApp number form on the customer screen. A returning customer is added to the sale automatically; a new one fills the form in the dialog for you to save.
2.100.0 — UX
- Food / Drink tabs replace "+ Add filter". All, Food (burger icon) and Drink (wine-glass icon) now sit next to the search box on the till and are always on screen, plus a tab for your second product line if you run one. Food lists the items whose Department is "Kitchen / Food" and Drink the "Bar / Drink" ones. An item made in both the kitchen and the bar appears under both.
- Categories are cards. The menu opens on one card per category, with its item count. Tap a card to see its items, with their names, prices and photos. A Back card sits in the top-left corner, and a "Categories › Drink › Coffee" trail runs above them. Changing tab takes you back to that tab's categories. A menu with a single category opens straight on its items.
- Search still covers the whole menu. Typing or scanning looks through every category of the current tab at once. Clear the search to go back to where you were.
- On a phone, the cart button floats and the search row is shared. The cart button is a floating circle at the bottom right, just above the tab bar, instead of a row of its own at the top. The search box and the Food / Drink tabs share one row, with Food and Drink shown as icons.
- Department is explained in the Back Office. When you add or edit a product or a storefront menu item, a note under the Department field says it chooses the till's Food or Drink tab as well as the kitchen or bar ticket. An empty Food or Drink tab on the till points you to the same setting.
2.99.0 — UX
- Privacy, Cookie and GDPR pages — rewritten and translated. They were English-only. They now exist in French, Indonesian and English, on the same layout as the Terms and Refund pages, and describe what Epidom really does: which providers handle your data (payments, email, hosting, nightly backups, analytics, messaging), what is collected — including staff attendance photos and location — how long it is kept, and how to use your rights. The old "EU servers (Frankfurt)" and "Data Protection Officer" claims are gone, and the French Terms now include the account-deactivation and data-retention clause.
- Partners, Careers, Press, Status and the public Changelog are translated. Changelog dates follow your language, a note says release notes are written in English, and "Back to Home" keeps you in your language.
- Search results and link previews match the page language. Titles, descriptions and share cards on the marketing pages appear in French, Indonesian or English instead of always English. The pricing snippet shows the price in your currency, the home snippet no longer calls the cashier free (the storefront is free forever; the POS has a 14-day trial), and the server-rendered page now declares its language.
- Each language version points search engines at itself. /id and /en pages are no longer treated as copies of the French page, French blog posts and guides no longer advertise English and Indonesian versions that don't exist, comparison pages say which language they really serve, and two unsupported claims (a founding year, a site-search box) are gone from the structured data.
- Sitemap dates are real. Articles report their publication date; pages with no known date report none instead of the moment the sitemap was built.
- The old /payments page now redirects to /pricing in every language.
- Removed the unused waitlist form, which sent visitor details to a third-party server.
- Plan prices and the support contact are each defined in one place, with tests that fail if a translated price or a hardcoded support address drifts.
- Footer copyright, "Infrastructured by", screen-reader labels and the 404 page links follow your language. The account deactivation and reactivation settings are translated into French, and attendance address lookups now identify themselves with the real support inbox.
2.98.0 — Fitur
- Pricing has its own FAQ. What happens after the 14-day trial, changing plan or cancelling, and refunds — with a link to the refund policy — instead of the homepage FAQ repeated.
- Docs guides end with a "Start free" card, and blog posts have a byline. The card links to sign-up in French, Indonesian and English. Posts show "Epidom Team" with the date and reading time; a named author with a photo can be added per post later. Both sign-up buttons record a click.
- Partners: a supplier application path. Coffee roasters, frozen-food distributors and bakery suppliers pick what they supply and open a prefilled WhatsApp message (or email us).
- Careers and Press have real buttons — "tell us about yourself" and "Media inquiries" — instead of email addresses to copy by hand.
- The home trust bar shows what Epidom works with (Stripe, Xendit/QRIS, WhatsApp) instead of shop names.
- Ready for real proof, invisible until it is real. Customer logos, case studies and team cards are now data-driven sections that stay hidden until real, sourced and consented entries are added. The About page shows an honest "small product team" block instead of placeholder avatars and a numbers card.
2.97.0 — Perbaikan
- Tighter plan activation. The free-plan activation now only ever activates the Free plan, and the service behind it refuses anything else.
- Signing in only follows links inside Epidom. A crafted
?next=link can no longer send you to another site after you log in. - Your email stays out of the page address. The sign-up box on the home, services and pricing pages used to put what you typed into the URL, where analytics and server logs could see it. It now carries it to the sign-up form privately, keeps your place when you flip between Log in and Create account, no longer claims a "magic link" is on its way, and stacks on phones so the French button no longer squeezes the field.
- Sign-up and login forms are fully French and Indonesian — the "or continue with email" divider, the buttons and every validation message. Passwords must be at least 8 characters on sign-up (what the server always required); signing in no longer checks length.
- Pricing in French, and honest. The trial badge, note and button are translated; the trial banner no longer says "no card" (a card is required, and the trial is POS-only); French feature lists match the real plans; the Free card no longer lists POS and KDS; the plan-change dialog and its errors are translated in all three languages; only POS is marked "Most popular"; the Operations button no longer says "Start free trial"; and customers already on a paid plan are no longer offered the trial.
- The pricing sign-up path remembers your plan. A signed-out visitor who picks a plan goes straight to sign-up and comes back to the pricing page (POS reopens the free-trial dialog), and the plan-change dialog works from the keyboard — focus moves in, Tab stays inside, Escape closes.
- Unverified numbers, testimonials and claims removed. "500+ businesses", "20+ countries", "10k orders a day", a 4.9 rating, made-up customer quotes with outcome statistics, and invented shop names are gone; the hero shows four real product facts instead. The home, services and About pages no longer promise menu-photo import, an official WhatsApp API, WhatsApp pings, daily reports emailed at midnight, or "5-minute setup" — they say what Epidom actually does.
- Contact reaches us. WhatsApp is now the main way, with a message already written for you (France and Indonesia numbers, matched to your language). The contact form, which never sent anything, is gone, and so are an unsourced "under 4 hours" reply time and Indonesia-only support hours.
- Cookie choices are real. A "Manage cookies" button in the footer lets you change or withdraw your choice at any time. Google Analytics and the Meta Pixel are not loaded at all until you accept them; withdrawing stops them and deletes their cookies; the Meta image fallback, which could not honour consent, is removed; switching language no longer counts as refusing cookies; and the banner says plainly what Accept turns on and links to the Cookie Policy.
- The Cookie Policy no longer says things that were false ("self-hosted analytics", "no third-party trackers", "no advertising cookies"). It lists what actually runs, including that, if you accept analytics, Google Analytics also receives in-app page addresses, some in-app actions and the details of a sale rung up at the till.
2.96.0 — Fitur
- Zoom on the till. The More menu in POS Mode now has a Zoom row next to Language and Dark mode, so a cashier can fit more of the queue on a small counter screen, or enlarge everything for a kitchen display, without leaving the till. Until now zoom only existed in the Back Office account menu.
- Zoom in the Back Office menu on a phone. The menu that slides in from the right now has a Zoom control alongside Language and Theme — where you'd look for it on a phone. The account menu still has it too.
- A phone can't be zoomed into a broken layout. Zooming in leaves the page less room to lay out in, and a phone has little to spare: at 125% or 150% the page was laid out narrower than the app is built for, so content could run off the edge. A phone now zooms out freely (70–90%) but stops zooming in once the page would get too narrow, and the control says so — "Screen too narrow to zoom in further". Tablets and desktop keep the full 70–150%.
- Your choice is kept. Turn a tablet to portrait and the zoom steps down to what fits; turn it back and your 150% returns. The saved setting is never overwritten just because the screen was narrow for a moment.
- Fix: a few sizes ignored the zoom. The slide-in menu's width, and the "no orders" and "no tables" screens on the till, were sized against the physical screen rather than the zoomed page, so at 70% or 150% they came out too narrow, too wide or too short. They now follow the zoom like the rest of the layout.
2.95.0 — Fitur
- Give your number on the customer screen. The customer types their WhatsApp number on the pad; the till checks whether they're already a customer. A returning customer is greeted by their first name and is attached to the sale automatically — no name or email asked for. If you've already picked a customer yourself, that choice is never overwritten.
- New customers can leave a name and email — both optional. A number the till doesn't know opens the New customer form on the cashier side with the number already in it, then fills in the name and email live as the customer types them on the customer screen's on-screen keyboard. You review it and tap Save & attach — nothing is saved from the customer's side. Anything you type yourself in a field is left alone.
- The New customer form is WhatsApp-first. The WhatsApp number comes first, and name and email are optional — a number alone is enough (the customer is named after their number until you rename them in Back Office).
- Receipts email themselves. When an order is paid and the customer left an email — on the customer screen, or on their customer record — their receipt is emailed to them automatically. No extra tap.
- Is the receipt emailed? Now you can see. The Sale complete screen and an order's details in Order Queue → History now say whether the emailed receipt went out — sent (to which address, and when), failed (and why), on its way, or not sent yet — and offer Resend once it has been sent.
- Fix: History said "sent via WhatsApp" for emailed receipts. The receipt line under an order showed the last send, whichever way it went, as "Receipt sent via WhatsApp". WhatsApp and email each have their own status now.
2.94.0 — Fitur
- One shift per store. A shift now belongs to the store, not to whoever opened it. Open it as the owner and every cashier who signs in — on that tablet or another — sees the same running shift, and their sales count against it. The Shift page says Started by the person who opened it, so a cashier taking over no longer sees "No active shift". A second shift can't be opened while one is running, and if two people race to open or finish one, the loser is told it's already open or already ended instead of "try again". Anyone signed in to the till can finish a shift someone else opened. If your store already has several open shifts from before this change, the newest shows first; finish it and the next one appears. Reports name who opened a shift, not who rang each sale or who closed it — that's how a shared shift works for now.
- Shift history. Under the current shift on the Shift page: every finished shift, newest first — who ran it, how many orders, the cash counted and whether the drawer balanced. Tap one to open its full report.
- Shifts, in the Back Office. A new Shifts page, next to Schedule, is the manager's shift report. For any period, and one person if you like, it lists each shift with its opening cash, cash sales, expected and counted cash and the difference — shifts that don't balance are in red, each with a link to its full report. A Cash log tab lists every cash in and out with the reason it was recorded; a shift's opening float and closing count are labelled as such, not as cash in and out. Managers have the page, and anyone who could see till cash on the Schedule page keeps that view — managers with a saved page list that includes Schedule are given Shifts too.
- Attendance is only attendance. Log & History on the Schedule page no longer mixes till cash into the clock-in timeline. Opening cash and closing counts moved to the Shifts page.
- Schedule image. On the Work Schedule page, choose Shift blocks (the grid, as before) or Schedule image: upload a photo or screenshot of your schedule for the dates shown, and every staff member sees it on their My Schedule page as soon as it's uploaded. Upload again for the same dates to replace it, and add a short note for the team if you want. Your choice is remembered on that device.
- A sale can't land on a shift that's already finished. If a tablet hasn't noticed yet that the shift was finished on another tablet, its next sale now goes to the shift that is running, or is left unlinked if none is — it used to be filed on the finished shift, which threw off that shift's cash and made the next drawer read over.
- The Shift page keeps your count. A brief connection drop while you're typing the counted cash no longer replaces the page with a "couldn't load" message and wipes what you entered. Signing in as another cashier no longer briefly shows "No shift" when one is open.
- Fix: buttons cut off at the end of a shift. In the end-of-shift dialog the Print button was clipped at the left edge. The buttons now wrap instead of overflowing, Print is shorter, and Copy link is just an icon.
2.93.0 — Fitur
- Switch store. The More menu now shows the store you're in. Tap it to see your other stores and jump straight to that store's till — not its back office. If a sale is in progress you're asked first, because it belongs to the store you're leaving and would otherwise follow you into the next one; use Save Bill beforehand if you want to keep it. Staff who sign in with their own account see the stores they work at, and a store where their role has no till screen is greyed out.
- Language. One tap between FR, ID and EN.
- Dark mode. A switch for light and dark — the whole row is the tap target, not just the small switch.
- Fix: Order Queue scrolls again. On the Order Queue page, both the Active and History tabs ran past the bottom tab bar and couldn't be scrolled, so orders further down the list were unreachable. The page now scrolls.
- Scan barcode button, now inside the search box. Tap it to open a panel with a Test field — scan any barcode to see whether the scanner works, whether your menu has that code, and how slowly the scanner sent its keys, without adding anything to the sale. Below it, two settings kept on that tablet: Scan anywhere on this screen (turn it off and a scan only counts when typed into the search box) and Scanner speed — choose Slow / Bluetooth if scans are sometimes missed. Not yet tried with a real hardware scanner.
- Menu view options moved. The grid / columns / list switch is no longer in the top bar; it now floats in the top-right corner of the menu, over the tiles, and stays there as you scroll — it no longer takes a row of its own.
- Charge has a menu button beside it. Discount and Reprint Last are no longer separate buttons above Save Bill — open the ☰ button to the right of Charge and pick them, along with split, merge, coupon and the rest, from the More menu. Reprinting the last receipt is now two taps instead of one. The Add Customer row shows a person icon.
- Flatter top bar. The search box and its scan button, Add filter, the printer button and your name button in the top bar are now square and run the full height of the bar with no gap above or below, and show a pointer cursor. In the top bar, Add filter lost its dashed outline and is now just the label until you hover it; the Add filter buttons on the Order Queue and Order History filter rows are unchanged.
- The Order Queue opens on today. On both the Active and History tabs, the orders you see now start as today's — everything placed since midnight on your own clock — instead of every order there has ever been. Change it from the date box next to Unpaid (Yesterday, Last 7 days, All time…) and tap Reset to today to come back. The date box is always there, so you can always see why an older order isn't listed. The POS / Online ordering counts and the Unpaid count cover the same day, and the "orders unpaid" alert opens All time so none are missing. Everyone gets this once, whatever they had filtered before; their other filters are kept.
- Fix: "Today" starts at your midnight. In History, "Today" and every other date range began hours after midnight for anyone east of UTC — around 07:00 in Indonesia — so an order rung up in the small hours landed under yesterday. Days now run 00:00–23:59 on your own clock, and a "Today" left open past midnight moves on to the new day by itself.
- Light mode is navy, not grey. Buttons, the Charge button, the active menu-view switch and the POS Cashier / Back Office links were a neutral dark grey in light mode; they are now the deep navy dark mode is built on, with cream text. Text, muted text and borders take a navy tint too — muted text is easier to read — and the dim behind pop-ups is navy instead of black. Dark mode is unchanged.
- The Active Queue switch has room. On a narrow screen the switch on the Order Queue page no longer sits against the edge of the screen.
- Switch Account shows who's signed in. When you open Switch Account, the person already signed in on this tablet — or Continue as owner, if that's you — is highlighted in amber, and tapping it takes you straight back with no PIN to re-enter.
- Shift label matches the connection label. The "No shift" / open-shift label in the top bar is now the same compact size as the green Connected label. Its tap area is unchanged.
- Fix: signed out means back to the login screen. Opening Your Stores after your session had expired or ended showed "Failed to load stores — Unauthorized" with a Try Again button that could never work. You are now sent to the login screen instead — both when you open the page and if your session ends while it is already open. Other pages already did this.
2.92.0 — Fitur
- Printers, in the top bar. The printer button now lists every printer in use with its own Connect button, and Printer settings opens one card per printer: switch it on, pair it, choose 58mm or 80mm paper, choose whether it prints by itself, and press Test print to see its real layout on paper. The icon turns amber when only some of your printers are connected, so a dead kitchen printer can't hide behind a healthy receipt one.
- Receipt printer (main). Cashier to customer: the receipt with prices and total, the bill and the shift report — unchanged. A till that already had auto-print or a paper size chosen keeps both.
- Kitchen and bar order tickets. Items, options and quantities in large bold type, with the table, order type, guests, the call-out number and any notes — and never a price. Each item goes to the printer for its area, using the same Kitchen / Bar setting the kitchen screen already uses. If only one of the two is set up it prints everything, in a section per area. A custom item with no area prints nowhere, exactly as on the kitchen screen.
- Item labels. One sticker per item ordered — two coffees are two stickers, "1/2" and "2/2" — with the call-out number, item, options, note and time. Pick your printer's language (ESC/POS or TSPL), the sticker size and gap, and whether stickers are for all items, kitchen items or bar items.
- It prints when the order is placed — the same moment the order reaches the kitchen screen. A printer that should have printed but isn't connected is never silent: a warning names it and offers Connect & print, and a print that fails partway offers Try again. A printer that stops responding is disconnected and named instead of freezing every other printer. On the send-receipt screen, Print order tickets prints a second copy, marked REPRINT.
- The call-out number on paper. Tickets and labels carry the order's #12; the server now returns it when an order is created.
2.91.0 — Fitur
- A new Google Reviews tab in Storefront. Paste the review link from Google Business Profile ("Get more reviews") or your Google Place ID, and Epidom checks it and sets up the review button for you. You can test the link, download a QR code to print for tables, the counter or receipts, pause the button any time without losing the connection, or disconnect.
- It shows up on your storefront by itself. Once connected, a Review us on Google button appears on your public storefront page, and your Google Maps link is added for you — no second field to fill in (a Maps URL you entered yourself still takes priority). When an order is delivered, the customer's order page asks for a review too.
- See whether it works. Storefront → Analytics has a new Google Review taps card counting taps on the button.
2.90.0 — Fitur
- A Shift page of its own. Opening a shift, watching it and ending it moved out of My Schedule (which keeps clock in/out, your roster and your history, in POS Mode and in Back Office) into Shift, in the More menu. With a shift open it shows who is on charge, when it started, the opening cash, and a red Finish shift button, with Cash in / out and a live shift report beside it. With none open it asks for the opening cash. The account owner works a shift as the store's owner profile.
- The shift label in the top bar. A green Shift · 18:00 while your shift is open, an amber No shift when it isn't; tap it to open the page. Kitchen accounts, who never count cash, don't see it.
- Finish shift. The cash the till is accountable for, line by line — opening cash, cash sales, refunds, tips, float top-ups, paid out, safe drops, tips paid out — down to the expected cash, then the amount you actually counted and the live difference (balanced, over or short). Beside it: when the shift started, how many orders it took, every other payment method with its order count and total, and a note for whoever opens next. The count is typed blind: it is never pre-filled with the expected figure, and End shift stays off until you enter one. Back leaves the shift open.
- Confirm, then the report. Ending asks first, showing expected cash, counted cash, the difference, other payments and your note; it can't be undone. Afterwards the shift report is printed on the receipt printer when one is connected, previewed on screen exactly as it prints, and reachable by link (Copy link, Open report). With no printer connected nothing pops up — Print report pairs one, or on an iPad (no Bluetooth printing) opens the browser's print dialog.
- Also fixed on the way. Sales rung up after a shift was opened, without signing in again, were not attached to it — so their cash was missing from the drawer's expected total — and sales after a shift was closed kept attaching to the closed one. The till now follows your real shift on every POS screen.
2.89.0 — Fitur
- A live bill on the right, with the order queue one tap away. The cart is now a receipt that updates as you ring up — items, discount, sub-total, service charge and tax with their rates, total. An Order Queue button with a live count jumps to the queue, and a Dine In / Take Away switch (with guests and table) lives on the bill itself, so Save Bill and checkout inherit it instead of asking again.
- Save Bill, Print Bill, Reprint — always in reach. "Hold order" is now Save Bill (a rename: bills already held work exactly as before). Print Bill prints a provisional bill that says it is not a receipt. Reprint Last stays visible, and a More sheet holds the rest: Custom Item, Split Bill, Merge Bill, Discount, Coupon, Redeem Points.
- Add a customer without a pop-up. A row at the top of the bill expands in place: search as you type, or add someone new, or leave it empty. The chip shows their points and lifetime spend. A new Customers page in Back Office lists everyone with customer-since, member-since, points, life spending, orders and last visit, exports to CSV, and opens each customer's orders and points ledger (owners and managers can adjust points). Life spending is always computed from real orders, never stored, so a refund or cancellation can't leave it wrong.
- Discounts, coupons and loyalty points (Operations plan). A new Promotions tab on the Data page manages discount presets (percent or fixed), coupons (code, minimum spend, use limit, dates) and the points rules. At the till the cashier taps a preset, types a coupon or redeems points; the server re-prices every one of them and never trusts an amount sent by the tablet. A coupon's last use can't be spent twice by two tills, points are earned once when a bill is paid, and refunds and cancellations take points back proportionally (a second partial refund reverses only what the first didn't). Amounts are literal in the store's own currency.
- Split a bill by items, or pay one bill several ways. By items, each bill becomes its own order and the discount is shared out to the cent. Or one bill can be paid with several methods — an equal split between 2–10 people, or any mix such as cash plus card — recorded as one order with a Split payment method and one line per payment. The cash drawer counts only the cash part, method breakdowns list the real methods, refunds ask which payment gives the money back, and receipts print one line per payment. Orders from before this release are read exactly as they were.
- Merge Bill. Combine saved bills into one. The merged-away bills are cancelled, never deleted, and drop out of the reports.
- Custom Item. Ring up something that isn't on the menu — a description, a price, and a printer area (Kitchen, Bar or none, which skips the kitchen screen).
- A send-receipt screen after every sale. Instead of a small print prompt: the amount paid, the change in large type, an email box and a WhatsApp box to send the receipt (email receipts are new), a Print Receipt button, and New Sale. A receipt can be sent at most 10 times per order.
- Cash quick-buttons. The exact amount plus the round-ups a customer hands over — 50.000 / 100.000 in rupiah, 5 / 10 / 20 / 50 in euro and dollar, and sensible steps for any other currency.
- Grid, Columns or List, and barcode scanning. Choose how the menu is laid out (remembered per device). Products have an optional barcode field beside the SKU; a hardware scanner adds the item straight to the bill, and the search box matches barcodes too.
- Less top bar, and Switch user. The search, filters, view switch and scan button now share the status bar's row on tablets, giving the menu roughly one more row of items. The staff badge is a Switch user button.
- Also fixed on the way. Receipts on 58 mm paper no longer overflow: two lines that could run past the edge (an ordinary cashier name, the "powered by" footer) now wrap, so a few receipts print slightly differently. A held bill's discount reason is cleared when the discount is removed. History's department filter now finds custom items. The public receipt page shows what was tendered and the change. A table's status can no longer be changed from another store's order.
2.88.0 — Fitur
- POS and Online ordering tabs. The queue opens on two tabs, each showing how many orders are open in it. The Online count turns red while anything is waiting there, so a cashier working the POS tab can see an online order arrive. GoFood, GrabFood and other channels count as Online. This replaces the hidden "Source" filter; a queue saved with the old "All" setting opens on POS.
- Queue numbers. Every order started at the till, saved as a held bill, or placed on the storefront now gets a number — #1, #2, #3 — that restarts each morning in the store's own timezone. The bills of a split checkout share one number. Orders from before this release, and GoFood/GrabFood email imports (which carry the platform's own code), show a dash instead. You can search the queue by number ("12" or "#12"), and the number appears in Order History too, both in the table and in an order's detail.
- Split view, now the default. Three columns: a status list with live counts, the orders list — order number, time, queue number, customer, table and total — and the details of the order you tap: who and where, the items, how it was paid, the charges and total, with the same Start / Mark as Paid / Resume / Cancel actions as the cards. On a phone or portrait tablet there is no room for three columns, so the status list becomes a row of chips and the details open as a sheet from the bottom. Grid, Compact and Board are still one tap away in the view switcher.
- Order History gains Queue and Table columns.
2.87.0 — Fitur
- Invite a staff member to sign in with their own account. Open a team member in Staff Management, make sure they have an email, and choose "Send sign-in invite". They get a single-use link, valid for 7 days, to create their Epidom account — or, if that address already has one, to sign in with it and link it. Once claimed, the staff card shows a green "Verified account" label: it means the address was proven by actually opening the link, not merely that an email was sent. A link that hasn't been used yet shows "Sign-in invite sent"; re-sending replaces the old link, and changing the person's email or deactivating them cancels it.
- Only the invited address can claim a link. The emailed link can create an account for its exact address, but it can never attach an account that already exists unless you are signed in as that account. Someone signed in to a different account who opens the link is refused, and the attempt is recorded in the audit trail as a critical action. Each link works once, and everything that can change between sending and claiming — the person being deactivated, their email being edited, an account appearing in the meantime — is checked again at the moment of claiming.
- They land in their store's POS, and their PIN still applies. After signing in, a staff account sees its store on the Stores page (tagged "Staff", without the owner's edit and delete actions) and opens straight into the POS pages their role allows. They still pick their name and enter their PIN, exactly as on a shared tablet, and they can only ever act as themselves. Their store runs on the owner's plan; they never see billing, and no plan is created for them. Staff accounts are POS-only for now: Back Office and every owner-only page stay closed to them, so Back-Office-only roles (Admin, Finance) can't be invited yet and the invite button says why.
- A linked staff account can do what a PIN user on the owner's tablet could — reading the store's currency and tax settings, cycling a table's status, managing reservations — but not more. Access is default-deny: anything not explicitly allowed for staff accounts stays owner-only, and editing tables, refunds and other manager actions still need a manager PIN. Cash-in/cash-out entries are not yet available to staff accounts.
- Contact details and Contract are optional and collapse when empty. A new team member starts with both cards closed; open one to fill it in. An existing member's card opens only if it already has something in it. "Pay rate" is now "Contract": pick Freelance, Part-time, Full-time or Contract, and set pay per hour, a monthly salary, or a percentage of sales (capped at 100%).
2.86.0 — Fitur
- You can now transfer a store to someone else, verified by email. Profile has a new "Transfer store ownership" card: enter the new owner's email, confirm by typing the store's name, and they get a link that's valid for 7 days. Nothing changes until they accept it while signed in to a verified account on that exact address — if they don't have an account yet they create one first, and the link survives sign-up and email verification. Accepting moves the store to their account in a single transaction and the previous owner loses all access to it: their Owner staff row is deactivated and stripped of contact details, and every live staff PIN session on the store is ended. The store keeps behaving the same for its new owner — a store that was inheriting currency, tax and payment methods from the old business gets them copied onto itself first, and a business created for the recipient inherits the store's timezone and language. Billing stays with each account (the store runs on the new owner's plan; the transfer itself charges and refunds nothing), and storefront card payments follow the new owner's Stripe Connect account, or fail closed until they connect one. Only the real owner can start, view or cancel a transfer — not a Manager or any staff account, even on the owner's own signed-in device. The link is a bearer secret, so it is single-use, only ever travels in request bodies (never a URL the audit trail or platform logs would record), and each step is recorded as a CRITICAL audit action.
- Switching from the Owner to a Cashier no longer leaves the Owner's page on screen. Picking a staff member on the "Who's using this device?" screen only updated the client, so a Back Office page already rendered for the Owner (Staff Management, say) stayed visible under the Cashier's name. It now reloads so the server-side access checks run for the new person and send a Cashier to POS.
- "Switch Account" is now one thing everywhere, and no longer costs you your PIN. The separate "back to Owner (PIN)" shortcut is gone from both the Back Office menu and POS's "More" menu. Switch Account shows the picker without ending your session, so backing out returns you to exactly where you were — labelled "POS" or "Back Office" for the screen you came from — with no PIN re-entry. Only picking someone else, or "Log Out of Staff Session", changes who is signed in.
- The Owner row in Staff Management is now locked down. Its page access reads MASTER — every page, all checked and not editable — and its Role, Contact Details, Pay Rate and PIN fields are removed or explained (the PIN that matters is the Owner PIN in Profile), with a pointer to the transfer card.
- Sign-in and sign-up honour a `?next=` deep link (validated as a same-site path), so an invited new owner lands back on the transfer page after verifying their email; Google sign-in now respects it too.
2.85.0 — Fitur
- `/owner` (the multi-outlet Enterprise rollup) moved into the dashboard shell. It previously lived entirely outside it — no nav entry, no shell chrome, unreachable by any staff persona, discoverable only via one button buried in Finance — despite
nav.owner's translation key already existing, unused, in all three locales. Now gated and reachable exactly like Finance, with the old bare/ownerredirecting so existing links still resolve. Each row in its per-store table now links to that store's own Finance page, closing a drill-down gap the roadmap had named as a Phase 5 acceptance criterion but never shipped. - `/menu` and Storefront's Menu tab were duplicate entry points into the identical editor, inconsistently gated —
/menurequired the POS plan, Storefront's tab (a FREE-tier user's actual only path to publish a menu) had no gate at all. Consolidated into Storefront's Menu tab (?tab=menuURL sync);/menuis now a redirect. A staffer previously grantable only the narrower/menupermission keeps that exact access — Storefront now detects a menu-only session and renders just the editor, no other tabs. - Nav rail cleanup left over from Phase 1's POS Mode split: the "Point of Sale" section had been reduced to a single orphaned item after that split moved everything else out. Regrouped by job — General, Operations, Reports (Finance + Owner), Account (Profile, Billing, Custom Development) — instead of by leftover plan-tier labels.
- Locked nav items now explain the business event that earns the tier, not a generic "Upgrade to X" — matching this project's own written upsell philosophy (STRATEGY.md), and rendered as a visible line rather than a hover tooltip that was invisible on the mobile drawer.
2.84.1 — Perbaikan
- Materials/Products pagination could still get stuck showing only the first page. The server-rendered initial list only ever fetches one page (50 materials / 20 products), and the client seeded its
totalfrom that page's own length instead of the real store-wide count — so on every fresh page load, pagination computedtotalPages: 1and simply didn't render for roughly the first 20-30 seconds, until a background refetch quietly corrected it. Explains "sometimes it works, sometimes we're blocked at 50" exactly, and the identical bug existed in Products too (latent — no store had tested it with >50 products). The server component's real count is now threaded through instead of being thrown away. Verified: calling the actual server fetch function directly against this store's data now returnstotal: 94, correctly computing 2 pages. - Cancelling or refunding an order didn't visibly change the Finance page — or the dashboard's own analytics tiles. The order-status and refund mutations never invalidated any revenue-reading query. Checked against the live database first: the underlying numbers were always correct (all
NON_REVENUE_STATUSES-filtered routes exclude cancelled orders correctly); only the screen wasn't told to refresh, and it wasn't just the Finance page's dozenfinance-*queries — the dashboard's own analytics tiles use an entirely separateanalytics-*key namespace (found by cross-referencing every revenue-filtering API route against its actual consumer, not by re-reading the ticket), plus the Enterprise multi-store rollup (owner-summary) and the Storefront editor's analytics tab (storefront-analytics*). All four are now invalidated together, and forced to refetch even if the tab showing them isn't the one currently open. - Live Operations card — confirmed correct, and now proven correct end-to-end. The absence-exclusion fix from 2.84.0 is intact; this store had never published a schedule or recorded a single attendance/absence event, so the card could only show its all-zero empty state regardless of the fix. Reproduced the exact scenario against real data: published a shift, confirmed the staff member appeared in the late list (783 minutes late), reported an absence for them, confirmed they were correctly excluded — then cleaned up the test rows. See STATUS.md for the same steps if you want to see it in the UI yourself.
2.84.0 — Perbaikan
- You can now see every raw material you have, not just the first 50. The Data > Materials list quietly capped itself at 50 items with no way to reach the rest — with 94 materials entered, 44 were simply invisible. It now paginates the same way Products already does (10/20/50/100 per page, with page controls), and the Management > Edit Stock list (which showed the same capped count) now requests the API's full 100-item page instead of the 50-item default.
- Stock can now be reset to 0 in one click. Management > Edit Stock's "Adjust Stock" only ever took a manual +/- delta, so zeroing out an item meant typing its exact current balance by hand. A new "Reset to 0" quick action posts the correct adjustment for you.
- Cancelling an order now says, in the confirmation itself, that it stops counting toward your financial reports — it already did, but nothing told you so, and the confirmation dialog's French translation was missing entirely (silently falling back to English).
- A staff member you marked absent for the day no longer sits in the dashboard's "not clocked in" list with a climbing late timer. The Live Operations card excluded on-duty and clocked-in staff from that list, but not staff who'd already reported an absence — so someone correctly marked absent still showed up looking later and later all day.
- A production batch's ingredient table no longer shows raw English words on a phone. Three of its mobile-only field labels ("Qty:", "Cost:", "Total:") were hardcoded instead of translated, so they stayed in English even on a French or Indonesian device.
- A day can now hold more than one shift for the same staff member (e.g. 8h-10h and 14h-16h as two separate blocks). The roster grid already stored multiple shifts per day correctly but only ever let you add or edit the first one — there's now an explicit "add shift" control per day, and clicking any existing shift edits that one specifically.
2.83.0 — UX
- The dashboard menu is in the order you actually work in. Each group now opens with the page you reach for first: your storefront sits directly under your profile, the menu comes before the till in Point of Sale, and Operations starts with your data rather than your stock. Nothing was added or removed — the same pages, in the order a day actually runs.
- When you ask us who changed something, we can answer with a person now. Our internal activity log named whoever made each change but never said which restaurant they were working in or how to reach them, so two cashiers who share a first name at two different outlets were impossible to tell apart. Its per-person list now shows the restaurants each one worked in, their email address and their job title beside the name, and the log can be searched by restaurant.
- Those answers are also correct now. Whenever we narrowed the log to one severity or one outcome, the "destructive" and "refused" counts sitting beside each person threw that filter away and used their own instead of narrowing inside it, so somebody with three actions on screen could be shown a destructive count drawn from a far wider set. Two people could also have their counts merged when an automated job and an external service happened to share a name.
- The detail panel is readable again. Its text ran flush into both edges of the panel with no margin, and on a screen with the app zoomed in or out it either stopped short of the bottom or pushed its own buttons off the end. A revert preview also used to stay on screen when we moved to a different entry, which could aim an undo at the wrong action.
2.82.0 — Fitur
- You can now see everything that happened to your account. Your profile has a new Activity section with two lists: what you did, and what was changed on your account by someone else. Until now, if support reset your password or changed your plan, nothing anywhere told you it had happened.
- And you get an email the moment it does. Any change support makes to your account — a password reset, a temporary password, a plan change, a reactivation — now sends you a message straight away. If it wasn't expected, there's a "This wasn't me" button next to the entry that flags it for the team to look at.
- Nothing an administrator does goes unrecorded any more. Every action on the platform is now written down: who did it, when, from where, and what it touched. Deleting an account, resetting someone's data, changing a plan or granting admin rights all raise an alert to the team as they happen.
- Most mistakes can now be undone. Where an action can be safely reversed, it can be reversed from the log with one click — after a preview that says exactly what will change. Where it cannot, the log says so and explains why instead of pretending. Deleting an account or wiping a store now takes a full copy of the data first, so it can be put back.
- The log refuses to do anything dangerous. It will not undo a change if someone else has since edited the same thing, if the order sits inside a till session that has already been counted and signed off, or if reversing it would leave your stock figures disagreeing with your stock history. In each case it tells you which of those it is.
- Your feedback tickets refresh by themselves. The admin feedback list used to only update for whoever clicked; a status changed anywhere else stayed invisible until a reload.
- Activity is kept for 12 months and then permanently deleted, matching what the Terms already promise. Copies taken before a deletion are held for 90 days.
2.81.0 — Fitur
- The daily report now tells you how much cash should be in the register. Until now it printed what you sold and, if you'd picked a specific till session, the float you started with — but never the one number you actually need at closing time. Open the daily report and there's now a Cash on Hand block: opening float, cash sales, refunds paid back out, tips, cash in, paid-outs, safe drops and tips handed to staff, then the total that should be sitting in the drawer.
- It works while you're still open. The expected-cash figure used to be written only at the moment a cashier closed their till, so mid-day it simply didn't exist. It's now calculated live, and a report printed mid-shift is labelled provisional so nobody mistakes it for a signed-off end-of-day count.
- A day with two cashiers finally adds up. Running the report over a date range instead of one till session now gives a store-wide figure covering every till that was open, plus cash taken at the counter that wasn't tied to any of them.
- New Cash In / Out button at the till. Record a tip, top up the float, pay a supplier in cash, or drop takings into the safe — each one lands in the drawer maths straight away and shows up on the staff log with its reason. Anything that takes money out requires a reason, because "the till is short" and "we paid the vegetable man" should never look the same.
- Three counting mistakes fixed. An order marked delivered but not yet paid was being counted as cash in the drawer, so tills looked short whenever a customer hadn't settled up. Refunds were never subtracted at all. And a refund issued the morning after a sale was silently rewriting the previous day's takings — refunds now count against the day the money actually left the drawer.
- The Finance cash page and the dashboard show the same numbers. The cash reconciliation table gained the full per-category breakdown and a totals row, and the dashboard's open-till card now shows expected cash alongside the float it started with. Every screen reads from one shared calculation, so no two of them can disagree.
2.80.0 — Fitur
- Customers can now enter their own WhatsApp number on the customer screen. A single button on their side of the till opens a panel explaining that if they leave a number, their receipt is sent to that WhatsApp once they've paid. It's the only thing on that screen they can touch — everything else stays read-only.
- The number types itself into the cashier's checkout. Whatever the customer enters lands in the phone field of the payment dialog, so nobody has to read it out across the counter or key it in twice. It only ever fills an empty field — if the cashier already typed a number, the customer's entry never overwrites it, and the cashier still reviews it and still confirms the order.
- No keyboard needed. The number is entered on a large on-screen keypad, because a customer-facing screen usually has no keyboard, and on a touch monitor the pop-up keyboard covers the field you're filling. The country code sits next to it with the flag, defaulting to your store's own country, and all 245 countries are there behind a searchable list.
- The number is checked before it goes anywhere. The confirm button only lights up once the number is actually valid for the chosen country, so a half-typed number can't reach the cashier's form. Customers can remove a number they entered by mistake, and it's cleared automatically once the order is placed so the next customer never inherits it.
2.79.0 — UX
- Every page now loads about a third less code. The app was sending all three languages — English, French and Indonesian — to every device on every page, roughly 550KB of text to display one language. Each language is now its own download, and you only get the one you read. On the dashboard that is 118KB less over the connection and about 370KB less for the browser to work through before anything appears, which is felt most on the Android tablets behind the counter. Nothing was dropped: every phrase in all three languages is still there.
- The low-stock check on every dashboard page got much lighter. Opening any dashboard page loads the alert badge in the sidebar, and that was reading every column of every raw material you have, plus every column of every supplier attached to them, to use nine values. It now asks only for what it shows. The alerts themselves are unchanged — including the warning for materials that have gone below zero.
- Pages stop asking the database the same question twice. Checking that a store is yours ran two lookups one after the other, and repeated them for the layout, the page and again underneath. Those two now run together and the answer is reused for the rest of the request. The staff-session check had the same problem and got the same fix.
- The Profile page fetches its two pieces of data at the same time instead of waiting for the first before starting the second.
- Prices and offline status no longer re-render the whole screen. Two pieces of shared state that wrap the entire signed-in app were rebuilding themselves on every render, which made every component reading them redraw too — several times per page load. They now only change when something in them actually changes.
2.78.0 — Fitur
- The till can now show the order on a second screen facing the customer. Turn it on from the new screen icon in the Cashier header, open its window, and drag it onto your customer-facing monitor. It mirrors the register live: the item you just rang up in large type on the left over your store's logo, the full running receipt on the right, and the total across the bottom. Nothing on it is tappable — it's a display, not a second register.
- It's off by default and set per till. The toggle sits next to the printer settings and works the same way: each device remembers its own answer, so a counter with a customer screen can have it on while the tablet by the kitchen doesn't. Switching it off puts an open display window into standby instead of leaving a stale order frozen on it.
- The display wears your storefront's branding. It takes your logo, display name and theme colour straight from your storefront, and picks light or dark text so a pale brand colour stays readable. A store with no storefront yet falls back to its own name and the Epidom mark.
- A settled order says thank you. When you confirm a payment the customer's screen shows the confirmation, the amount and the order number for a few seconds, then clears itself ready for the next customer. It works offline too — an order queued while the connection is down gets the same confirmation.
2.77.0 — Perbaikan
- A new order now appears in Orders to Place straight away. Creating an order saved it correctly — with its supplier, quantities and expiration date — but the list on the Stock page kept showing "No Orders to Place", so it looked like nothing had happened. Two separate faults were hiding it: the page loaded only orders already sent to the supplier, skipping the ones still waiting to be placed, and every refresh after that read the reply from the server in the wrong shape and came back with nothing at all. Both fixed, and the list now also stays correct after you mark an order as placed.
- "Send to Supplier" works again. It reads a single order the same broken way, so the dialog opened with nothing loaded and its Send button permanently greyed out.
- Errors say what went wrong. Failing to create, update or cancel an order showed "[object Object]" instead of the reason, and a store on the POS plan got a blank "Failed to fetch supplier orders" instead of the message explaining that supplier management needs the Operations plan.
- Stock movements show up again. The same underlying fault was hiding your movement history in three places: the Movements tab under Stock, the Adjustment History on each item, and the "Recent Movements" card on your Dashboard. All three were reading the server's reply the wrong way and quietly showing nothing, however much history you had.
- Importing stock reports the right numbers. The "imported / failed" count after a CSV import was read from the wrong place, so the summary came back blank.
- Finishing or cancelling a production batch returns the real batch. Both were reading the reply the wrong way, and the count shown after deleting several products or recipes at once had the same fault.
- Hovering a tab no longer empties a list. Preloading suppliers, products or recipes (which happens when you hover a tab, to make it open instantly) stored them in the wrong shape, so the list it was meant to speed up could come up empty until the cached copy expired — up to ten minutes for the supplier picker. The clearest symptom: hover the Products tab, then open the record-waste dialog, and its item picker had nothing in it.
- The Owner dashboard loads again. It was asking the server for the wrong address, so the whole page failed with nothing to show.
- Failed image uploads say why. Rejecting a photo — too large, wrong type — showed a generic "Upload failed" instead of the actual reason, both on the shared image picker and when saving a store logo.
- Exports on a locked plan explain themselves. Exporting suppliers, products or recipes without the plan for it showed a generic failure instead of telling you the export needs an upgrade.
2.76.0 — Fitur
- A custom price now says which plan it buys. When we agree a special price with you, it's quoted against a specific plan — POS, Operations or Enterprise — instead of just being a number attached to whatever you were on.
- Paying an agreed price is one button. The quote appears at the top of your Billing page with the plan and the amount, and a "Pay with Stripe" button that charges exactly that price on the cycle we agreed. Your plan switches on as soon as Stripe confirms the payment.
- While a quote is waiting to be paid, the plan features pause. Your old subscription is closed the moment the new price is set, so you're not paying the previous rate for it, and any locked page sends you to Billing to settle the new one. Billing, your storefront and your account settings stay open the whole time, and nothing in your data is touched.
2.75.0 — Fitur
- The supplier list on a new order is no longer empty. Creating an order from the Stock page's "Create Order" button showed a supplier picker with nothing in it, so the order could never be submitted. It now lists every supplier you have on file — the ones who already carry that material first, with their agreed price and your preferred one at the top, and the rest below in case you're ordering from someone else this once. Their phone and email appear under the picker so you can call before you confirm.
- Orders can now carry a requested expiration date (DLC). Ask the supplier for a use-by date, and it prints on the order and follows the goods: when you mark the delivery received, the date lands on the raw material for you, so it's typed once instead of twice. Leave it empty for flour, sugar and anything else that doesn't need one. It's on both the single-item order and the bulk order.
- Print an order quote. Every order gets a "Print order quote" button — right after you create it, and again from the Orders to Place list. The document carries your store's name, address, phone and email, the supplier's contacts, every line with quantity, DLC, unit price and total, the order status, and a signature strip for both sides. Print it or save it as PDF.
- Creating an order works again. The delivery date the form sends was being rejected before it ever reached the database, so "Create Order" failed every time. Fixed for both the single and bulk order forms.
- The printed quote's totals are labelled properly. Subtotal, tax, shipping and total were printing as raw internal names.
2.74.0 — Perbaikan
- A batch is now baked whole. If your recipe makes 5 baguettes and you ask the kitchen for 3, it takes the ingredients for one whole batch and puts 5 on the shelf. Before, it took three fifths of the flour — which is not something you can do with a dough. Nothing changes for dishes cooked to order: those still use exactly what the portions need.
- Recipes now say how they're made. Every recipe is either cooked when it's ordered — which is all of yours today, and unchanged — or made ahead in batches. Only batch recipes show up on the Production page, so a dish that's cooked fresh can't be started as a batch by mistake and have its ingredients taken twice.
- Duplicating a recipe keeps its settings. A copy used to come back as a Kitchen recipe cooked to order, whatever the original was.
2.73.0 — Perbaikan
- Your recipes now work the way you actually cook. Everything with a recipe is treated as made fresh when it's ordered, so the raw ingredients come out of stock on every sale — rice, chicken, eggs, spices. Items you buy in ready to sell keep being counted on a shelf, and you manage those from the Stock page for adjustments and reordering.
- Each product now says how it's made, right on the card. "Cooked to order", "Counted on a shelf" or "Not tracked" — so you can see at a glance which is which. Change it in Edit, under "How do you make this?", if you prepare something ahead in batches.
- The Production screen now shows how it works. A small bakery diagram walks through it: flour and butter go in when you bake, twenty loaves sit in stock, and each sale takes one off the shelf — so it's clear the ingredients come out once, not twice.
- The changelog reads properly now. Bold text, commands and links were showing as raw symbols, and long lines ran off the side of the screen on a phone. Fixed on both this page and the public one.
2.72.0 — Infra
- Retired two leftover fields from the old stock model. Nothing you can see changes — the "track stock" switch on custom items works exactly as before. Under the hood it now reads from the single field that decides how each product is made, instead of a second copy that could drift out of step with it.
- Added a self-check you can run against a real database. It places orders, watches the stock come out, checks the finance report picks it up, logs a prep run and cancels an order — then confirms every number lines up. Run
pnpm tsx --env-file=.env scripts/verify-stock-flow.ts.
2.71.0 — Fitur
- Today's prep, with one tap to log it. The Production tab now opens with a list of what to make: everything you count that has dropped below the level you set, with the number already filled in. Tap "Made it" and the ingredients come out and the stock goes up in one go — no more four-field form, which is why prep went unlogged in the first place.
- We won't take the same ingredients twice. If something sold before you got round to making it, its ingredients already came out at the till. Log that batch afterwards and we only take what's left to take, and only add to your shelf what's actually still on it. The prep list tells you when this applies instead of quietly doing different sums.
- End-of-day count sheet. Enter what's really on the shelf and we record the difference. This is what finally puts the cost of food you made but binned into your figures — until now it simply vanished from the books.
- Offline orders can no longer be sent twice. If the connection dropped at the wrong moment while an offline order was syncing, you could end up with the same order — and the same stock coming out — recorded twice. Each queued order now carries its own tag and the second attempt is recognised instead of duplicated.
- Recipe cost changes now reach the product. When an ingredient price changes, the products made from that recipe update their cost too, so your profit figures stay honest. Tick "Customize manually" on any product to keep a price you typed yourself.
- The offline sync message now speaks your language. It was hardcoded in Indonesian.
2.70.1 — Perbaikan
- Zoom now fills the screen instead of shrinking away from it. Zooming out left the page stranded in a box that stopped short of the window — about three quarters of the width at 70% — with dead space down the right and along the bottom. Every part of the layout that was sized against the screen now takes the zoom into account, so the page occupies the whole window at every level and zooming out genuinely fits more on screen. Nothing changes at 100%.
- Dialogs stay reachable when zoomed in. At 125% and 150% pop-up windows were being drawn taller than the screen, pushing their buttons off the bottom with no way to scroll to them. They now size to what you can actually see.
- Menus now open where you clicked, at any zoom. The account menu and every other drop-down was drifting away from the button that opened it once you zoomed — far enough at 125% and 150% to slide off the right of the screen and be cut in half. They now sit against their button at every level, and scale with the rest of the page.
- The menu bar and the page below it stay lined up at every zoom level.
2.70.0 — Perbaikan
- Ingredients now actually come out of stock when you sell something. They were not. A dish could be sold all day without a gram of flour leaving your inventory, because of a broken link between a product and its recipe. Every product now says plainly how it is made — you count how many you have, you make it fresh when it's ordered, or you don't track it — and stock comes out to match.
- Your cost and profit figures will change, and the new ones are the real ones. Because ingredients were never leaving stock, cost of goods was reading close to zero for most shops and gross profit was reading far too high. Now that ingredients are counted properly, expect your margin to drop to its true level. Past months are left exactly as they were reported, so only new sales reflect the corrected figures.
- Selling more than you prepared no longer loses the ingredients. If you sell ten croissants when you counted six, the last four are treated as made to order and their ingredients come out then. If you log that batch afterwards, we know it was already accounted for and will not take the ingredients twice.
- Products can now show as "oversold". Selling past zero used to quietly show as healthy stock. It now shows the real, below-zero number so you can count what is actually there and correct it.
- Extras and add-ons now count towards your costs. The ingredients used by a modifier were already coming out of stock, but their cost was missing from your profit figures.
- Delivery-app orders are marked as uncosted rather than counted as pure profit. Orders coming in from delivery apps are not linked to your products, so their cost cannot be known. The finance page now says how many there were instead of treating them as 100% margin.
2.69.2 — UX
- The mobile menu is now on the right. The menu button has moved to the right end of the top bar and the menu itself slides in from the right, so it falls under your thumb instead of asking you to stretch across the screen. Swiping in from the right edge opens it, and swiping back to the right closes it.
- Opening the mobile menu no longer built it twice. A second, invisible copy of the menu was opening at the same time as the real one, which is why the background behind it looked darker than it should. Fixed.
2.69.1 — UX
- One button in the toolbar instead of two. Install and Offline & Sync were both showing at once, which read as the same button twice. They are now two states of one slot: you get Install while there is something to install, and it becomes Offline & Sync once the app is installed. Neither one flashes on screen before the app has worked out which it should be.
2.69.0 — Perbaikan
- The installed app now opens when you launch it with no connection. Tapping the Epidom icon with the wifi down used to land on the "you're offline" card every single time, because the app asked our server which store to open before it could show you anything. It now works that out on the device and takes you straight back to the screen you were last on.
- Offline & Sync no longer vanishes once you install the app. Installing Epidom used to hide the whole panel — on the one device where working without a connection matters most, there was no way to check whether anything was actually saved. It stays now, with the Offline Mode switch visible and locked on.
- You can see exactly what will work offline. Offline & Sync now lists every page that opens without a connection (POS Cashier, Order Queue, Kitchen & Bar, Data, Staff, Schedule, Dashboard) and every kind of data saved on the device — menu and prices, live orders, cashier list, materials and stock, staff and schedules — each marked Ready or Not saved, with how many records and when they were last refreshed. No more guessing.
- Pages are now saved for offline, not just data. Epidom used to save your figures but not the screens that display them, so a page you hadn't opened since launching the app simply couldn't load without a connection. Turning on Offline Mode, and every "Sync now", now saves the screens themselves too — there's also a "Save pages for offline" button to do it on demand.
- Testing offline on a development server now says so. Offline storage is deliberately switched off on localhost so a development server can never hand you stale files. The panel now explains that outright instead of leaving you to conclude offline mode is broken.
2.68.0 — UX
- The Install button works again. The install window was being hidden by the app itself, so pressing Install did nothing at all on any device. Fixed.
- Installing the app is now one tap. The Install button used to open an Epidom window that mostly existed to hold a second Install button. It now opens the real installer straight away — a one-tap install on Android and desktop Chrome, and clear step-by-step instructions with pictures on iPhone and iPad, where Apple gives no other way in. It also opens reliably now even if you dismissed it once before.
- The install window and the home-screen entry are in English. The app name, description and the two shortcuts ("Cashier", "Order Queue") were Indonesian-only.
- Offline & Sync moved to its own button, next to Install. The Offline Mode switch, the last-synced time, "Sync now" and the storage readout all still live in one place — they're just no longer buried inside the install window. "Sync now" is also a proper size to hit on a tablet; it was previously a 24-pixel target.
- Adding Epidom to an iPad or iPhone home screen now looks like an app. It picks up the real Epidom icon instead of a blurry snapshot of the page, opens without Safari's address bar, and shows a dark status bar that matches the app instead of a white strip.
- Marketing and public pages were sending browsers a conflicting, outdated set of app settings — a different theme colour and a light status bar — which overrode the correct ones. Every page now sends one consistent set.
- Long-pressing the installed icon shows the Epidom mark next to "Kasir" and "Antrian Pesanan" instead of a blank placeholder.
- The home-screen icon is no longer a half-megabyte download. The manifest was offering a 580 KB icon file that some devices preferred over the 6 KB one meant for the job.
- Order and stock notifications now use the app icon rather than a full-colour logo that was ten times the size and didn't fit the notification badge shape.
- Offline & Sync is fully translated into French. Eleven labels in that panel had no French text and were printing their internal names on screen.
2.67.1 — Perbaikan
- Stock figures now update everywhere the moment stock moves. Running a recipe in Production correctly took the ingredients out of inventory, but Management and Data kept showing the old figure for up to half a minute, which read as "the deduction never happened". Recording waste and receiving a supplier delivery had the same lag. Every page showing that material now updates as soon as the change lands, on every open tab and device.
- Low-stock warnings now come from production and waste too, not only sales. A recipe run or a spoilage entry could take an ingredient below its minimum in complete silence — the alert only ever fired when something was sold. All three now raise the same warning and send the same notification.
- The checkout window now tells you when ingredients come out of stock. With the Kitchen & Bar display on, ringing up an order doesn't move stock yet — it comes out when the order is marked delivered. With it off, stock moves as soon as payment is confirmed. The window now says which applies to your store, instead of leaving it to be guessed.
- Menu items that aren't linked to a product are now marked. Selling one moves no stock at all, which is a perfectly valid setup but was invisible. They now carry a "No stock link" tag explaining it, alongside the existing "From Product" tag.
- French menu labels are readable again. Two labels in the menu editor had no French translation and printed their internal name (
storefront.menu.fromProductBadge) on screen instead of a word.
2.67.0 — Fitur
- Filter your order history to one cashier shift, from open to close. Add the new Shift filter in Order History and pick a till session — the table narrows to everything sold between that session's open and close, and the payment-method totals recompute with it. It's a time window, not just orders rung on that till, so online and delivery orders taken while the drawer was open are counted too. A session that's still open runs up to right now and is labelled as such, so a mid-shift look doesn't read as a final total.
- Print a proper daily report, on receipt paper or on screen. The Export button in Order History now offers a daily report alongside the existing PDF order list. It lays out the same blocks a till report is expected to have: sales, discounts, service charge and tax down to the total; invoice count and average per invoice; a cancellation summary; splits by sale type, by guest, and by payment method; and a per-product breakdown grouped under each menu category with its own subtotal. Scope it to a shift and it also reconciles the cash drawer — opening float, expected, counted, and the difference. Print it straight to a Bluetooth thermal printer at 58mm or 80mm, or open it as a page and print from any printer, including on iPad where Bluetooth printing isn't available.
- Every daily report has its own link. The report opens at a stable address you can bookmark, re-open, and re-print later without setting the filters up again — the same way a customer receipt already works.
- Count guests at checkout, and see what that's worth. Dine-in checkout now has a guest-count stepper, carried through if you park the order and finalise it later. The daily report turns it into total guests, average guests per day and average sales per guest. Orders taken before this shipped, and takeaway orders, simply have no guest count — they're left out of that block rather than counted as one guest each, and if nobody records guests the block doesn't print at all.
- Offer the report the moment a shift closes. The Close Shift dialog has a "print report on close" switch — turn it on and the shift's report opens as soon as you close the drawer. It's remembered for next time. The report stays one click away either way, including mid-shift, so you can check the numbers before counting the cash.
- Slice the whole Finance page by a till session. The same shift picker sits on Finance. Choose a session and every figure on the page — the KPI cards, the P&L, and each tab — narrows to that window, so it reconciles exactly against the printed daily report.
2.66.0 — Fitur
- Your dashboard now shows the shop floor, and only the cards you can actually use. A new Live Operations card sits at the top: who is clocked in right now and for how long, which till sessions are open with their float and order count, and anyone who was on today's roster but hasn't clocked in yet — refreshed on its own, so you can leave the dashboard open on the counter screen. Times are counted on your shop's own clock, not the browser's, so a night shift lands on the right day.
- The Production History chart hides itself when you don't use production. Recipe-to-batch production is off by default, and the chart was still drawing a flat zero line for everyone who never turned it on. It now appears only once production is enabled, and the row it sits in re-flows so the remaining cards fill the space instead of leaving a gap.
- Cards you can't open no longer take up the dashboard. Stock levels, low-stock alerts, suppliers, stock movements and the new operations card belong to the Operations plan; below that tier the dashboard used to show them all locked or empty. They're now replaced by a single upgrade card, and the underlying data is no longer loaded for accounts that can't see it — so the page opens faster too.
- Stock and alert tables read properly on a phone. The material name column was pinned to a fifth of the row, which left roughly sixty pixels for the name on a 375px screen. Name now takes the space it needs and the number columns hold a fixed width.
- Live Operations is visible to the owner and manager accounts only — the same rule the attendance log already follows, so a cashier or kitchen login doesn't see the whole floor's clock records.
2.65.0 — Fitur
- Zoom the app in and out from your account menu. Open the menu under your name and you'll find a zoom stepper — 70%, 80%, 90%, all the way up to 150%. Zoom out to fit more of the order queue on a small counter screen, or zoom in when the kitchen display is read from across the room. It replaces reaching for the browser's own zoom, which isn't available at all on a tablet (the app locks pinch-zoom so the layout can't be dragged out of shape) or in the installed app, where there's no browser toolbar to open. The setting belongs to the device, not the account, so a shared cashier tablet keeps its zoom for whoever signs in next, and it's remembered the next time the app opens.
2.64.3 — Perbaikan
- Update notes are listed newest-first again. On a day that shipped several releases they came out in a jumbled order, so the latest update could sit halfway down the list instead of at the top. Release dates carry no time of day, which left same-day releases with nothing to sort them by — the version number now settles it. Affects both the What's new page in your dashboard and the public changelog.
2.64.2 — Perbaikan
- Orders made up entirely of custom items can be completed again. A custom product line such as "Men's Haircut" has no kitchen prep step, so it never appears on the kitchen screen — and on an order containing nothing else, Mark All Complete quietly did nothing and the order sat at In Production with no way to move it forward. It now advances the order directly. Orders that mix custom and regular items work as they did before.
- A brief database hiccup no longer looks like being signed out. If the connection to the database dropped for a moment — routine on serverless Postgres, which closes idle connections and cold-starts — the app could not look up your session and reported "Unauthorized", which mid-checkout reads as a surprise logout rather than something to retry. The lookup now retries for a moment on its own, and if it still cannot be resolved you get a "please try again" message instead of a false logout. Creating, holding, finalizing and editing an order all go through this.
- Order status badges are filled instead of outlined. Confirmed and Ready shared a single style and In Production was a faint outline, so the queue's most important signal was easy to miss at a glance. Each status now carries its own solid color, matching the status tiles above the queue and the color stripe down the side of each card: Confirmed blue, In Production orange, Ready green, Held grey, Cancelled red, Delivered white. They read identically in light and dark mode, across the grid, compact and board views.
2.64.1 — UX
- The Mark as Paid window now scrolls instead of running off the screen. With every payment method your store accepts listed at once, the top of the window and its Confirm button could both sit outside the screen on a laptop or tablet, with no way to reach them.
- Only one window opens at a time now. Choosing Mark as Paid, Refund or Cancel from an order's details used to open a second window stacked on top of the first — double-dimmed background, the order details still showing around the edges. Each one now replaces the details, and closing it takes you straight back to the order where you left off.
- Order card buttons no longer overlap on narrow cards. When an unpaid order showed both a stage action and Mark as Paid, the two labels printed over each other. Mark as Paid now shares the top row with the cancel ✕ and the main action (Complete, Start Process…) gets a full-width row below it, with longer labels shortened rather than spilling out. All three are also bigger to tap on a tablet.
- Deleting a material, recipe or supplier from its details window asks once, not twice. The details window raised its own confirmation, which then raised the page's confirmation for the same delete.
- Editing or deleting a material from its details window no longer leaves the details stacked behind it, matching how products, recipes and suppliers already behaved.
- Deleting a category from Manage Categories now replaces the category list rather than stacking on top of it.
2.64.0 — Perbaikan
- Fixed the intermittent 404s and "page needs reloading" errors when moving between dashboard pages. The main cause was the offline cache holding on to page data from a previous release: after a new version shipped, it kept serving the old version's data, which pointed at files that no longer existed. The cache now leaves that data alone entirely and only stores genuinely static assets.
- The installed app's shortcuts work again. Long-pressing the app icon and choosing Cashier or Order Queue used to open a "page not found" screen, because those shortcuts pointed at an address that only exists inside a specific store. They now resolve to the right store automatically, and opening the installed app goes straight to your dashboard instead of the marketing homepage.
- Opening the app no longer lands on a dead page after a store is removed or a page is renamed. The "continue where you left off" shortcut now checks that the saved page still exists before jumping to it, and a store that is no longer yours sends you to your store list instead of a broken dashboard.
- `/store/{id}` opens your preferred section instead of a "page not found" screen — a trimmed or hand-shared store link now works.
- Dashboard pages show a loading skeleton while they open, and a recoverable error screen with a Try again button if something fails, instead of a blank screen you had to reload by hand.
- Losing connection mid-shift now shows a proper offline screen that reconnects on its own, rather than the browser's error page. The app also no longer tries to reload itself while offline, which previously left the screen stuck.
- Fixed an upgrade dead-end that could trap the browser in an endless redirect. If your plan lapsed while your last-opened page needed a higher tier, opening the app bounced you to the pricing page, which bounced you back to that page, forever — so the one page that could restore your plan was the one page you could never reach.
- Fixed an endless loop for accounts with no store left. Deleting your last store put the app in a permanent bounce between the store list and the setup screen; you now land on setup, where a new store can actually be created.
- Losing wifi mid-shift keeps the screen you were on. The offline fallback now restores the actual dashboard or cashier page rather than a generic offline card, since the app's code and its local order data are both still on the device. Signing out clears it, so a shared tablet never shows one account's screen to the next.
- Connectivity is now detected by actually reaching the server roughly once a second while offline, rather than trusting the device's network indicator — which reports "connected" behind café/hotel wifi sign-in pages and when a weak link silently recovers. Queued orders and cached data now sync the moment the connection genuinely returns, and the "last synced" time stays accurate.
2.63.0 — Perbaikan
- Custom Products now render as their own section in POS Cashier and on the storefront, in a visually distinct block headed by the store's name for that product line, instead of appearing as just another menu category among the food and drink headings.
- Fixed the "Track stock" toggle not saving: the product update endpoint dropped the field, so switching stock tracking on or off in the Custom Products edit dialog silently did nothing.
- Repaired menu categories that had drifted out of sync: products edited before the category-sync fix kept their old category on the cashier and storefront. A one-off repair (
pnpm tsx scripts/repair-menu-item-categories.ts, with--dry-runto preview) re-points every product-linked menu item at its product's current category.
2.62.0 — Fitur
- Products can now be marked as not stock-tracked — a service (a haircut), or any always-available item. Turn "Track stock" off and nothing is ever deducted and it can never run out; turn it on and set a quantity, and it's deducted per order like any other product. Available on Custom Products items, where it defaults to off since services are the common case there; every existing product keeps tracking stock exactly as before.
- Fixed custom items showing as "SOLD OUT" on the storefront: they were being created as unavailable back when custom items were unconditionally hidden from the public menu. Storefront visibility is now controlled by its own setting, so that flag no longer applies — existing items are corrected automatically.
- Fixed a product's category change not reaching the POS Cashier or storefront: renaming or reassigning a product's category updated the product itself but left the linked menu item filed under its old heading, on both screens, indefinitely. The category now syncs across (creating the menu category if needed) and pushes live to the cashier like name/price changes already did. Applies to all products, not just custom ones.
2.61.0 — Fitur
- Order Queue can now mark an in-production order complete on its own: an order that's had "Start Processing" clicked previously had no further manual action on the Order Queue at all — the only way to move it forward was switching to Kitchen & Bar and tapping every item. It now gets the same "Mark All Complete" action Kitchen & Bar has, right on the card, for stores that don't want a cashier bouncing between screens for a simple order.
2.60.0 — Perbaikan
- Fixed two Custom Products dataflow gaps found in operator testing: turning off the feature's master toggle on the Data page now correctly pulls those items off the public storefront too — previously the storefront-visibility toggle in Storefront Settings was checked on its own, so a store that had opted into storefront publishing kept showing custom items there even after the master feature was switched off. Also, the "name this product line" field on the Data page's disabled/explainer view now pre-fills with whatever was last saved instead of appearing blank — turning the feature off never actually cleared the name, but the input didn't show it, making it look lost.
2.59.0 — Perbaikan
- Fixed notes (and modifiers) disappearing when a held order is resumed for the first time: the very first time a cart was held (before it had ever been resumed once already), the order-item's note and selected modifiers were never actually written to the database — only quantity/price/name were. Resuming that order later showed the item with no note at all. A held order that had already been resumed and held again worked correctly, since that code path (a separate branch in the same route) already saved both fields — this just brings the first-hold branch in line with it.
2.58.0 — Perbaikan
- Order status now updates instantly on Kitchen & Bar and the Order Queue: marking an item (or a whole station) "Ready" is pushed live everywhere else in the app, but the KDS item-status route was the one gap — it never fired that push, so both screens fell back to periodic polling (up to 10s, or 15s where live push isn't configured) to notice. Also fixed a same-device gap where the KDS card kept showing "Waiting other department" for a few seconds after that station had already finished, because the local optimistic update only flipped the item, not the order.
- Order Queue status badges (status/source/payment/unpaid) now stretch to the full width of their column instead of shrinking to fit their own text, so the badge stack reads as one clean aligned block instead of a jagged one. The status filter tiles above the queue (All/Confirmed/In Production/Ready/Held) had the same problem for a different reason — the grid was hardcoded to 6 columns for 5 tiles, leaving a dead gap the width of a whole tile; now a 5-column grid.
- Checkout confirmation is significantly faster: creating or finalizing an order now returns as soon as the order itself is committed. Stock deduction, shortfall-batch drafting, and the background order-placed notification (an outbound network call) used to all run before the response was sent — they're now deferred to run immediately after via Next's
after(), off the critical path but still guaranteed to complete. The two independent lookups needed to build the order (menu item validation, finance settings) now also run concurrently instead of one after another.
2.57.0 — Fitur
- Custom Products, round two — separated the two on/off switches and made it a real POS department: the master enable toggle (which drives POS Cashier inclusion) moved back to the Data page's Custom Products tab, where it's paired with the Operations-plan pricing wall; Storefront Settings now has its own, independent toggle purely for whether those items also publish to the public storefront link. Custom-line items now show up as a genuine third department filter — labeled with the store's own name for it — right beside Kitchen/Bar in POS Cashier, the Order Queue, and Finance Reports' revenue-by-department breakdown, instead of living in a separate section or an "Unassigned" bucket.
- POS cart: every line item can now carry a note, not just ones with modifier options — the pencil icon on a cart line no longer waits for a menu item to have option groups before it appears.
2.55.0 — Perbaikan
- Custom Products redesign, based on live operator testing of 2.54.0: fixed a real currency bug where editing a custom item showed the raw IDR-stored value instead of converting to the store's display currency (e.g. a €10 cost showed as "208333,33"). Removed the per-item "Show on Menu"/"Show on Cashier" switches entirely — custom items are now never shown on the public storefront and always auto-added to POS Cashier for as long as the feature is on, no manual toggling needed. The on/off toggle and naming moved out of the Data page tab into Storefront Settings' "Storefront Features" card (alongside Online Orders/Table Reservations), now gated behind an Operations-plan upgrade wall. POS Cashier gained a dedicated section for these items, structurally separate (below) the regular Kitchen/Bar item grid rather than mixed into it. The Data page tab itself is now leaner — just the product list, or a link to Storefront Settings when the feature is off — fixing an unrelated layout gap reported alongside the above.
2.54.0 — Fitur
- Optional "Custom Products" second product line: a store can now sell something entirely unrelated to its Kitchen/Bar menu — the example that prompted this, a café that also runs a small hair-salon counter — without Epidom building anything salon-specific. Off by default; the store owner names it (e.g. "Hair Salon", "Spa Services") when turning it on from a new tab on the Data page. Items in this line skip the Kitchen/Bar KDS workflow and material stock/recipe deduction entirely (there's no such thing as inventory for a haircut), and each item has two independent visibility switches — "Show on Menu" (the customer-facing Storefront) and "Show on Cashier" (the POS Cashier sell grid) — instead of the one shared toggle regular menu items use. Revenue still flows into the same integrated Finance Reports as everything else.
2.53.0 — Infra
- Local dev and PR preview deployments no longer share the production database. Vercel's
DATABASE_URL/DIRECT_URLwere scoped to bothproductionandpreview— every preview build's own migration step, and any local work, ran directly against live data. Added a Neondevelopmentbranch (instant copy-on-write, reset from production nightly via a new GitHub Actions workflow) and re-scoped Vercel so onlyproductiondeploys touch the production branch;preview/local now point atdevelopment. No user-facing behavior change — this is entirely about protecting production from development activity.
2.52.0 — Fitur
- Admin can now set a custom price for an individual account's subscription: from the Master Admin Panel's "Manage" menu, a new "Set Custom Price" action lets the operator override what a user is billed. For a real Stripe-paying customer, it actually changes what Stripe charges them starting the next billing cycle (a live price override); for an admin-granted/comped account, it's stored as a reference figure — shown on the admin panel and that user's own Billing page — for the operator's own manual invoicing arrangement. Mainly for negotiated Enterprise deals, previously handled entirely outside the app.
2.51.0 — Fitur
- Marketing site now remembers your last chosen language: picking a language from the switcher on epidom's marketing pages (
/,/pricing, etc.) now sticks on every future visit to an unprefixed URL — a bookmark, the logo click, typing the bare domain — instead of only lasting for that page load. Previously the site only auto-guessed a language once from your browser settings on first visit; an explicit pick now always wins over that guess, on a real cookie so it's honored before the page even renders.
2.50.0 — Perbaikan
- Fixed a redirect loop in "Resume where I left off": clicking the EPIDOM logo (the way back to the marketing homepage from inside the app) now records
/itself as the last-visited page before navigating, so the marketing site loads normally instead of instantly bouncing back into the app. Previously the logo linked straight to/without updating the tracked URL, so the stale in-app URL was still "last visited" and the resume redirect fired immediately — signed-in users had no way to actually see the marketing site.
2.49.0 — Fitur
- "Resume where I left off": signed-in visitors who land on the marketing homepage are now sent straight back to the last app page they had open on that device — including filters/tabs, since most of those already live in the URL — instead of seeing marketing content. New Profile setting (replaces "Default landing page") lets you turn this off. Device-local (localStorage), cleared on logout so the next person on a shared device isn't bounced into someone else's page.
- "Back to Stores" added to the account dropdown, alongside Switch Account/Account Access — there was no way back to the store picker from inside a store without using the browser back button.
2.48.0 — Fitur
- Offline Mode is now mandatory (no opt-out) once the app is installed as a PWA — previously it auto-enabled once but respected an explicit prior "off," which meant an installed app could still end up without offline support.
disableOfflineModeitself now refuses while standalone, not just the UI. - PWA install flow rebuilt on `@khmyznikov/pwa-install`, replacing the hand-rolled iOS-detection code — its own richer, per-platform install guide (opened from a dedicated button), and its
isUnderStandaloneModeis now the source of truth for "already installed" in this component. The install button (and the whole Offline & Sync dialog) is hidden entirely once installed — nothing left to configure once Offline Mode can't be toggled anyway. The app-preview screenshot (device-appropriate — phone vs. desktop) is still shown up front in our own dialog, same as before.
2.47.0 — Fitur
- NotificationBell: upload your own custom sound instead of only the built-in Chime/Ping — pick "Custom" to select an MP3/WAV/OGG/M4A clip (validated: audio only, 3 seconds max, 1MB max), stored on-device only (no server upload). A trash icon next to "Custom" removes it and falls back to Chime.
- Push toggle and sound options merged into one row in the NotificationBell popover — a single switch for enabling/disabling push, plus a "customize" dropdown covering why push can't be toggled right now (blocked/iOS-install-needed) and the full sound picker, instead of two separately-bordered always-expanded sections.
- Fixed a test regression from the MagicBell change (2.43.0): the low-stock alert test suite's Prisma mock didn't define
user.findUnique, whichfireLowStockAlertnow calls to resolve the MagicBell recipient — crashed 3 tests. Production was never affected (the real Prisma client always has.user); this was a test-mock gap only, caught during this session's pre-push verification.
2.46.0 — Perbaikan
- Billing page now shows "Lifetime access" instead of a literal far-future date (e.g. "May 4th, 2126") for accounts granted a lifetime period from the admin panel — mirrors the admin user table's existing lifetime detection, now shared via one
isLifetimePeriod()helper instead of two separately-maintained thresholds.
2.45.0 — Perbaikan
- `/admin/capacity`'s Vercel card no longer shows a raw "404" as an error — a Hobby-plan team with zero billable usage gets a real
costs_not_found404 from Vercel's billing API; that's honest zero-usage data, not a broken integration, so it now renders as "$0.00, no billable usage this period" instead of an error string. - Backup card now tells configured-but-never-run apart from not-configured — previously always said "Set R2_ACCOUNT_ID..." even when R2 was fully configured and just hadn't backed up yet.
- First real database backup run — 50 tables, ~1,800 rows, ~166KB compressed, uploaded to Cloudflare R2.
2.44.0 — Perbaikan
- Fixed `/admin/capacity`'s "Failed to fetch capacity" error — the orders-per-day query referenced the
OrderPrisma model name in raw SQL instead of its actual mapped table name (orders), which every other query on the page correctly used. - NotificationBell gets a custom in-app sound — a synthesized chime/ping (no audio file to host) plays when a genuinely new order/reservation/onboarding item arrives while the tab is open, with a 3-way Chime/Ping/Off picker saved per device. Doesn't apply to OS-level push or MagicBell's own channels — neither the Web Notifications API nor MagicBell support a custom delivery sound, a platform limitation this app can't work around.
2.43.0 — Infra
- Merchant alerts (new order, low/critical stock) now route through MagicBell instead of separate direct WhatsApp (Fonnte) and browser-push (VAPID) calls — one unified API, with web push/mobile push/email/in-app configurable per category in the MagicBell dashboard (SMS can be turned on later by connecting Twilio there, no code change needed). Recipient is the store's owning account, identified by email. The old push/WhatsApp infrastructure is intentionally left in place (unused by this flow, not deleted) rather than bundling a second, larger cleanup into this change. Nothing else — the in-app notification bell, customer-facing WhatsApp receipts, and transactional email — changed.
2.42.0 — Fitur
- Offline Mode: the "Install app" dialog (topbar/sidebar download icon) now doubles as an Offline & Sync settings surface, and stays visible after install instead of disappearing. A new Offline Mode toggle eagerly downloads menu, live orders/KDS, materials, staff roster, and staff schedules to the device (via IndexedDB) so POS keeps working with no connection; everything else (finance, admin, marketing) stays online-only by design. Auto-enables itself the first time the app is confirmed running installed to the home screen, without waiting for anyone to find the switch — turn it back off any time to opt out for good.
- "Last synced: <date>" status, shown in the Offline & Sync dialog and in the POS offline banner, plus a "Sync now" button that flushes any queued offline orders and refreshes the offline data mirror together. Reconnecting automatically triggers the same refresh.
- Offline data now survives a reload/relaunch (previously it lived only in memory and was gone the moment you left the POS screen or closed the tab).
2.41.0 — Infra
- New `/admin/capacity` dashboard: database size and per-table disk usage (auto-discovered, no hardcoded table list), row-growth for the highest-traffic tables, tenant scale (stores/users/orders-per-day), and Vercel Blob storage usage — an early-warning view for approaching a platform limit before it causes an outage.
- Platform usage cards for Vercel and Neon (billing/consumption vs. quota) — optional, degrade to "not configured" until their API credentials are added.
- Independent, off-platform database backup: a nightly job streams every table straight from Postgres into Cloudflare R2 (gzip-compressed), with a 90-day retention and a daily freshness check that alerts if a backup hasn't succeeded in 36+ hours. Restores are a deliberate CLI script (
pnpm restore:backup), never a web action, and are documented end-to-end in the newdocs/BACKUP_RESTORE.mdrunbook including a quarterly restore-drill checklist.
2.40.0 — Fitur
- Storefront → Analytics is now real, dynamic, and date-range driven instead of a static "Coming Soon" mock. Tracks storefront page views, menu views, item views, and WhatsApp-button clicks via a new anonymous, daily-rotating visitor fingerprint (no cookies, no raw IP stored, bot/crawler and chat-app link-preview fetches excluded so sharing the link into WhatsApp doesn't inflate the numbers). The tab now shows real unique visitors with a trend vs. the prior period, menu-view and WhatsApp chat-conversion rates, storefront-attributed orders/revenue (from existing
Order.source = STOREFRONTdata), a visitor trend chart, and top viewed/ordered items.
2.39.0 — UX
- Kitchen & Bar / Order Queue ticket timers no longer show absurd raw minute counts on old tickets (e.g. "51482m 50s"). Past an hour, the live mm:ss counter now switches to a human duration — "8hrs 9mins ago", "7 days ago", "1 month 4 days ago" — instead of continuing to tick in raw minutes.
- Order History table's Date column now reads "<weekday>, <day> <month>" (e.g. "Thursday, 9 July") with the time on a second line, instead of a year-inclusive timestamp that made the column harder to scan. The exact weekday, date, year, and timezone are still there in full — now on the order detail dialog, which shows a precise timestamp with both the timezone offset and full zone name (e.g. "Sunday, Jul 9, 2026, 4:00 PM GMT+7 (Western Indonesia Time)") for the order date, delivered date, and last receipt send time.
2.38.0 — Fitur
- Menu Editor: categories and items can now actually be dragged to reorder. The grip handles were previously decorative; they now use
@dnd-kit(new dependency) to reorder categories among themselves, and items within a category, persisting the newdisplayOrdervia the existing update endpoints. Touch-safe (40px drag-handle hit targets,touch-action: none) for iPad/Android use. - Product-linked menu items: deleting is now framed as "remove from POS menu," not "delete." The item's trash icon and confirm dialog say "Remove from POS menu" and clarify the product data is kept — only its POS listing goes away. Deleting a category that contains product-linked items shows the same clarified wording for its "delete items" option.
- Product-linked menu items no longer show a Category or Modifiers editor in the Menu Editor. Both are owned by the Product (
Product.category,Product.optionGroups) and edited there via the existing "Edit in Products" link — duplicating them in the Menu Editor risked silent drift. Description and photo remain editable in place since those aren't synced from the product.
2.37.0 — Fitur
- Kitchen & Bar display and the Order Queue are now one module. Both pages share a single "Active Queue" setting: turning Kitchen & Bar display off now also empties the Order Queue (new orders skip straight to History as Delivered), and a matching toggle was added to the Order Queue page itself — turning it off does the same and flips Kitchen & Bar display off too. Either page's toggle controls the same store-wide setting, in sync everywhere.
- Closed a gap where a Pay Later order could still sit on Pending in the queue even with the display off — it now also goes straight to Delivered (still unpaid, still followable from Order History's Mark Paid action).
- Hold order is now disabled while the Active Queue is off, since there's no queue left to park it in or resume it from.
2.36.0 — Fitur
- Menu Editor: move an item to a different category (or create one) without deleting and re-adding it. The Edit Item dialog now has a Category field (searchable, creatable) that reassigns the item's
MenuCategoryin place. - POS menu items linked to a Product are now clearly marked and protected from drift. A "From Product" badge + tooltip appears on any menu item backed by a Product; its Edit dialog locks Name/Price/Department (owned by the product and synced one-way) and offers an "Edit in Products" button that deep-links to that product's edit dialog on the Data page instead. Category, description, image, and modifiers stay editable in the Menu Editor.
- Fixed the Data > Products "in POS menu" icon going stale. Adding/removing/editing a menu item (from the Menu Editor, another tab, or another device) now publishes a
menu.changedrealtime event that the Products page subscribes to, instead of relying only on window-focus refetch; the Menu Editor also invalidates the Products page's linked-status cache directly for instant same-tab feedback. - Added a bulk "Remove from Menu" action to the Products page's multi-select toolbar, mirroring the existing bulk "Add to Menu".
- The "not yet in POS menu" icon on a product card no longer looks similar to the green "already in menu" one — it's now a neutral/muted color so the two states are easier to tell apart at a glance.
2.35.0 — UX
- Email and WhatsApp Number now format live and show their own inline error, like Username already did. Add/Edit Staff previously only caught a bad email or phone value on submit (or worse, silently on Add — those two fields had no inline error rendering at all). WhatsApp Number now strips non-digits while typing (keeping a leading
+); Email strips whitespace and lowercases. Both re-validate as you type — Add via awatch+triggereffect (react-hook-form), Edit via a new sharedoptionalEmailSchema(common.schemas.ts, alongside the existingphoneSchema) checked directly against the plainuseStatefields — and show the specific reason inline instead of only a generic toast after Save.
2.34.1 — Perbaikan
- Staff save failures now say which field is wrong. Saving the Add/Edit Staff dialog surfaced a bare "Validation failed" toast on any Zod validation error (e.g. a non-phone value sitting in WhatsApp Number), giving no way to tell what to fix — the actual per-field reason was already being computed server-side (
parsed.error.flatten()) but discarded on the way to the toast, which only ever read the response's generic top-level message. Both dialogs now surface the real field + reason (e.g. "WhatsApp Number: Invalid phone number format").
2.34.0 — Perbaikan
- Staff filter dropdowns (Finance Reports, POS Order History, Order Queue) no longer drop staff the moment they're deactivated. Finance Reports and Order History both queried/filtered
isActive: true, so a staff member's own past orders became impossible to filter by as soon as they were deactivated — with no way to isolate their historical revenue or transactions. All three now keep deactivated staff selectable, labeled "(Inactive)", instead of quietly removing the option along with the person. - Hid "Switch Account" from the account dropdown when there's no one to switch to. Previously always shown to the owner regardless of roster state, it reloaded into an empty picker whenever the store had zero staff or only inactive ones. Now hidden unless at least one active, non-Owner staff account exists (mirrors the existing zero-staff bypass check in the dashboard layout).
2.33.0 — UX
- Custom staff role moved into the Role dropdown, and it now actually shows up. The Add/Edit Staff dialogs previously had an always-visible "Custom role label" text field sitting below the Role select — confusing since it looked mandatory, and its value was silently discarded by the display layer (the staff table's role Badge never read
customRoleLabel, only the base role name). Replaced with a "Custom…" entry inside the Role dropdown itself: selecting it reveals a single inline text input right there, and the staff list now shows that custom title instead of the base role wherever it's set. - Staff username field now auto-formats while typing instead of only rejecting invalid characters on submit — lowercases and strips anything outside
a-z0-9_.live, matching the existing backendusernameSchemaregex, in both the Add and Edit Staff forms.
2.32.0 — Fitur
- Fixed the printed/digital receipt going nearly invisible in dark mode.
ReceiptDocument(the shared component behind the public/r/[orderId]page and the receipt-settings live preview) deliberately renders as fixed black-on-white "paper" regardless of the app's theme — but a global dark-mode CSS rule that remaps.text-black/.text-gray-*to pale cream (for readability against the dark dashboard theme) was catching it too, since it wasn't opted out. Fixed by applying the sameprint-reportmarker class already used by the other "always white" views (print-report-shell.tsx,order-history-print-view.tsx) and extending that opt-out rule to cover the additional gray shades the receipt uses. - Added a printer-menu "Reprint Last Order" action plus a direct link to Order History (
?tab=history) for reprinting anything older — the last completed order'sReceiptDatais now kept in a small persisted store (useLastReceipt) instead of living only inside the checkout dialog's local state and disappearing once it closed. Order History itself gained a genuine "Reprint" action (prints via the paired Bluetooth printer, not just the "View Receipt" web link), backed by a newGET .../pos/orders/[orderId]/receiptendpoint that returns the sameReceiptDatashape used everywhere else. - Fixed the POS cart's Pay button requiring a scroll to reach. Root cause: the shared dashboard
PageShellwraps every page's content in a plain, unbounded-height div inside its own scrollable region — fine for ordinary pages that scroll at the page level, but it meantPosShell'sflex-1(which expects a bounded-height flex parent so its own internal item-grid/cart scroll regions can work) had nothing to size against, so the whole POS page just grew to fit content instead of keeping the cart footer pinned. Gave that wrappermin-h-full— additive for every other page, and the fixPosShellneeded to properly fill the viewport and let only its item grid and cart list scroll internally.
2.31.0 — Fitur
- Redesigned the printed thermal receipt and fixed a real cropping bug.
buildEscPos()'s store name was hard-truncated with.substring(0, 16)regardless of paper width — a store named "TAHOMA CAFE & EATERY" printed as "TAHOMA CAFE & EA", cutting off mid-word. The same truncation hit item names and order notes. Replaced with a properwrapText()word-wrapper everywhere free text is printed, so long content wraps onto extra lines instead of silently disappearing. Also widened the paper feed before the cut command and added an explicit tear-guide line — thin feed was letting consecutive orders visually run into each other on cutter-less printers. - Added a Receipt & WhatsApp settings card (Profile page) with an editable footer message, Facebook handle, social-links visibility, and a live receipt preview — reusing Instagram/TikTok/tagline from the store's Storefront where one exists, so there's no duplicate data entry for a cash-only store.
- Automated customer-facing digital receipts. New public, unauthenticated
/r/[orderId]page (works for both storefront and walk-in POS orders) renders the same receipt data as a shareable, printable page. Once an order's payment is confirmed and a customer phone number is on file, a WhatsApp message with the receipt link now sends automatically (Fonnte) — gated by a per-store on/off toggle. Every send attempt is logged (OrderReceiptSend) and surfaced as a "View Receipt" / "Send via WhatsApp" action with sent/failed status in both POS order history and the storefront's own order-status page, with a manual resend option.
2.30.0 — Perbaikan
- Fixed a currency-conversion bug that mis-priced menu items and revenue figures for every non-IDR store — surfaced as a EUR store's ~€1 product showing as "€20,833.33" in the POS menu. Root cause:
Product/Materialcosts are stored in IDR (the platform's base currency) everywhere, but several code paths copied that raw IDR number directly into places that must hold a literal value in the store owner's own currency, without converting first.
2.29.0 — Fitur
- Finance Reports overhaul: fixed a silent-failure bug, and shipped discount/refund tracking, four new report types, and a multi-outlet P&L rollup. Root cause of "the margin/loss cards are gone": the KPI grid and every report tab rendered
{data && (...)}with no error branch — a failed or slow query didn't show an error, it just silently disappeared. Every tab now shows an explicit loading skeleton or a retryable error state instead.
2.28.0 — Fitur
- Bulk "Add to Menu" on the Products list, and a fix for products that silently never made it into the POS menu. Selecting products in bulk-select mode now shows an "Add to Menu (N)" button alongside bulk delete — it skips products already linked, resolves each distinct category once up front (so a batch sharing a category doesn't race and create duplicates), then links the rest in parallel and reports a single summary toast. Root-caused the underlying sync gap: a new store's first-ever storefront record was created via a find-then-create check that two near-simultaneous product creations could both pass, so the loser's
createthrew a unique-constraint error that was silently swallowed — that product's menu item was just never made, with no visible failure anywhere.getStorefrontByStoreIdnow upserts onstoreIdinstead, so the second caller becomes a no-op update rather than a failed create. The new bulk action doubles as the recovery tool for any product that still falls out of sync for other reasons — select it and re-add. - The Data page (Raw Materials / Recipes / Products / Suppliers) now remembers where you left off, matching the pattern already used on Stock/History. The active tab persists to
localStorageand syncs to a?tab=URL param (deep-linkable, wins over the saved value); each tab's own filter/sort selections (category, department, stock status, sort order, page size) persist the same way, scoped per store. Free-text search and pagination position are deliberately excluded so a reload never lands on stale search text or an out-of-range page.
2.27.0 — Perbaikan
- Fixed a hydration-breaking bug on the Work Schedule page: the staff/shift-block filter popovers and the "Apply Template" staff checklist both nested a
<Checkbox>(which renders as<button role="checkbox">) inside an outer<button>— invalid HTML that broke hydration and left the filter dropdowns non-functional. Both now use arole="button"div with keyboard support instead. - Dates now render in the user's selected language everywhere, not just translated UI text around them. Found the root cause: two competing date-formatting utilities existed (
lib/utils/formatting.ts, locale-aware but silently defaulting to English whenever a caller forgot to pass a locale — which was almost every caller; andlib/utils/format-date.ts, hardcoded toen-GBwith no locale concept at all), plus ~15 files calling.toLocaleDateString()/.toLocaleString()/.toLocaleTimeString()directly with a hardcoded or omitted locale, plus every one of the ~29 calendar popups across the app never passing alocaleto the shared<Calendar>primitive (so month/weekday names always showed in English regardless of app language).
2.26.1 — Infra
- Two dashboard pages (Finance Reports, Dashboard Analytics) were eagerly bundling the `xlsx` library just for an "Export as Excel" button. Every other export surface in the app already dynamically
import()sxlsx/jspdfon click rather than on page load (seesrc/lib/utils/export.ts); these two hand-rolled exports had drifted from that pattern. Switched both to a dynamic import, matching the rest of the codebase — trims unnecessary JS off the initial bundle for two pages every merchant hits regularly, no behavior change.
2.26.0 — Fitur
- Management is now a Stock-focused page; Alerts is signal-only. Management's five tabs (Deliveries, Production, a mislabeled "History" that was actually production-batch history, Stock, Movements) are down to two: Stock and History. Stock now covers the full lifecycle — a live per-item stock-reduction bar (Progress component, colored by severity, realtime via the existing
STOCK_CHANGEDchannel), a new editable expiration date per material (single field, not per-batch/lot), waste/condition recording (existingWasteEntryreasons, now front-and-center), search/status/category/expiration filters with click-to-sort, and a "Reorder & Deliveries" sub-tab that absorbs the old Deliveries tab's PLACED→RECEIVED tracking plus Alerts' former "Create Order"/"Bulk Order" dialogs and "Orders to Place" view. History is the renamed Movements tab (the real stock-change ledger) with an added date-range filter and sort. Alerts drops the?view=orderstoggle entirely — it's just the low-stock table and unpaid-orders card now, and its supplier action buttons deep-link into Management's Stock tab (?tab=stock&highlight=<materialId>or&supplierId=<id>), which auto-opens the matching reorder dialog on arrival. - Send a supplier order's PDF detail directly from the Stock tab — by email (Resend, as an attachment) or WhatsApp (Fonnte, as a document link), pick one or both. A local-download alternative uses a new dedicated print page (
/management/print,window.print()→ Save as PDF) following the same pattern already established bypos/orders/print/attendance/print, replacing the older jsPDF-dialog download this superseded. - New optional Production page (
/production), off by default per store. Recipe-to-batch production (the old Production + Production History tabs, moved wholesale into a newproductionfeature) is a distinct manufacturing workflow that not every merchant needs — many cook fresh to order with no fixed recipe. The page shows a guide explaining what it's for before the owner opts in via a toggle; enabling reveals the moved Produce/History sub-tabs. - Filters and the active tab are now remembered per device. Stock, History, and the Management tab bar all persist their last state to
localStorage(reusing/relocating the POS feature's existingusePersistedStatehook tosrc/lib/hooks/, now shared instead of POS-only) — returning to the page lands back where you left it. Deep-link URL params always win over the saved state. - New shared
FilterBarcomponent (search + filter controls + clear button) factored out for Stock/History to share instead of each hand-building its own layout.
2.25.0 — Fitur
- Work Schedule: a custom-range roster grid, filters, and a bulk "Apply Template" action. The Block Name field in Manage Shift Blocks now shows a "e.g. Morning Shift" placeholder. The roster grid gained a staff filter and a shift-block filter (both above the table) — the staff filter narrows which rows show, the block filter dims/excludes non-matching entries within a row while keeping every staff row visible, since the point of "who's on Morning this week" is seeing who's not on it too; day-off entries stay visible under a block filter since they're informative, not noise. The fixed Mon–Sun week is now a picker built on the shared
DateRangeField(the same range calendar used elsewhere in the app), constrained to a 7-day minimum so it can't shrink below a normal roster cycle — the grid's day columns, and its width, now track exactly the chosen range instead of always rendering 7. Prev/next now pages by the current range's length, and "Today" resets to the default Mon–Sun week. Both filters persist across range navigation on purpose (a manager filtering to one block or person is usually paging through several ranges with that same lens). Also added an "Apply Template" dialog next to Manage Shift Blocks: pick one shift block, check staff (default none, "Select all" shortcut) and days of the visible range (default weekdays), and Save creates every resulting roster entry in one go via the existing-but-previously-unusedPOST /staff-schedules/bulkendpoint, chunked at 200 entries per request to stay under its validation cap. Staff/day pairs that already have an entry that day are skipped rather than duplicated, and the result toast reports how many were applied vs. already scheduled. Previously every cell had to be assigned one at a time via the single-cell dialog. Bulk-applied entries land as Draft like any manually-created entry and still need Publish. - Fixed the "standard work minutes per day" field rendering as a 12-hour clock picker (e.g. "08.00 AM"). It's a duration (how many hours count as a normal day, for the overtime split), not a time of day — the AM/PM suffix implied a shift start time that didn't exist. Replaced with a plain hours + minutes counter (e.g. "8 h 0 m"), matching the "Xh Ym" format already used for the Regular/Overtime columns in the same table.
2.24.1 — Perbaikan
- Subscription plan price was hardcoded in Rupiah, ignoring the owner's chosen display currency — and stale against the public /pricing page besides. The in-app Billing page and the Profile page's Subscription card both showed a static
Rp 99.000/Rp 249.000string for the POS/Operations plan regardless of locale or the account'scurrencysetting — a USD-currency owner on the French UI saw "Rp 249.000/mois". Both surfaces now derive the price from the plan's IDR base rate through the same live exchange-rate conversion (useCurrency().formatPrice) already used everywhere else in the dashboard, so it renders correctly in whichever of the ~140 supported currencies the owner has selected. The Profile card previously supported only IDR/USD/EUR/MGA and silently fell back to Rupiah for every other currency; it now works for all of them. The base itself was also wrong:Rp 99.000/Rp 249.000predates a price raise applied to the public/pricingpage (now Rp 229k/Rp 459k, i.e. $14.99/$29.99) — the Billing/Profile surfaces were never updated when that raise shipped. Both now use the same Rp 229.000/Rp 459.000 base as/pricing. Removed the now-dead hardcodedRp .../monthtranslation strings and the finitePLAN_PRICEScurrency lookup table that caused this. Also found and fixed the same staleness on the actual/paymentscheckout summary:id.ts/fr.ts'spricing.plans.starter/pro.price(read bypayment-summary.tsx) still quoted the old Rp 99.000/249.000 and 9,99 €/24,99 € — a checkout screen showing a different price than the pricing page just clicked from. Corrected to match/pricing's Rp 229.000/459.000 and 13,99 €/27,99 €.
2.24.0 — Perbaikan
- Every image upload now guarantees a compressed output instead of trusting the client. Added a server-side compression pass (
compressImageServer, usingsharp) inside/api/upload: every accepted image is resized to at most 1600px on its longest edge and re-encoded until it's at or under a target size (2MB by default; a feature can request a larger target, e.g. the storefront cover banner's 5MB, via amaxSizeMBform field, clamped server-side to a safe range). This is now the authoritative guarantee — client-side compression (compressImage()) remains a bandwidth/UX optimization on top of it, not the only enforcement point. - Fixed a real bug: the shared
<ImageUpload>component (used for storefront logo, cover banner, and menu item photos) rejected the raw selected file against the feature's target size (e.g. "must be under 2MB" for a logo) before attempting compression — so a normal 3–4MB smartphone photo was rejected outright even though compression would have shrunk it comfortably under the limit. Raw files are now checked only against a generous processing-cost ceiling (5MB); the target size is what compression aims for, not a rejection threshold. - Attendance selfie capture had no compression or validation at all (
selfie-capture.tsx+clock-in-out-dialog.tsx) — camera captures and the file-picker fallback now run throughcompressImage()before upload, same as every other image surface. - Centralized the size/dimension rule in
src/lib/constants/image.ts, shared by both the client util and the new server util, so the two layers can't drift apart. Documented the rule inAGENTS.md("Images" under Coding rules) so future image-upload features follow the same pipeline instead of reinventing size limits.
2.23.0 — UX
- Logout consolidated into one menu, for both personas — not scattered across a standalone topbar button and a PIN-based switcher. While acting as a staff persona, the account dropdown previously had no logout option at all — the topbar's real "Logout" button hid itself in that state (it signs the underlying owner account out entirely, which a cashier shouldn't casually trigger), and "Back to Owner Account" is a PIN-gated switch, not a logout. The standalone topbar Logout button is gone; every logout-shaped action now lives in the one account dropdown, for Owner and staff alike:
- Zero-staff stores skip the picker entirely.
StoreAccessGate(and/pos's own gate, kept consistent with it) now also bypasses when a store has no active non-owner staff members — with nobody else to choose from, the "who's using this device?" screen had nothing real to offer and was just an always-the-same-answer click every time a solo-operator store (on OPERATIONS+, past the FREE/POS bypass) was opened from/stores.
2.22.0 — Fitur
- OS-level Web Push notifications — new storefront orders and low/critical stock now alert even when the tab is closed or the screen is off. New optional
VAPID_*env vars enable real browser/OS push (standard Push API, not Firebase) delivered through the existing PWA service worker. A new toggle in theNotificationBellpopover ("Enable"/"Disable") lets any device — owner or staff persona — subscribe; delivery fans out per-store to every subscribed device, since a shared shop device operated via staff PIN carries no independentUser.idto key against. Dead subscriptions (expired/unsubscribed — common on iOS Safari, which rotates these more aggressively than Chrome) are cleaned up automatically on the next failed send (404/410). - Storefront orders now also live-update open dashboards. Fixed an adjacent gap found while wiring this up:
POST /api/public/ordersnever called the existing PusherpublishStoreEvent(ORDER_CREATED)that the POS order route already fires — a storefront order previously only reached an open dashboard tab on its next 30s notification-bell poll. Both routes now match. - Fixed: `/sw.js` and the PWA manifest were unreachable for anyone without a login session.
src/proxy.ts's auth matcher excludedfavicon.icoand image extensions from the login redirect, but notsw.js/manifest.webmanifest— so any first-time visitor to the public storefront (never logged in) got an HTML login-redirect page back instead of the actual service worker file, silently breaking PWA install/offline support and, now, push registration for that entire audience. Found by testing this feature live against a running server, not just via type-check. Logged-in dashboard users were unaffected (they always carry a session cookie). - Graceful degradation, same pattern as the rest of the realtime layer: until an operator sets
VAPID_PUBLIC_KEY/VAPID_PRIVATE_KEY/VAPID_SUBJECT/NEXT_PUBLIC_VAPID_PUBLIC_KEY, the push toggle stays hidden entirely and nothing changes — same "no-op, fall back to polling" contract aspublishStoreEvent(). - Cross-device/browser handling: iOS Safari requires the site to be added to the Home Screen (iOS 16.4+) — the toggle shows an explicit "Add to Home Screen" hint there instead of a dead-end "unsupported" state, since Apple gates the whole Push API behind standalone install. A previously-denied browser permission is surfaced as its own "blocked" state (browsers never re-prompt after denial) rather than a retry button that would silently no-op.
- New
src/lib/push/(VAPID-configured send helper, mirrorssrc/lib/realtime/publish.ts's graceful-degradation pattern),src/hooks/use-push-notifications.ts,PushSubscriptiontable, andpush/notificationclicklisteners inpublic/sw.js.
2.21.0 — Fitur
- Shifts, Schedule, and Attendance merged into one page.
/shifts(till cash sessions) and/attendance(clock-in/out audit log) were separate nav items with no owner-vs-staff view split — a staff member granted either page saw the same full manager table an owner would, and by default couldn't reach either at all. Both are now folded into/schedule, which already split by role: the manager view gains a Log & History section merging clock events and till cash open/close (shown as Cash In/Out) into one chronological, filterable-by-staff/type/date-range timeline; the staff view gains Clock In/Out on every staff member's page, plus Cash In/Out for POS-capable roles (Cashier, Owner/Manager) only, plus a self-service My History./shiftsand/attendancenow redirect to/schedulerather than dead-ending old links. No schema change —ShiftandAttendanceRecordstay two separate, independently-audited tables; only the UI merged, via a newfetchUnifiedLog()/mergeUnifiedLog()(the latter pure and unit-tested) reused by the new Log tab, the staff History section, and the existing attendance PDF export (which now includes Cash In/Out rows too). - Roster: one Publish button, not two. "Publish Week" and "Print / Export PDF" collapse into a single dynamic action — "Publish Week" while the visible week has any draft entry, "Published" (muted, still clickable) once every entry is published, and clicking it in that state opens the PDF export instead of re-publishing. The week grid's day headers also show the weekday name (Mon, Tue, …), not just the date.
- No more double PIN, extended to till cash too. Following up on the clock-in/out PIN dedup shipped earlier this session, opening a till (
POST /stores/[id]/shifts) now also accepts an already-active StaffSession as proof instead of always re-asking for the PIN. - "Clock In / Out" removed from the account dropdown (now lives on the Schedule page) — replaced with "Account Access", a small dialog showing which persona is signed in on this device and exactly which pages it can reach. Built as a dialog rather than a Profile page section:
/profileis hard-gated owner-only, so a staff persona could never have reached a card placed there.
2.20.0 — Fitur
- Live push layer (Pusher Channels) — orders, menu, materials, products, recipes, and stock now update across tabs/devices without waiting for the next poll. Every write that matters (POS order create/status-change/finalize, material/product/recipe create/update/delete, stock adjustments and sale deductions) now fires a lightweight
{action, entityId}event on the store's private channel via a new non-blockingpublishStoreEvent()helper; the affected list/query on any other connected tab or device invalidates and refetches immediately instead of waiting up to 5–10s for its existing poll tick. The old aggressive polling intervals are left in place as the fallback, unchanged — this is additive, not a replacement. - Presence. New
<PresenceAvatars>component +useStorePresence()hook show who else is currently connected to a store (owner or PIN-logged-in staff) via a Pusher presence channel — an initials avatar stack with name tooltips, renders nothing when nobody else is online. - Graceful degradation by design (AGENTS.md §6): none of this requires setup to keep working. Until the operator sets
PUSHER_APP_ID/PUSHER_KEY/PUSHER_SECRET/PUSHER_CLUSTER(see.env.example),publishStoreEvent()silently no-ops and every surface behaves exactly as before — polling only, no live push, no presence. The one exception is the POS order queue, which already had a custom SSE endpoint (orders/stream) for this; it now prefers Pusher when configured and falls back to that existing SSE mechanism unchanged when it isn't, rather than running both at once. - New
src/lib/realtime/(channel/event naming, server + client Pusher singletons, the publish helper) andPOST /api/pusher/auth(channel authorization — accepts either an owner/manager session or an active staff PIN session, matching the existing dual-auth pattern already used by staff-gated POS routes). - Menu-item-level push coverage is partial: a product's price/name/department change publishes
menu.changed(since that syncs to its linkedMenuItem), but direct storefront/menu-editor CRUD onMenuItem(add/remove/reorder categories and items outside of a linked product) doesn't publish yet — POS menu still catches those via its existing 5s poll, just not instantly. Full storefront-item-level coverage is a follow-up. - No presence beyond "who's connected" — no live cursors, no per-field "someone else is editing this record right now" indicator, no conflict resolution for two people editing the same record at once (still last-write-wins). True field-level collaborative editing (the deeper Notion/Figma comparison) is out of scope for this pass.
- Only the six domains above publish events. Tables, reservations, supplier orders, alerts, schedule, finance, and admin dashboard remain poll-only for now — next in line by current poll aggressiveness (10–60s tier).
2.19.0 — Fitur
- Store access gate: pick Owner or Staff before the dashboard, not just before POS. New
StoreAccessGate, generalized from the existing POS staff picker, now wraps the entire(dashboard)route group — not just/pos— so a shared/unlocked device shows a "who's using this device?" checkpoint before any page renders, dashboard-wide. Picking a staff member reuses the same PIN as everywhere else; picking "Continue as Owner" now requires the separate Owner PIN (prompting to set one first if none exists yet) instead of trusting an already-open Better Auth session, since that session is exactly what a shared device leaves sitting unlocked. Skipped entirely on FREE/POS plans (no staff feature to gate). Once chosen, the sameusePosSessionstate POS itself reads means/poswon't ask again on top of this. - Fixed: "Continue as Owner" didn't clear a leftover staff session. Both the new gate and the existing POS gate's "Continue as Owner" only updated client-side state — a StaffSession cookie left over from an earlier persona (or another tab) would still be read by the server-side owner-only-page guard and bounce the request right back out. Both now clear it first (same call the existing "switch back to Owner" flow already made).
- Clock-in/out: no more double PIN. A PIN is meant to prove identity once, at login — if a device is already operating as a specific staff persona (via the gate above or "Switch to Staff Account"), the Clock In/Out dialog now skips straight to picking clock-in/out for them instead of asking to re-select their name and re-enter their PIN. The kiosk-style "pick anyone, enter their PIN" flow is unchanged for devices with no active persona. Enforced server-side too: clock-in/clock-out/absence now accept an active StaffSession as proof (new
isStaffAuthenticated()helper), not just a submitted PIN. - POS: periodic PIN re-verification. Because POS handles cash, a staff PIN there now expires after 4 hours of continuous use and re-prompts for the same PIN (not a full re-pick) — separate from, and on top of, the once-a-day session everywhere else. Doesn't apply to the Owner persona.
- Clock-in/out: history log + photo retake. The dialog now has a read-only history view of a staff member's own recent clock-in/out/absence records (timestamp, selfie thumbnail, location). Nothing in it is editable except the photo, and only within 30 minutes of the original capture — a retake replaces the selfie on that same record (via a new
POST .../[attendanceId]/retake-photo, itself PIN/session-checked and window-checked) rather than creating a new entry, so a blurry first shot doesn't leave a duplicate in the log. Everything else about a record (type, timestamp, notes) stays immutable, same append-only-audit-trail principle as the existing manual-close correction flow. - Selfie capture: hardened for desktop and non-mobile browsers. Distinguishes camera-permission-denied, no-camera-found, and insecure-context (non-HTTPS/non-localhost) failures with a specific message for each instead of one generic one, and adds a "Try again" retry button so granting the permission via the browser's own UI doesn't require closing and reopening the whole dialog. The
facingMode: "user"constraint was already a soft/"ideal" hint (works on desktop webcams that don't report a front-facing camera), and the file-input fallback already degrades correctly to a normal file picker on desktop — this pass is about clearer failure states and recovery, not a new capture pipeline.
2.17.3 — Perbaikan
- Pricing page now matches what's actually shipped. Audited every feature claim on
/pricing(tier cards + the detailed comparison table) against real plan gates in the code. Removed five claims that don't exist in the product (a daily P&L email, allergen/nutrition labels, a wholesale order portal, SSO, and a public API/Zapier/webhooks integration) and fixed a self-contradiction where the Operations card advertised a "multi-outlet dashboard" while the comparison table marked the same thing Enterprise-only — both were partly right: Operations gets unlimited outlets, but the cross-store owner roll-up dashboard (/owner) is genuinely Enterprise-exclusive; copy now says so distinctly instead of using the same ambiguous label for both. Added the three real, previously-unadvertised Operations-tier features shipped this week — shift scheduling & rosters, selfie + geolocation attendance, and waste/loss tracking — plus a reservations mention on the POS tier. Removed four dead, never-rendered i18n feature strings left over from an earlier card layout.
2.17.2 — UX
- One integrated date-range picker, everywhere. Every date-range filter across the dashboard — Finance, Dashboard Analytics, Attendance, Owner Dashboard, and POS Order History — now opens the same single calendar popover (
DateRangeField) instead of a pair of separate From/To date inputs. Quick-pick shortcuts (Today, Yesterday, Last 7/30 Days, This Month) live in the same popover rather than a second dropdown next to the inputs, so picking a range is one click into one control instead of juggling three. POS Order History keeps its own outer preset selector (with its wider "All time"/"Last month" options) and uses the new picker just for its "Custom" calendar step. Removed the now-redundantDateRangeLabelcomponent.
2.17.1 — UX
- Schedule: "Today" shortcut, day-off marking, and a print/PDF export. The week grid now has a one-click jump back to the current week, a "Mark Day Off" toggle per staff/day (shown distinctly from a working shift in the grid and in staff's own "My Schedule" view), and clicking a date header opens a day-at-a-glance detail view. Publishing a week now also opens a printable roster (branded with the same Epidom header/watermark/footer as other reports) — "printing" is how the exported PDF is produced (browser Print → Save as PDF), matching the existing Order History report's pattern rather than a separate one-off PDF pipeline.
- Attendance: date-range presets and a print/PDF export. The From/To inputs are replaced with the same integrated calendar-and-presets picker (Today, Yesterday, Last 7/30/90 Days, This Month, This Year, or a custom range) used on the Finance report, plus a "Print / Export PDF" action that exports whichever tab (Log or Hours & Overtime) is currently open.
- Fixed: "pages.noData" showing as literal text instead of translated copy on the Attendance page — the key existed nested under
pages.analytics.noData, not at the top levelpages.noDatathe new page actually called; added the missing top-level key to bothid.tsanden.tsand audited every other new i18n key introduced by the staff-scheduling/attendance feature against both locale files (all resolve correctly now). - Overtime threshold is now a time input, not raw minutes. "Standard work minutes per day" was a plain number field requiring mental math (e.g. typing "480" for 8 hours) — now an
HH:mmtime picker, converted to minutes only at the API boundary. - New
StaffSchedule.isDayOfffield (migration20260805230007_add_staff_schedule_day_off), mutually exclusive with the existing named-block/custom-time choice (enforced in Zod).
2.17.0 — Fitur
- Order-linked production. When a Kitchen & Bar order needs more of a recipe-linked product than is currently sitting on hand, the system now auto-drafts a production task the moment the order enters the kitchen queue — visible on the KDS board with a "making" indicator on that ticket — instead of running as a disconnected, staff-triggered-only workflow. Tapping the item to Ready on KDS completes that task in the same action; no separate "go complete the batch" step.
- Management's Production History now shows where each batch came from — a "Manual" badge for the existing proactive/ahead-of-demand flow (unchanged), or "From Order #…" for one auto-drafted by a live order, with the linked order number for traceability.
- Known v1 limitations: one auto-drafted batch per shortfall-triggering order — near-simultaneous orders for the same out-of-stock product each get their own batch rather than being consolidated (batch sprawl during a rush is possible); order cancellation doesn't currently auto-cancel a still-open linked batch; only the primary order-confirmation paths (POS cash/pay-later, POS finalize, and Xendit online payment) trigger the auto-draft — aggregator-imported orders and other less common entry points don't yet.
2.16.0 — Fitur
- Staff Scheduling. Managers can build and publish weekly work rosters from a new
/schedulepage — pick from reusable named shift blocks (e.g. "Shift 1" 08:00–16:00) or set a custom time per staff member per day, tag a Kitchen/Bar/Both department, and publish a week in one action. Staff (Cashier/Kitchen roles) see a read-only "My Schedule" list of their own upcoming published shifts. - Selfie + Geolocation Attendance. A new "Clock In / Out" action in the account menu (available to every staff persona, regardless of page permissions) captures a front-camera selfie and best-effort GPS location before recording a clock-in, clock-out, or absence report — falls back to the device's native camera picker if
getUserMediais unavailable, and never blocks the action if location permission is denied. Managers get a filterable audit trail (by staff and date range) on a new/attendancepage, with each entry's selfie thumbnail and a "view on Google Maps" link (no embedded map — see AGENTS.md's no-maps-library rule). - Automatic Working Hours & Overtime. The Attendance page's "Hours & Overtime" tab pairs clock-in/out events into completed workdays (correctly attributing a shift that crosses midnight to the day it started) and splits worked minutes into regular vs. overtime against a configurable per-store daily threshold (default 8h). A stuck clock-in (forgotten clock-out) is flagged rather than guessed at, with a manager "close manually" recovery action that appends a correction record instead of editing history.
- Shift-Block Revenue Report. A new "By Shift Block" tab on the Finance report sums revenue/order-count per named shift block per day, plus who was rostered on. Named blocks can deliberately overlap (staggered handover coverage), so this is a coverage-window report, not a partition — the UI explicitly discloses that totals across blocks aren't expected to sum to the grand total.
- Known v1 limitations (see
docs/roadmap.md): the overtime threshold is a single flat per-store value (no per-role/per-shift override); true order-level "who processed this sale" attribution only exists for cashiers via the existing POS till session (/finance/by-shift?staffId=) — for every other role, the new report shows "who was rostered on" as context, not a claim of who rang up a given order.
2.15.0 — Fitur
- Waste Management. Record wasted Materials or Products from Management → Edit Stock ("Record Waste") with a predefined reason (Expired, Damaged, Spoiled, Overproduction, Quality Control) or a custom free-text reason — each entry snapshots the item's cost and stores a computed loss value, and deducts current stock the same way a stock adjustment does.
- Waste loss is now trackable on the Finance report. A new "Waste Loss" KPI card (and, alongside it, a "Net Profit" card that already existed in the export but had no card) shows the period's total loss, which now also reduces Net Profit. A new "Waste" tab lists every entry with a by-reason breakdown, and included in the Excel export.
- Waste entries are correctable for any condition. Editing an entry (from the Finance Waste tab) reconciles current stock by appending a compensating movement rather than rewriting history, and an "Advanced" override lets a manager correct the recorded unit cost itself; deleting an entry restores the stock it had consumed.
- Fixed: stock adjustments for Products silently failed. The "Adjust Stock" dialog has offered a "Product" option since it shipped, but the backend rejected it with "Product stock adjustment not yet implemented." Product stock adjustments (and the new waste feature) now work for both Materials and Products.
2.14.15 — Fitur
- Account deletion replaced with deactivation + a 30-day reactivation grace period. Profile → Account Settings' "Delete Account" is now "Deactivate Account": your data is never touched, and logging back in any time within 30 days offers a one-click "Reactivate My Account" button that instantly restores everything. Your public storefront(s) go offline for as long as the account stays deactivated, and come back automatically on reactivation.
- 1-year data retention with support-assisted recovery. After the 30-day window, self-service reactivation ends but your data stays on file for up to 12 months — recovering it means contacting support for a case-by-case quoted fee, after which an admin can manually reactivate the account. Past 12 months, a new daily background job permanently and irreversibly purges the account.
- Admin panel: reactivate deactivated accounts. The Master Admin Panel now shows a "Deactivated" stat tile and badge per user, plus a "Reactivate Account" row action that works at any point within the 1-year retention window. The existing instant hard-delete action is unchanged.
- Updated Terms & Conditions and Privacy Policy with the full deactivation → reactivation → retention → deletion lifecycle, and explicit data-subject rights for France/EU users (GDPR, right to complain to the CNIL) and Indonesian users (UU PDP / Law No. 27 of 2022).
- Fixed: Privacy Policy required login to view.
/privacywas missing from the auth middleware's public-route allowlist, so visitors were redirected to/logininstead of seeing the policy — defeating the point of a public privacy policy. Added alongside/terms.
2.14.14 — Fitur
- "Mark as Paid" now records how an order was settled. Every mark-paid action — the Active Queue order card, the Order History detail dialog and its bulk action, and the dashboard's Unpaid Orders alert — now opens a confirmation dialog to pick the payment method actually used (Cash, QRIS, GoPay, OVO, DANA, ShopeePay, Virtual Account, Credit Card) and add an optional note (e.g. "client paid directly to the owner") before settling the order.
2.14.13 — Fitur
- Enterprise "Custom Development" page. Under the dashboard's Enterprise section, request a custom feature or website build — describe your requirements, budget, and timeline. Submissions notify the founder/CRO team by email and appear in a new admin triage queue at
/admin/custom-development. Users can edit or delete their own submitted requests. - Admin panel: Custom Development link + pending-request badges. The Master Admin Panel header now links to the Custom Development queue, and both it and the Feedback button show a live count badge for open/new items awaiting a first look.
- Consolidated Tracking into Management. The Tracking page's unique stock-movement ledger is now a "Movements" tab inside Management; its redundant Stock Levels view (already covered by the Data page) was dropped, along with the separate Tracking nav item.
- Fixed a tracking-page crash (
Value 'BOTH' not found in enum 'Department') caused by an out-of-sync generated Prisma Client after the Materialdepartmentfield'sBOTHoption was added. - Delivery print dialog's "Export PDF" now actually exports a PDF (previously a stub); removed the dead, unreachable "Add Delivery" form mode.
- Stock deduction now skips line items a KDS operator cancelled, so a cancelled item's ingredients/stock are no longer deducted when the rest of the order is delivered.
- Fixed the pricing page's plan cards: CTA buttons, dividers, and feature lists across the four tiers weren't level with each other because the header tagline and price block varied in height per tier — now consistently aligned.
2.14.12 — Fitur
- Finance reports: filter/breakdown by category, department, and staff shift. The Finance page now has Staff and Category (menu category) filters, plus new "By Category," "By Shift," and "By Department" report tabs/cards — the last answering "how much did Kitchen sell vs. Bar today." All tables gained sortable column headers.
- Kitchen/Bar department field, separate from the existing free-text category, on Raw Materials, Recipes, and Products — filterable and shown as a badge on the Data page, the Menu editor, and a new Kitchen/Bar toggle on the POS item grid. A Product's department automatically syncs to its linked storefront menu item, same as name/price already did.
- Dashboard: "New Orders" card. Highlights orders awaiting confirmation (especially storefront orders) right on the dashboard, with a link straight to the Order Queue. Live-updates via the existing order SSE stream.
- Dashboard & Finance date ranges now show a dynamic label ("Today," "Last 7 Days," "This Month," etc.) next to the date pickers, and the Dashboard's Analytics section now defaults to today instead of month-to-date.
2.14.11 — Fitur
- Fees & Taxes settings, per store. Added a "Fees & Taxes" card to each store's Profile page: a configurable tax rate (with an inclusive/exclusive toggle and custom label, e.g. "PPN 11%"), a separate service charge rate, and a payment-processing fee-rate table per method (QRIS, GoPay, OVO, DANA, ShopeePay, bank transfer, Stripe card), pre-filled with editable estimated default rates so a store gets accurate-ish reports even before touching the settings.
- Financial calculations and reports now account for fees and taxes. Tax, service charge, and the estimated payment-processing fee are computed once per order (at POS checkout, hold/finalize, and storefront checkout) and frozen onto the order — so editing a store's rates later never rewrites past reports. The Finance page now shows Tax, Processing Fee, and Net Revenue KPIs, and the per-channel breakdown deducts tax and processing fee alongside aggregator commission; both are included in the Excel export.
- The payment-processing fee is an estimate based on the merchant's configured rate, not a live reconciliation with Xendit/Stripe settlement data — Xendit's webhook payload doesn't carry the actual charged fee in this integration.
2.14.10 — UX
- Fix: "My tickets" crash for NEEDS_REVIEW status. Accounts with tickets transitioned to
NEEDS_REVIEWby an admin were experiencing a render crash on the "My tickets" tab. Added the missing status to the frontendFeedbackStatusunion type, theSTATUS_BADGESmap in the feedback dialog, and added corresponding translations (EN: "Needs review" / ID: "Perlu ditinjau"). - Storefront settings: real-time auto-save. All fields in the storefront settings page now auto-save to the database 1.5 seconds after you stop typing. The manual "Save Settings" and "Cancel" buttons have been removed. A subtle "Saving..." / "Store settings saved." indicator appears at the top of the form.
- QR code dialog: copy link. The shared QR code dialog (storefront, table QR, etc.) now shows the URL in a read-only input below the QR image, with a one-click copy button that briefly shows a green checkmark on success.
2.14.9 — UX
- Feedback dashboard: ticket detail modal. Every row/card now has an expand icon (and every board card is clickable) that opens a full-detail modal — user, page, full description, screenshot, dev note, and priority/status editing all in one place, without leaving the list.
- Clickable status filters. The 5 summary stat cards (Open / In Progress / Review / Resolved / Archived) now double as filters — click one to narrow the list to that status, click again to clear it.
- Type filter + search. Added a Bug/Feature/General type filter and a free-text search box (matches user, description, page, or ID) next to the view switcher.
- Three layouts: Table / Board / Feed. Added a view switcher — Table (existing grouped list), Board (Notion-style Kanban columns by status, unaffected by the status filter beyond dimming the other columns so the full picture stays visible), and Feed (flat, newest-first card stream). The chosen view persists across visits via
localStorage.
2.14.8 — Fitur
- Staff status management. The staff edit dialog now has an Active/Inactive control, guarded so the last active staff member or the store Owner can't be deactivated (would lock everyone out). The Owner role itself is locked from being changed and always appears pinned at the top of the list.
- Role Access Details panel. The staff edit dialog now shows a reference panel describing exactly what each role (Owner / Manager / Cashier / Kitchen) can access.
- PIN validation. The new-PIN field now requires exactly 4 digits before Save is enabled, with an inline error otherwise.
- Menu item descriptions. Storefront Editor's Add/Edit item dialogs now have an optional description field (e.g. "orange, jasmine, espresso"); it renders under the item name in both the editor list and the POS product grid.
2.14.7 — Fitur
- Add Product dialog now shows a "Link to existing menu item" selector when the store has menu items not yet connected to any inventory product. Selecting one links the new product directly to that existing POS/storefront item instead of auto-creating a duplicate entry. Fully optional — leaving it on "Don't link" continues creating a new menu entry as before.
- Added
?unlinked=truequery param toGET /api/stores/[id]/storefront/itemsto return only menu items with no product association (used by the selector above).
2.14.6 — Fitur
- Added "Review" status (
NEEDS_REVIEW) to the admin feedback tracker, sitting between In Progress and Resolved (styled purple). Stats card updated to show a 5th column for this status. - Added developer notes to each feedback entry: a private free-text field (saved as
devNotein the DB) accessible only to admins. Inline pencil-to-edit textarea with save/cancel — visible below the description in both mobile and desktop views. Existing notes render with a violet 👁 badge and remain editable. - DB migration:
20260728083727_add_feedback_needs_review_and_devnote— addeddevNote String?to thefeedbacktable andNEEDS_REVIEWto theFeedbackStatusenum.
2.14.5 — UX
- Added click-to-copy/bubble-to-copy to feedback description text. Clicking the description text now copies the full feedback text to the clipboard with a visual "Copied!" popover bubble (tooltip) and toast notification. Toggling expansion of long description texts is now handled by clear "Show more" / "Show less" links.
2.14.4 — Perbaikan
- Made the live "Change" preview in POS checkout recompute directly from the same field value driving the Amount Tendered input, in the same render pass, instead of a separate
useWatchsubscription — removes any chance of it lagging behind a keystroke. - Fixed wrong Change/Amount Tendered for stores on a non-IDR display currency (e.g. EUR) — every price in this system is stored internally in IDR and converted only for display, but the cashier's typed Amount Tendered was never converted back to IDR before being compared against the (IDR) cart total, so the preview showed a nonsense number, and — more seriously — the same unconverted value was being sent to the server and saved on the order. Amount Tendered is now converted to the base currency once, up front, before it's used anywhere (live preview, the printed receipt, and the API request). No effect on stores using IDR as their display currency (the common case), since that conversion is already a no-op there.
- Redesigned the POS topbar for mobile: store name, connection status, staff name/role, and sign-out now collapse into a single profile icon menu on the left, and the cart button that used to float over the bottom of the screen now lives on the right side of the same topbar. Desktop is unchanged.
- Added validation to POS cash checkout: Confirm Order is now disabled and shows an "insufficient amount" message whenever Amount Tendered is less than the order total, instead of allowing an order to be confirmed with negative change. Also enforced server-side, so a cash order can't be placed underpaid even by a direct API call.
- Filled in the Meta Pixel side of the acquisition funnel (previously only Google Analytics was tracking most of these stages): signup now sends
CompleteRegistrationwith real params instead of none; clicking a paid plan now also sendsInitiateCheckout; activating the free plan now sendsLead; and checkout success now sendsStartTrialfor the 14-day POS trial orSubscribefor an immediate paid subscription, distinguished via a newtrialflag threaded through the Stripe checkout success URL. Recurring-payment tracking from the Stripe webhook is server-side only and would need a separate Meta Conversions API integration — deferred for now.
2.14.3 — Perbaikan
- Fixed the POS mobile cart cropping the item list with no way to scroll to the rest. Root causes: the shared
ScrollAreacomponent was missingoverflow-hidden(affecting every usage of it in the app), and a bottom-sheet drawer's height (autoclamped bymax-height) doesn't reliably size a scrollable list through a nested flex layout. Switched the cart from a bottom-sheet drawer to a centered dialog — the same scrolling pattern already proven across every other dialog fixed this release — with its own header, scrollable item list, and a clearly separated totals footer. - Fixed the marketing site's mobile navigation drawer having no scroll region at all — a nav list taller than the screen had no way to reach the items below the fold.
- Simplified the POS product grid and table floor plan on mobile phones: removed the bordered "card" look from product tiles (the color-coded table status borders are kept, just thinner, since that color is a status signal, not decoration) and tightened the grid spacing so more fits on screen at once.
- Fixed the Tables page on mobile: the header (table count + "Download All Table QRs" + "Add Table") crammed into one row and wrapped into an unreadable multi-line column — it now stacks cleanly on narrow screens. Fixed the Reservations section's refresh button getting visually stranded on its own line once the status filter chips wrapped, by anchoring it to the section title instead. Tightened side padding throughout both sections on mobile.
- Fixed each table card's QR/edit/delete icons overlapping the table label — they were absolutely positioned over the card with no reserved space, which collided with the label below on narrower cards. They're now a normal row above the label instead, so they can't overlap it.
- Removed the left/right padding and card box around the Storefront Settings' Opening Hours rows on mobile — each day now runs edge-to-edge with a simple divider between days, instead of a boxed card floating with wide empty margins on both sides. The boxed look returns at
sm:and up. - Removed the shared
Cardcomponent's default padding and inter-section gap entirely on mobile phones app-wide (wasp-6/gap-6everywhere, phone or desktop) — every screen built on aCard/CardHeader/CardContent/CardFooter(Storefront settings, Dashboard data sections, account settings, and more) now uses the full screen width on a phone, with the normal spacing returning atsm:and up. Individual sections that already set their own padding are unaffected. - Removed the POS product grid's remaining left/right container padding and the gap between product tiles on mobile — the scrollable area now only has top spacing (
pt-2), so tiles run flush to the screen edge and to each other instead of floating with margins on every side. - Fixed the Storefront editor's loading skeleton overflowing narrow phones — it used fixed pixel widths (
w-96) sized for desktop instead of shrinking with the viewport, pushing part of the placeholder off-screen while the page was loading. - Capped the dashboard's shared page padding (
PageShell, used by every dashboard page — Data, Tables, Staff, Storefront settings, and more) atp-2on mobile phones, down fromp-4— one more source of wasted margin on every dashboard screen, not just the ones touched individually above. - Restored a border on the POS product grid's tiles on mobile — with zero gap between them and no image on some items, there was no visible boundary at all between adjacent products. The border now doubles as the tile spacing, reading as a clean grid instead of floating text.
- Fixed a console accessibility error on the POS mobile cart ("
DialogContentrequires aDialogTitle") from switching it to a dialog earlier this release — it has its own visible header already, so this is a screen-reader-only title/description rather than a duplicated one on-screen. - Fixed Confirm Order and Hold not submitting at all on POS Checkout, POS Hold, Table create/edit, Add Staff, and Open/Close Shift — introduced earlier this release while adding scroll-safety to these dialogs. The submit button was wrapped in a
<form>placed around the dialog, but dialog content renders through a React Portal to a different part of the page, so that<form>never actually contained its own submit button in the real DOM — clicking it did nothing. Fixed by using the sameform="id"attribute binding (works regardless of DOM position) already used correctly everywhere else in the app. - Silenced a Postgres driver deprecation warning on server startup ("SSL modes 'prefer', 'require', and 'verify-ca' are treated as aliases for 'verify-full'") by making the database connection string's SSL mode explicit (
sslmode=verify-full) instead of relying on the implicit alias. No behavior change today — this is exactly whatrequirealready resolves to — but it keeps the connection secure once a future major version of the Postgres driver changes what the implicit modes mean. - Added a ninth Enterprise plan feature to the pricing page: custom integrations and bespoke website builds for restaurants/cafés with specific needs beyond the standard plans.
2.14.2 — Perbaikan
- Fixed the POS checkout "Change" amount not always updating live as Amount Tendered was typed — switched to React Hook Form's
useWatch, the more reliable pattern for a value that needs to recompute on every keystroke. - Fixed checkout and Hold showing a generic "Failed to create order"/"Failed to hold order" toast with no way to know what went wrong. The API already knew the real reason (most commonly: resuming a held order after one of its items was deleted or taken off the menu) — the dialog was discarding it before showing a toast. It now surfaces the server's actual reason, naming the exact item(s) that are no longer available so the cashier knows what to remove from the cart.
- Fixed several dialogs across POS and the dashboard (checkout, hold order, staff, shifts, account settings, menu editor, and more) that had no internal scroll region — on a phone, content taller than the screen (e.g. a Notes field at the bottom of checkout) simply ran off the bottom with no way to scroll to it. They now use the same bounded-height, scrollable-body pattern already used elsewhere in the app.
- Simplified the mobile phone layout across POS, the customer storefront menu, and dashboard list views (Materials, Products, Recipes, Suppliers, Staff, Shifts, Feedback): removed boxy card borders/shadows/heavy padding in favor of a flatter, whitespace-separated list, and shrank secondary/metadata text (timestamps, emails, descriptions) so primary content stands out more. Desktop and tablet views are unchanged — this only applies below the
smbreakpoint. - Fixed the POS mobile cart's "Clear All" button overlapping the sheet's own close (X) button — the sheet's close button floats in the top-right corner regardless of content, so the cart header now reserves room for it on mobile.
- Fixed the POS header's date/time and staff badge overlapping the store name on narrow phone screens — the header has a fixed height, so text that didn't fit was wrapping and getting clipped instead of showing cleanly. The clock is now hidden on mobile (redundant with the phone's own status bar) and the staff name truncates instead of overflowing.
- Tightened the Storefront Settings "Online Orders" / "Table Reservations" toggle cards on mobile — removed the box border/rounded corners/padding in favor of a plain divided list, matching the flattened style used elsewhere.
2.14.1 — Perbaikan
- Added Credit Card as a POS payment method, alongside Cash/QRIS/e-wallets/Virtual Account.
- Fixed the POS cart failing to Pay or Hold again after resuming a held order, with visibly wrong totals (e.g. a subtotal in the billions). The API endpoints powering the order queue were sending raw database price values straight to the browser instead of converting them to plain numbers first — the browser received them as text, so every total calculation silently glued numbers together as strings instead of adding them.
- Fixed the storefront's checkout button becoming unreachable when several items were in the cart — the same underlying viewport-height issue from the last release, found in more places (POS's mobile cart sheet and about a dozen other dialogs across Data, Recipes, Suppliers, and Management) and fixed everywhere at once via the shared dialog component.
- Fixed table action buttons (QR code, edit, delete) and a notification's dismiss button being completely unreachable on iPad/touch devices — they were only ever shown on mouse hover, which touchscreens don't have. They're now always visible.
- Enlarged several more touch targets that were under Apple's recommended minimum tap size across POS (staff switch, table actions, reservation actions).
- Fixed the desktop POS cart's Pay button becoming unreachable with enough items in the cart, with no way to scroll down to it. A shared layout container was missing
min-height: 0, a CSS default that (combined with a scrollable flex container) otherwise lets content grow past its allotted space instead of scrolling within it — silently clipping the bottom instead of showing a scrollbar. Fixed at the shared layout level, so this can't recur on any dashboard page, not just POS. - Fixed the desktop POS cart's Pay button overflowing past the panel's right edge regardless of total size — it was sized to 100% of the row's width while sitting next to the Hold button, which a plain width percentage doesn't account for. It now correctly grows to fill only the space actually left over, and wraps onto a second line for a very large total instead of cropping.
- Fixed the desktop POS cart panel itself getting squeezed/cropped on the right — the menu grid next to it had the same missing "shrink below natural width" behavior horizontally that the scroll bug had vertically, so it could refuse to make room for the cart's fixed width. The cart panel now always keeps its full width regardless of the menu's content.
- Audited the rest of the app for the same iPad/touch issues found above and fixed what else turned up: the Create/Edit Store dialogs had the same viewport-height bug, and a toast notification's dismiss button plus the "remove photo" control on every image upload (menu items, profile photo, etc.) were hover-only and unreachable on touch, same as the table buttons.
- Documented all of these as explicit rules in the project's coding guidelines, so future changes get checked for them upfront instead of being caught one bug report at a time.
2.14.0 — Fitur
- Data → Materials: added Purchase Quantity + Purchase Price to raw materials and their per-supplier prices. Buy flour as "a 1000g bag for €2" instead of having to work out and type a per-gram cost by hand — the exact per-unit cost is derived and stored automatically in the background, and everywhere that cost feeds into (recipe costing, stock value, supplier comparisons) keeps working exactly as before. Existing materials are unaffected — they're treated as a pack of 1, same as today.
- Fixed Unit Cost silently rounding down to "€0.00" wherever it was shown as a standalone per-unit rate (Materials list, Material details, Recipe cost-estimate breakdowns, Supplier details) — a real cost like €0.002/g was being truncated to 2 decimal places for display. It now shows enough precision to actually be meaningful.
- Fixed buttons feeling unresponsive or needing a second tap on iPad Safari across the POS Cashier and public storefront — the app never told iOS to skip its default double-tap-to-zoom detection delay, which can eat a fast, deliberate tap. Also enlarged several touch targets that were well under Apple's recommended minimum size for a fingertip: the POS cart's quantity +/- and Remove controls, the Pay/Hold/Clear buttons, and the storefront cart's quantity +/- and floating "Checkout" button (previously as small as ~16px, now 36–48px).
- Admin → Feedback: added a Priority field (Urgent / High / Medium / Low) alongside Status, editable inline from the same dropdown pattern. Tickets are now sorted by priority within each status group, so the most urgent open items always surface first.
- Fixed the Data page crashing with "Decimal objects are not supported" right after saving a Material — the new Purchase Quantity field wasn't included in the step that converts database values into plain data before sending them to the page, for both a Material's own record and its per-supplier prices.
- Dashboard: the fixed sidebar now only shows at true desktop/laptop widths. Every iPad size (Mini, Air, and Pro in portrait, up to 1024px) previously got the full 230px sidebar forced on top of already-tight content — they now get the same collapsible hamburger menu already used on phones, giving the actual page far more room.
- Fixed the storefront's checkout, cart, and item-detail sheets getting cropped on iPhone Safari with the "Pay" button pushed out of reach — they sized themselves against the full-screen viewport height instead of the actually-visible area above Safari's address bar and toolbar. Also padded each sheet's bottom edge for the home-indicator area on notched iPhones.
2.13.1 — Perbaikan
- Fixed every Materials/Products/Recipes/Suppliers/Feedback save showing a generic "Invalid input data" error with no explanation. The server always computed the exact field and reason (e.g. "SKU is required", "Price must be non-negative"), but every form was discarding that detail before showing it. Errors now say exactly which field is wrong and why, and highlight that field in red on the form — instead of leaving you to guess.
- Fixed saving a Material or Product failing with "Price can only have 2 decimal places" even when the price you typed was a clean whole number. Any account on a display currency other than IDR converts prices through a floating-point exchange rate before saving, which almost never lands on an exact 2-decimal value — that unrounded value was then rejected by the server. Converted prices are now rounded to 2 decimals at the source, so this can no longer happen.
- Fixed linking a recipe to a Product sometimes leaving Cost Price stuck at a stray "0" instead of the recipe's real cost. A cheap recipe's per-unit cost can be a real, non-zero amount in the base currency (IDR) that still rounds to 0.00 once converted to a stronger display currency — that rounded-to-zero value was being written into the field anyway (now it's left alone instead), and a separate rendering bug was displaying that 0 as a stray floating character on the form even when the field itself was otherwise empty.
- Data → Products: Cost Price is now locked to the recipe's auto-calculated value whenever a recipe is linked, so it can't drift out of sync with the recipe by accident. A "Customize cost price manually" checkbox (off by default) unlocks the field for a manual override when you actually need one.
2.13.0 — Fitur
- Data → Products: linking a recipe to a product now auto-calculates the Cost Price from that recipe's real ingredient cost (summed across every linked recipe if more than one), instead of requiring manual entry. The field stays editable — this is a smart default, not a locked value — and only kicks in when you actively change the recipe selection, so opening an existing product never overwrites a price you already set.
- Data → Materials and Products: adding a new item now suggests a SKU automatically from the name/category (still fully editable, with a regenerate button), and shows a live "available" / "already used" check as you type — no more finding out about a duplicate SKU only after a failed save. The Category field is now a searchable picker suggesting categories you've already used, instead of free text, to cut down on near-duplicate categories from typos — you can still type a brand-new one.
2.12.0 — Fitur
- Admin Feedback console: entries now group by status (Open → In Progress → Resolved → Archived) with newest first within each group, each row/card and its status dropdown are color-coded by status, and every entry shows its ID (click to copy) for referencing a specific ticket.
- POS orders can now be cancelled — from the Active Queue (a Cancel button on each order card) or from Order History (a Cancel button in the order detail view). Cancelling an order that was already marked Delivered automatically restores the stock that had been deducted for it, so inventory numbers stay accurate.
- POS Cashier: a new "Hold" button next to Pay lets a cashier park the current cart aside (labeled by customer/table) to serve someone else, without completing payment. Held orders show up immediately in the Active Queue (with a Resume button) and Order History, but stay out of the Kitchen Display until they're resumed and actually paid — and out of every revenue/analytics report until then too. Resuming reloads the cart for editing; paying finalizes the same order rather than creating a duplicate.
- Fixed the Active Queue order cards' action buttons (Start Processing, Resume, Complete) wrapping into and overlapping the neighboring card, which also cut off the Cancel button.
- The Enterprise plan's pricing CTA now opens a WhatsApp chat instead of the old Calendly booking link. Every public-facing contact point across the site (Privacy, Press, Careers, GDPR, Partners, Contact, Refund Policy, Terms, footer, checkout-failed screen, and FAQ) now points to the same three real addresses — cro@, ceo@, and consult@prionation.io — and the same real WhatsApp number, replacing several stale placeholder emails and a wrong WhatsApp number.
- Storefront Settings now has a "Show QR" button next to your storefront link, generating a downloadable QR code for it. Tables Manager can now generate a QR code per table (scanning it opens the menu with that table's number already filled in for ordering) — download one at a time, or all at once as a single labeled sheet for printing table tents.
- Storefront Settings now has an Opening Hours editor — set open/close times or mark each day closed, shown on your public storefront page. This was previously only possible to display, not edit.
- Fixed the Tables Manager's per-table QR/Edit/Delete icons being invalid HTML (buttons nested inside another button), which was triggering a React hydration warning.
- Profile settings now offer a searchable picker for currency (~150 world currencies, up from 4) and timezone (the full worldwide IANA list, up from 5), instead of a short fixed dropdown. Currency conversion now works for any of them — previously only USD actually converted prices (EUR/MGA just relabeled the number); the POS Cashier screens (cart, checkout, order history) previously didn't convert at all and now do, matching what Dashboard finance/materials pages already did.
- The Admin panel's Region column now shows "Unknown" instead of a specific-looking but potentially fake city/currency for any account whose timezone has never actually been confirmed by a real device sync — previously every account showed "Jakarta · IDR" from the moment it was created, whether or not that was real. Also fixed the existing browser-timezone auto-sync silently never re-confirming itself for a device that had already synced once before this tracking existed.
2.11.0 — Fitur
- Installed Google Analytics 4 and the Meta (Facebook) Pixel, both gated behind the cookie-consent bar (GA needs analytics consent, Meta needs marketing consent) and both wired to fire nothing until the visitor actually grants it.
- Instrumented the full funnel with GA4 events: marketing CTA clicks (hero, header, pricing plans, "book a demo", email capture, contact form), signup/login, every onboarding step plus final completion, dashboard activity (creating a product/material/recipe/supplier/production batch), POS checkout (as a standard e-commerce
purchaseevent with line items), and billing (checkout started, subscription confirmed). - Meta Pixel now also fires a
CompleteRegistrationevent on signup, and got two reliability fixes: the pixel ID is hardcoded as a fallback so it still works if the hosting provider's env config is missing it, and the base code renders as a literal<script>tag physically inside<head>(a Next.js quirk meant it wasn't showing up as real markup, which is what Meta's own install check looks for).
2.10.0 — Fitur
- POS plan now comes with a 14-day free trial — connect a card, pay nothing for 14 days, then it renews automatically. The POS plan is highlighted as a special promo on the pricing page.
- Clearer plan boundaries — Online Orders and Table Reservations now require the POS plan; Data (Materials/Recipes/Products/Suppliers) stays an Operations feature. Hitting a feature your plan doesn't include shows an "Upgrade to POS" prompt (or sends you to pricing) instead of failing silently.
- Every plan now has a proper home for its menu: FREE manages a display-only Store Menu from Storefront settings; POS and up get a dedicated Menu page (same data, editable items with full edit/delete support); Operations' Data page keeps the deeper Product/recipe/stock layer, auto-synced to the menu so a Product's name/price never drifts from what customers and the POS Cashier see.
- Onboarding can now import your existing menu from a CSV / old data file (POS plan).
- Admin "Reset Account Data" now also signs the user out on all devices so they cleanly restart from onboarding.
- Log in and sign up are now one page with a toggle instead of two separate screens — same
/loginand/registerlinks still work. - Menu items can now have a photo — add or edit an item to upload one, with a size/resolution guide (square, 500×500 px ideal, max 2 MB).
- Fixed a redirect loop that could bounce a freshly-logged-in new user between
/loginand/storesinstead of landing them on onboarding. - Fixed rate limiting: every endpoint a user/IP hit shared one counter, so routine background checks (e.g. subscription status) could silently exhaust the much tighter budget for payment-sensitive endpoints like checkout, wrongly blocking the very first attempt. Each endpoint now has its own independent counter.
- Fixed stock/quantity fields (stock adjustment, materials, products, recipes, supplier orders, production batches) rejecting small decimals like 0,02 or 0,002 — inputs now accept both comma and period as the decimal separator, and precision was widened to 3 decimal places end-to-end (matching what the stock-adjustment ledger already supported) so gram/millilitre-level measurements track correctly. Also fixed the same comma-decimal typing bug on every price/cash field (cost price, selling price, supplier price, menu item price, POS cash tendered, shift opening/closing cash), and a follow-up bug where a mid-typing value could get rewritten (e.g. "0,0" collapsing back to "0").
- Material/product/recipe/supplier picker dropdowns (stock adjustment, recipe ingredients, supplier reordering, delivery receiving, production) are now sorted alphabetically instead of by creation date, so they're easier to scan.
- Fixed recipe cost calculations being off by up to 1000x whenever an ingredient's unit (e.g. grams) differed from its material's stock unit (e.g. kilograms) — cost per batch, ingredient cost breakdowns, production batch cost analysis, and stock deduction on sale now correctly convert between units before multiplying. The recipe editor's ingredient unit field is now locked to the material's unit to prevent the mismatch from being reintroduced.
- Fixed a crash opening any supplier with a phone number saved in a spaced-out format (e.g. "+33 3 88 45 12 67") instead of strict E.164 — the phone input now normalizes on read, and new/edited supplier phone numbers are validated up front.
- Products are now automatically added to the store's POS/storefront menu as soon as they're created (via the Data page or CSV import), grouped under a menu category matching the product's own category — no more manually clicking "Add to POS menu" for every item. You can still remove any item from the menu manually at any time. Existing products that weren't yet in the menu have been added.
- Increased the max zoom on the Instagram onboarding profile-picture cropper from 300% to 700%, so a small or low-res profile photo can still be framed to fill the crop area.
2.9.0 — Fitur
- Instagram quick-start onboarding — upload a screenshot of your Instagram profile and AI pre-fills your storefront: business name, tagline from your bio, storefront URL from your username, Instagram link, a brand color, and smarter menu suggestions. Crop your profile picture straight from the screenshot to use as your logo — or skip and set up manually as before.
2.8.0 — Fitur
- Customer feedback widget — report a bug or suggest a feature from the dashboard, with an optional screenshot; tickets are emailed to the team and users get a "My tickets" tab to track, edit, or delete their own submissions.
- Admin feedback console — review all tickets with status control (Open / In Progress / Resolved / Archived), an in-place screenshot preview, and a mobile-friendly layout.
- Order history — the POS Orders page now has "Active" and "History" tabs with search, status/source/date filters, an order-detail dialog, and Excel export.
- Customer "My orders" — storefront visitors can see the orders they placed on this device, with live status, from a new My Orders page.
- Dashboard analytics — a reporting section with Revenue, Orders, Average Order Value, and Customers KPIs, revenue-trend and orders-by-status charts, a date range, and Excel export.
- Mobile-first pass — the POS cashier cart now works on phones (floating bar + bottom sheet), dense tables collapse to cards on small screens, and pinch-zoom is disabled app-wide for a native feel.
- Install app — a cross-platform "Install app" button in the mobile menu that also guides iOS Safari users through installation.
- Changelog & versioning — this changelog is now database-backed, surfaced through a "What's new" notification in the dashboard bell and a clickable version badge in the footer and dashboard.
2.7.0 — UX
- Public storefront is now fully light & dark theme-aware — fixed washed-out, unreadable text in dark mode across the menu, item detail, cart, checkout, and order-status pages by moving to semantic design tokens.
- Storefront language switcher (Indonesian / English / French) plus a light/dark toggle added to every storefront page; the visitor's browser language is auto-detected on first visit.
- Storefront fully internationalized across id / en / fr — menu, cart, checkout drawer, item detail, order status, and store profile.
- Storefront profile footer redesigned — compact, with the Epidom logo + wordmark, and trimmed empty space.
- Home page: smoother section transitions — removed the stray dark band that appeared between adjacent warm (cream) sections.
- Fixed a React hydration error on the storefront (the open/closed badge and theme toggle now resolve on the client).
- Fixed dialog action buttons touching with no gap (Add Menu Item and POS checkout).
- Fixed missing translations in the storefront menu editor's Add Item dialog.
- Admin: Revenue report now reads live Stripe data with multi-currency views (IDR / EUR / USD), a per-month cash-collected recap, a customer payment log, and one-click Export / Print to PDF.
2.6.0 — UX
- Storefront logo & cover image are now photo-upload fields — drag-and-drop with preview, auto-compression, and resolution guide (logo 400×400 · cover 1920×1080).
- Data / Products — 'Add to POS menu' button now shows 'In Menu' badge instantly (optimistic update) instead of waiting for a page reload.
- Sync-to-menu prompt: editing a product's price or name now offers a one-click action to update the linked POS menu item.
- Recipe cards show a '47× last 30d' demand badge — pulls 30-day POS order counts so you know which recipes are driven by real sales.
- Tracking page gains a 'Recent Movements' tab showing all stock changes with source context (POS order # / Batch # / Manual).
- Dashboard now includes a 'Recent Stock Movements' card as a live activity feed.
- Subscription pricing on profile page updates instantly when you change your currency (IDR / USD / EUR) — no reload needed.
- Pricing labels corrected: POS = Rp 99.000/bln, OPERATIONS = Rp 249.000/bln.
- PWA install button in the topbar — disappears automatically when the app is already installed.
- Removed orphaned placeholder component (data-manage.tsx).
2.5.0 — Infra
- Prisma 6 → 7 — migrated to pg driver adapter (@prisma/adapter-pg); removed url/directUrl from schema.prisma; added prisma.config.ts for CLI.
- Added DIRECT_URL support (Neon non-pooled endpoint) so prisma migrate deploy runs over a direct connection and never hits pgBouncer prepared-statement limits.
- build script: prisma migrate deploy now runs before next build — missing columns (isAdmin, hasOnboarded) are created automatically on every Vercel deploy.
- Service worker fix: response.clone() was called inside an async .then(), causing 'body already used' errors that blocked login. Fixed to clone synchronously.
- OAuth errors now redirect to /login?error=<code> with a human-readable toast instead of Better Auth's raw HTML error page.
2.4.0 — Fitur
- Guided onboarding (5 steps: business → logo → menu → theme → publish) with server-side redirect guard after completion (hasOnboarded flag).
- POST /api/onboarding/complete — marks onboarding done permanently; subsequent visits to /onboarding redirect to /stores instantly.
- Fixed onboarding menu-item save bug: createCategory() was returning the inner payload directly but code was reading .data?.id — items silently skipped. Fixed.
- Staff email invitation flow — PIN delivered via Resend after invite.
- Table reservations — per-table toggle, public booking form, dashboard management panel.
- Notification bell with badge count for unread alerts.
2.3.0 — UX
- Full i18n sweep — 100+ new translation keys across POS, KDS, Tables, Storefront editor, Finance pages.
- epi-navy dark / epi-cream light design tokens fully bridged into shadcn CSS variables.
- Dark/light mode toggle in topbar; default dark.
- Currency provider — formatPrice() auto-converts from stored value to user's selected currency (IDR / USD / EUR).
- Shift management — currency-aware formatting, sortable columns.
- Account Settings — data usage stats, linked accounts, change password, delete account.
2.2.0 — Fitur
- Finance reports — daily/weekly/monthly P&L: revenue, COGS, gross margin. Per-channel breakdown (DIRECT / GoFood / GrabFood / ShopeeFood / Tokopedia).
- Multi-outlet owner dashboard (ENTERPRISE) — rolls up all stores with drill-down.
- Aggregator email ingestion — GoFood/GrabFood order emails → Order records via Inngest + OpenAI.
- Automatic stock deduction on order → DELIVERED (serializable transaction through Recipe → Material chain).
- LOW_STOCK and CRITICAL_STOCK alerts with notification bell badge.
- Staff PIN login — clock-in with PIN, shift open/close.
2.1.0 — Fitur
- POS cashier — menu grid, cart, checkout dialog (CASH / CARD / TRANSFER / QRIS).
- Kitchen Display System (KDS) — real-time order columns by status.
- Order queue with SSE real-time updates.
- Table management — seat assignment, status tracking (FREE / OCCUPIED / RESERVED).
- Production batch management — schedule batches from recipes, track status, view history.
- CSV Smart Import (AI-powered) for Products, Materials, Recipes, Suppliers.
2.0.0 — Fitur
- Public storefront at /@slug — customizable menu page, theme color, tagline, social links.
- Online ordering — customer checkout form, QRIS/cash payment, order tracking page.
- Storefront editor — WYSIWYG menu builder with drag-and-drop category reordering.
- GoFood / GrabFood / ShopeeFood / Tokopedia aggregator link fields.
- Storefront analytics — view counts.
- Inventory management — Products, Materials, Recipes (with cost-per-batch), Suppliers.
1.0.0 — Fitur
- Public launch — auth (email/password + Google OAuth), store creation, billing (FREE / POS / OPERATIONS / ENTERPRISE via Stripe).
- Dashboard with stock overview, production chart, alerts card, supplier card.
- Better Auth integration — HMAC-signed cookies, email verification, password reset.
- Multi-store support (Business → Stores hierarchy).
- Indonesian (id) primary language, English (en) secondary.