Synaplan
Open-source AI platform for companies: self-hosted AI agents, GDPR-compliant chatbot widget, RAG knowledge base, WhatsApp and Outlook AI, MCP, multi-model routing. Docker, Kubernetes or hosted in Germ
Send RFQs, collect supplier quotes by form or PDF, compare them excluding VAT and auto-approve the best one within your rules. Arabic and English.
BusinessTypeScript · PLpgSQLGitHub @alharbiRayOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
RFQ
Translation pending
Taken from the repository README
Loading the full README.
Read the full README ↗Arabic-first RFQ and quote comparison for Saudi businesses. Send a request for quotation, collect supplier quotes by web form or emailed PDF, compare them line by line excluding VAT, and auto-approve the best one within rules the buyer sets. Every step is recorded on a decision trail.
Screenshots: add
docs/screenshots/landing.png,compare.png,dashboard.pngandsupplier-form-ar.png. The live demo needs no sign-up: open the site and press Explore the live demo.
A Saudi contractor or office manager who buys every week runs the same loop by hand:
Nobody records why that supplier won, the spreadsheet is never the same twice, and the cheapest quote isn't always the right one.
Clarix turns the loop into one flow, in Arabic or English, right to left where it should be:
flowchart LR
subgraph Browser
B[Buyer app<br/>Next.js RSC + server actions]
S[Supplier form<br/>/quote/token]
V[Public site<br/>/ and /pricing]
end
subgraph Vercel
MW[Middleware<br/>2.5 s auth cap]
SA[Server actions]
WH["/api/webhooks/resend-inbound<br/>svix-verified"]
JR["/api/jobs/process<br/>CRON_SECRET"]
end
subgraph Supabase
DB[(Postgres + RLS<br/>jobs queue, audit_events)]
ST[(Storage: quote-pdfs)]
AU[Auth]
end
G[Gemini Flash<br/>zod-validated JSON]
R[Resend<br/>email in/out]
B --> MW --> SA --> DB
S --> SA
V --> SA
R -- inbound PDF --> WH -- enqueue --> DB
SA -- enqueue + waitUntil --> JR
JR -- claim_jobs skip locked --> DB
JR --> G
JR --> R
SA --> G
SA --> ST
MW --> AU
GH[GitHub Actions every 15 min<br/>+ Vercel daily cron] --> JR
auth.uid() through RLS. The demo
account is read-only at the database.waitUntil, and swept every 15 minutes. claim_jobs uses
for update skip locked, so runs never collide.More detail: docs/ARCHITECTURE.md, docs/SECURITY.md,
docs/DECISIONS.md (75 numbered decisions), and DESIGN.md.
| Choice | Why |
|---|---|
| Next.js 14 App Router | Server components read through RLS with the user's cookie, and server actions keep every write on the server. One deployable runs the buyer app, the public supplier form, webhooks and the job runner. |
| Supabase (Postgres, RLS, Auth, Storage) | Tenancy enforced by the database, not by where clauses someone forgets. Postgres also hosts the job queue and rate limits, so there is no extra infrastructure. |
Gemini Flash via @google/genai | Reads scanned PDFs natively, handles Arabic, supports structured output, and has a free tier for a pre-revenue product. The model is GEMINI_MODEL, which matters: see the challenges below. |
| Resend | Outbound invitations and POs plus inbound email in one service. The inbound webhook makes "reply with your PDF" work. |
| next-intl, cookie-based | Full Arabic/English without locale URLs, so every existing link (emails, supplier tokens, webhooks) keeps working. The language follows the buyer across devices through profiles.locale. |
| Tailwind and shadcn/ui (Radix) | Accessible primitives. Logical properties (ms, ps, start, end) make RTL a direction flip, not a second stylesheet. |
| Vitest and PGlite | 701 tests. Migrations and RLS are tested against real Postgres in WASM, with no Docker and no hosted database. Gemini and Resend are always mocked. |
| Vercel and GitHub Actions | Hobby-tier hosting. CI runs typecheck, lint, tests, the offline evals and the build on every PR, and a free schedule sweeps the job queue. |
1. Background work that silently never happened. The supplier form, manual entry and PDF
confirmation called maybeAutoGenerateRecommendation() without await. On a laptop that works.
On a serverless platform the function freezes as soon as the response is sent, so the
recommendation, the auto-approval and the PO email were dropped at random, with no error
anywhere.
waitUntil. A cron sweep finishes anything a frozen instance
left behind.2. The middleware that took the whole site down. Middleware called
supabase.auth.getUser() on every matched request with no timeout. When Supabase Auth was slow,
every page hung until Vercel killed it with MIDDLEWARE_INVOCATION_TIMEOUT (504), including the
public supplier form and the webhook. The fix:
fetch, plus a Promise.race./login with an explanation.3. Race conditions in auto-approval. Two suppliers submitting the last quotes at the same moment could both create a recommendation, and both runs could send a PO. The award insert now gates every side effect, and the database enforces the uniqueness.
4. A model that quietly disappeared. The extraction evals (below) failed every case one
morning. gemini-2.5-flash had started returning 404 "no longer available to new users". The
default moved to gemini-3.5-flash (the newest models were overloaded that day), and
GEMINI_MODEL now changes it without a deploy of code. Because evals run against the real API on
demand, this was caught before any buyer was affected.
5. Arabic that wasn't really Arabic.
next/font places a size-matched Arial fallback right after Geist in the font
stack. Arial has Arabic glyphs, so every Arabic word rendered in Arial instead of IBM Plex Sans
Arabic. Removing the generated fallback fixed it.6. Security without a security team. Every server action checks the session on the server. On top of that:
Details: docs/SECURITY.md.
22 deliberately messy synthetic quotes in Arabic, English and mixed (VAT-inclusive prices, Arabic-Indic digits, line totals only, emails, tables, other currencies). Each runs through the production prompt, schema and repair path.
| Field | Rule-based baseline | Gemini (gemini-3.5-flash) |
|---|---|---|
| Unit price (excl. VAT) | 46.3% | 100% (32/32) |
| Currency | 100% | 100% |
| Delivery days | 90.9% | 94.1% |
| Warranty period | 100% | 100% |
Method and full results: docs/EVALS.md.
npx tsx evals/extraction/run.ts # offline baseline
RUN_LIVE_EVALS=true npx tsx --env-file=.env.local evals/extraction/run.ts --write # real Gemini
Requirements: Node 22, a Supabase project (hosted or supabase start), and optionally Gemini and
Resend keys.
npm install
cp .env.example .env.local # fill in the values; names are documented in the file
supabase/migrations/0001 to 0020 in order (supabase db push, or paste
them into the SQL editor). They are additive.npm run dev
Open http://localhost:3000. Signed out, you see the landing page; sign up to reach the app.ALLOW_SEED=true npm run seed:demo # local Supabase
ALLOW_SEED=true DEMO_SEED_PROJECT_REF=<ref> npm run seed:demo # a hosted project, on purpose
npm run typecheck && npm run lint && npm test && npm run build
Without a Gemini key, the AI features fail gracefully and quotes can be entered by hand. Without Resend, nothing is emailed. Tests never call either.
docs/PROGRESS.md: every phase, its PR, and how it was verified.docs/plans/: the plan for each phase.docs/METRICS.md: how savings and scorecards are calculated.PRODUCT.md and DESIGN.md: product truth and the design system.