Because the server runs on-site, the POS continues to take orders when the internet goes down. For a restaurant, that matters more than almost any other property of the system.
Mise-Kal is a derivative of Mise, developed independently as a long-term project.
| License | MIT |
| Platform | Windows, macOS, Linux, Android, iOS |
| Backend | PocketBase 0.40.1 (single binary) |
| Application | Flutter 3.47.5 |
| Tests | 101 backend checks, 74 Dart tests |
| Status | Pre-1.0, active development |
Table of contents
- Capabilities
- Design invariants
- Architecture
- Installation
- Development
- Testing
- Repository layout
- Roadmap
- Contributing
- Security
- Acknowledgements
Capabilities
Point of sale
Floor plan with live bill totals per table. Order taking with modifiers, kitchen notes, and quantities. Send-to-kitchen in one action. Partial payments accumulating to settlement. Discounts by percentage or amount, each with a reason and each written to the audit log. Voids are recorded rather than silently deleted.
The till keeps working when the network drops: adding to a bill is queued to disk and drains automatically when the server answers again.
Kitchen display
Tickets appear the moment the till fires them, sized to read from across a kitchen. Tapping an item walks it through queued, cooking, ready. Timers count from when the ticket was fired and the card ages neutral to amber to red against a ten-minute target. A bill's status is derived server-side so every terminal agrees on it.
Management
Menu with categories, photographs, tags, preparation times, modifier groups, and a sold-out toggle. Tables grouped into zones. Staff accounts with five roles, each described in plain language rather than by permission flags; a manager can reset a forgotten PIN without anyone opening the admin dashboard. Sales broken down by item, by who served it, and by hour, with CSV export.
Table-side ordering
A guest scans the QR code on their table and orders from their own phone. It is a web page, not an application to install. It runs on the same server and port, so nothing is exposed to the internet and there is no second process to manage.
Guests reach exactly three public routes; all fourteen collections remain staff-only. No price is ever taken from the request. Their items land on the table's bill unsent, and a waiter fires them.
Hardware
Network thermal printers over TCP 9100 using ESC/POS, which is what virtually every restaurant printer already speaks, with no driver to install. A printer is an address and a paper width. Receipts and kitchen tickets, with a PDF fallback.
Design invariants
These are not style preferences. Each is enforced in code and covered by a test. Breaking one silently is how a restaurant loses money.
Money is only ever computed on the server
server/pb_hooks/lib_money.js recomputes every order's totals on write. A client
that PATCHes a forged total has it overwritten.
A point-of-sale system must never let a client decide what a bill costs. The tablet is on the restaurant's wi-fi and could be anything.
Test: smoke, "Client cannot forge a total".
Menu names and prices are snapshotted onto order lines
When an item is added to an order, its name and unit price are copied onto the line. The line does not reference the menu item for pricing.
Editing tomorrow's menu must never rewrite yesterday's bill.
Takings are counted by when a bill closed
Reports and shifts key on closed_at, not created_at.
A table seated before a shift began and settled during it is that shift's takings. Keying on creation credits the money to the wrong session and leaves the drawer looking short.
Ageing and status use separate visual channels
On the kitchen display, a ticket's border and timer carry how long it has been waiting. Its dots and chips carry what state each item is in.
Merging them makes a late ticket full of cooking items unreadable: the two meanings fight each other. A red frame with blue dots reads correctly at a glance.
Architecture
+-------------------------------------------------------------------------+
| RESTAURANT LAN |
| |
| +-------------------+ +------------------------+ |
| | Flutter app | | Guest phone | |
| | | | | |
| | POS shell | | QR code -> web page | |
| | KDS shell | | 3 public routes only | |
| | Manager shell | | no price from request | |
| +---------+---------+ +-----------+------------+ |
| | | |
| | HTTP (LAN only) | |
| +------------------+ +-------------------+ |
| | | |
| v v |
| +---------------------------------------------+ |
| | PocketBase (single binary, on-site) | |
| | | |
| | +----------------+ +-------------------+ | |
| | | Migrations | | Hooks | | |
| | | | | | | |
| | | the schema | | order numbering | | |
| | | version | | ALL MONEY MATH | | |
| | | controlled | | role guards | | |
| | | | | audit log | | |
| | +----------------+ +-------------------+ | |
| | | |
| | +----------------+ +-------------------+ | |
| | | SQLite | | Realtime subs | | |
| | +----------------+ +-------------------+ | |
| +---------------------------------------------+ |
| |
| +-------------------+ |
| | Thermal printer |<-------- ESC/POS over TCP 9100 -----------------+|
| | receipt / pass | |
| +-------------------+ |
+-------------------------------------------------------------------------+
Request path for one order
Waiter taps a menu item
|
v
+-----------------+ optimistic local line
| POS shell |----------------------------> shown immediately
+--------+--------+
|
| create order_items record
v
+-----------------+ recompute line total,
| lib_money.js | then recompute the bill,
| (server hook) | then persist
+--------+--------+
|
| after-success hook
v
+-----------------+ realtime subscription
| KDS shell |<---------------------------- ticket appears
+-----------------+ no refresh, no polling
Offline behaviour
Network up Network down Network restored
---------- ------------ ----------------
write sent --> write queued to disk --> queue drains,
to server (addLine only) oldest first,
stops at first failure
so ordering survives
Opening a bill and settling one deliberately do not work offline. Opening a bill needs a number the server assigns, and settling one needs a total only the server computes. Settling against a made-up number is how a restaurant loses money.
Installation
Requirements
| Component | Version | Notes |
|---|---|---|
| Flutter | 3.47 or later | For running from source or building |
| Git Bash | Any recent | Windows only; every script is POSIX shell |
| Developer Mode | Enabled | Windows build only; Flutter needs symlinks for plugins |
PocketBase is downloaded on demand. There is no database to install and no cloud account to create.
Running from source
git clone https://github.com/thewoldaa/mise-kal.git
cd mise-kal
# Terminal 1 - the server
./server/scripts/dev.sh
# Terminal 2 - the application
cd app && flutter run -d windows
The first run fetches the pinned PocketBase into server/bin/ and applies the
schema migrations. The admin interface is then at http://127.0.0.1:8090/_/.
On first launch, point the application at 127.0.0.1:8090. The -d macos,
-d linux and -d chrome targets work as well.
Building for Windows
installer/windows/build_windows.sh
Produces a runnable build at app/build/windows/x64/runner/Release/Mise.exe
with PocketBase staged into it, so the application starts its own server on
first run.
Developer Mode must be enabled once, as an administrator:
start ms-settings:developers
The script checks for this before doing anything and explains the fix rather than failing with Flutter's message, which does not name the prerequisite.
Building for macOS
./installer/macos/build_dmg.sh
Produces a DMG. It is unsigned unless CODESIGN_IDENTITY is set; users open it
from the right-click menu once. See docs/install-macos.md.
Development
Setup
# One-time: fetch PocketBase and jq into the harness cache
.harness/scripts/setup-tools.sh
# Verify the machine can run everything
.harness/scripts/worktree.sh doctor
Working in an isolated worktree
.harness/scripts/worktree.sh create --agent <name> --task <task-id>
cd .harness/worktrees/<name>
# ... inspect, plan, implement, test ...
.harness/scripts/test.sh --agent <name> --all
git commit -m "feat(scope): what changed and why"
.harness/scripts/worktree.sh report --agent <name> --task <task-id> --summary "..."
Each agent receives its own git worktree, branch, reserved port block, and scratch directory. Nothing is shared except git's object store, which git already makes safe for concurrent use.
The wave model
Wave 1 agent A: task 1 agent B: task 2 agent C: task 3
\ | /
+------ integrate into main ----------+
Wave 2 agent D: reads the result of Wave 1, builds on it
Integration refuses to start from a dirty main, runs a trial merge to detect
conflicts before anything lands, merges each branch as its own attributable
commit, runs the full suite on the merged result, and verifies that each branch
is genuinely an ancestor of main afterwards.
.harness/scripts/wave.sh open wave-1 "Phase 1 stabilisation"
.harness/scripts/wave.sh add wave-1 --agent alice --task t-001
.harness/scripts/wave.sh integrate wave-1 --dry-run # conflict check only
.harness/scripts/wave.sh integrate wave-1 # merge and verify
.harness/scripts/wave.sh close wave-1
See .harness/README.md for the full manual.
Testing
.harness/scripts/test.sh --agent <name> --all # everything
.harness/scripts/test.sh --agent <name> --suite smoke # one backend suite
.harness/scripts/test.sh --agent <name> --app # Dart tests only
Backend suites
Each spins up a throwaway database on its own port and tears it down afterwards. None of them touch real data.
| Suite | Covers |
|---|---|
smoke |
Order numbering, modifier pricing, tax and service charge, voids, table release, forged totals |
setup |
First-run endpoints, and that bootstrap can never run twice |
kitchen |
A bill's status following its lines, and the guards on bills not in service |
payments |
Part payments, settlement, discounts, refusing money against a cancelled bill |
staff |
PIN resets, privilege guards, and that a venue cannot lose its last owner |
guest |
Table-side ordering: menu visibility, server-side pricing, route isolation |
Dart tests
Printing, reporting, the offline queue, and localization. The offline queue
tests use a fake PocketBase speaking HTTP on a loopback socket rather than a
mock object, because what matters is that a real ClientException with
statusCode == 0 comes back out of the SDK. That distinction is what
isNetworkFailure keys on to decide between keeping a write for retry and
dropping it as refused.
Why the harness patches the suites
Upstream's suites were written for a single developer on one machine. Three things in them break under concurrency, and all three were confirmed by reading the scripts:
| Problem | What happens | Fix |
|---|---|---|
| Hardcoded ports 8091-8098 | Two agents on the same suite fight over a port; one gets a connection refused that looks like a product bug | Each agent gets a reserved block |
Literal /tmp/g.json |
Git Bash does not honour TMPDIR for a literal /tmp path, so agents overwrite each other's responses |
Scratch paths are rewritten per agent |
pkill -f "pocketbase serve --dir=..." |
pkill -f matches command lines as strings; every agent's server has identical relative arguments, so one agent's cleanup kills another's server mid-suite |
The server's PID is captured and only that process is killed |
The harness generates a patched copy at run time and leaves the originals untouched, so upstream stays mergeable. It asserts on every substitution, so an upstream restructure fails loudly rather than silently running an unisolated suite.
Repository layout
mise-kal/
├── PLAN.md Design contract inherited from upstream
├── README.md This file
├── CONTRIBUTING.md Workflow contract
├── SECURITY.md Threat model and the guarantees the code keeps
├── AGENTS.md How an AI agent works in this repository
├── LICENSE MIT, with the upstream copyright preserved
├── .env.example Configuration template, no values
│
├── .harness/ Multi-agent development harness
│ ├── README.md Operating manual
│ ├── scripts/ worktree, wave, test, integrate, cleanup, security
│ ├── tasks/ Task definitions with front matter
│ └── waves/ Wave manifests
│
├── server/ PocketBase: schema and all business rules
│ ├── VERSION Pinned PocketBase version
│ ├── pb_migrations/ The schema, version-controlled
│ ├── pb_hooks/ Rules that cannot live on a client
│ ├── pb_public/ The guest-facing page
│ ├── scripts/ Dev server and six test suites
│ └── fetch_pocketbase.sh Downloads the pinned binary
│
├── app/ Flutter: one binary, three role-based shells
│ ├── lib/core/ Design system, discovery, printing, reporting
│ ├── lib/data/ Models, repositories, offline queue
│ ├── lib/features/ setup, auth, pos, kds, manager
│ ├── lib/l10n/ Indonesian and English translations
│ └── test/ Dart tests
│
├── installer/ Packaging, per platform
└── docs/ Owner guide, developer guide, architecture
Roadmap
| Phase | Focus | Status |
|---|---|---|
| 1 | Stabilise the implementation, tests, and installer | In progress |
| 2 | Indonesian localization | In progress |
| 3 | Payment and QRIS integration layer | Planned |
| 4 | WhatsApp and business notifications | Planned |
| 5 | Inventory and stock control | Planned |
| 6 | Multi-branch support | Planned |
| 7 | Owner dashboard | Planned |
| 8 | Backup and sync architecture | Planned |
| 9 | Analytics and AI-assisted business insights | Planned |
| 10 | Production hardening | Planned |
Task definitions with acceptance criteria live in
.harness/tasks/.
Contributing
Read CONTRIBUTING.md first. The short version:
inspect -> plan -> implement -> test -> commit -> report
- Never commit to
main; work onagent/<name>/<task> - Run the suites before committing
- Use conventional commits, and explain why rather than what
- Never delete a working upstream feature to simplify an implementation
- Never commit credentials; this repository is public
Security
The threat model, the guarantees the code is built to keep, and the test that proves each one are in SECURITY.md.
Report a vulnerability through a private security advisory, not a public issue.
Acknowledgements
Mise-Kal is built on Mise by K M Shahriar Hossain, which established the security posture and design decisions this project inherits, in particular the rule that money is only ever computed server-side.
The MIT license is retained unchanged and the upstream copyright notice is preserved in LICENSE. Upstream remains configured as a git remote so improvements can be taken deliberately:
git fetch upstream
git log --oneline main..upstream/main # what upstream has that we do not
git merge upstream/main # when it is worth taking
License
MIT. Use it, change it, run it in your restaurant, sell services around it. Keep the copyright notice. See LICENSE.