Synaplan
기업을 위한 오픈소스 AI 플랫폼
견적 요청을 보내고 공급자 견적을 모아 부가세 없이 비교한 뒤 규칙 안에서 가장 나은 것을 승인한다
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.
비즈니스TypeScript · PLpgSQLGitHub @alharbiRay온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기RFQ
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
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.