Synaplan
開源 AI 平台,供企業使用:自行託管的 AI 代理、符合 GDPR 的聊天機器人小工具、RAG 知識庫、WhatsApp 與 Outlook AI、MCP、多模型路由。Docker、Kubernetes 或託管於 Germ
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
Multi-tenant hospitality SaaS for Nepal's F&B industry. One React app on one Supabase project;
inventory, HR and POS are modules toggled per client, Crest Customization (sizes, extras and "No …"
choices on a dish, and build-your-own dishes walked step by step) is a paid add-on to POS, and Crest Suite Pro is the owner layer sold on top.
Repo: C:\crest-suite · E-drive backup: E:\CREST SUITE MANAGEMENT\檢視完整 README ↗Multi-tenant hospitality SaaS for Nepal's F&B industry. One React app on one Supabase project; inventory, HR and POS are modules toggled per client, Crest Customization (sizes, extras and "No …" choices on a dish, build-your-own dishes walked step by step, and combos sold at one price) is a paid add-on to POS, and Crest Suite Pro is the owner layer sold on top.
Repo: C:\crest-suite · E-drive backup: E:\CREST SUITE MANAGEMENT\
This file is a map, not a summary. Everything below points somewhere; nothing here restates what it points at. A line that describes rather than points is a line that goes stale without anyone noticing — that is how this file reached 1.8 million characters (see S666). Add new material to the file that owns the subject, and add a pointer here only if there is nowhere else.
npm start # Dev server → http://localhost:3000
npm run build # Production build (this is what Vercel runs)
npm run build:verify # Same build, for checking a change locally: clears the stale
# ESLint cache first and treats warnings as errors. Use this
# while `npm start` is running — the two share
# node_modules/.cache, and a dev server writing stale entries
# back is what produces build errors that are not in the code.
npm test # Jest suite
npm run check:docs # Rules-glob, rules-stub and CLAUDE.md-size checks. Also run by
# build:verify, so a rule that silently stopped loading fails a
# pre-push check rather than waiting for the next /doctor pass.
Env vars required:
REACT_APP_SUPABASE_URL
REACT_APP_SUPABASE_ANON_KEY
REACT_APP_USDA_API_KEY
REACT_APP_VAPID_PUBLIC_KEY
REACT_APP_SITE_ORIGIN
.env.example says what each one is for. REACT_APP_SITE_ORIGIN is the only one whose absence is
silent in dev and visible on paper: it is the production domain the printed Subscription Agreement
quotes as the evidence of what a signed contract incorporated by reference, so unset it prints
[SET REACT_APP_SITE_ORIGIN]/legal/terms rather than a plausible wrong domain, and
legalReadiness() refuses to call the documents publishable.
REACT_APP_SUPABASE_SERVICE_ROLE_KEY must never be set here or in Vercel — admin operations go through the admin-user-ops Supabase Edge Function instead (see S311).
| Path | What lives there |
|---|---|
src/modules/ | The product, one directory per area: ims/, hr/, pos/, admin/, dashboard/, ownerReport/ |
src/pages/ | Routes belonging to no single module — login, signup, pricing, help, settings, periods |
src/shared/ | Cross-module code: the scoped data-access layer, paged reads, error text, hooks |
src/components/ | Reusable UI, and the route guards every protected route stacks |
src/context/ | Auth, settings and theme providers. Singular context — the plural is a glob that has already rotted once |
src/utils/ | Standalone helpers: BS calendar, expression evaluator, subscription state, timeouts |
src/data/ | pricingPlans.js — the plans, their feature lists, and the SHIPPED prices. What a screen prints is resolvePricing() over the admin's Settings > Plan Pricing figures, reached as useSettings().pricing |
src/legal/ | The published Terms and Privacy Policy as markdown, their content hashes, and the registry the acceptance flow stamps. Edit a .md and re-run node scripts/hash-legal.mjs; a test fails if you forget |
supabase/ | Migrations and Edge Functions |
scripts/ | Build, audit and docs-check scripts, all reachable from package.json — except check-design-layers.mjs, run by hand after a theme change |
public/ | PWA shell and service worker, plus the second manifest for the staff app |
| Read this | For |
|---|---|
CLAUDE.md | Commands, stack, and the rules that must be known before you think to ask: the gate model, tenant isolation, the privilege invariants, the Supabase and BS-date traps, plus a pointer table into .claude/rules/. Loaded on every request of every session, so it holds nothing module-specific. |
.claude/rules/ | The same kind of rule, scoped by a paths: glob so it loads only when a matching file is open. Check npm run check:docs still passes after moving a file. |
.claude/skills/new-feature-checklist/ | The steps between "it works" and "it shipped". Invoke it before any new page, report or module feature. |
DESIGN.md | The design system as it actually exists in the code: tokens, scales, components, and the named rules that are already enforced. |
PRODUCT.md | Who buys this and why — users, positioning, brand personality, anti-references. |
IMS_TODO.md | The S756 IMS re-analysis: the owner's 28 decisions and the fix list by stage, with what is still open. |
POS_TODO.md | POS open items. Shipping something listed there closes the entry in the same commit. |
There is no ARCHITECTURE.md and there should not be. CLAUDE.md plus .claude/rules/ already is
that document, and a fourth copy of the gate model would drift from the three that exist.
.gitattributes normalises the tree to LF on commit, which is not the same
as the working tree being LF, and it is not. Measured 2026-09-09: MenuPricing.js is pure LF and
MenuRepricing.js, its sibling in the same directory, is pure CRLF; several .claude/rules/*.md
are mixed within one file. Git never reports this, because it normalises on the way in. So a
script that searches for a string containing \n silently matches nothing in half the repo —
assert your match count, and try \r\n before \n per replacement block rather than per file.
Do not "fix" a file by normalising it: that turns a 12-line diff into a whole-file rewrite.
Separately, do not write files with PowerShell's Set-Content -Encoding utf8 — it emits a BOM —
or read them with Get-Content, which reads ANSI and mangles every em dash. Use
[System.IO.File]::ReadAllText / WriteAllText, or a shell that is not PowerShell.CACHE_NAME bumped in public/service-worker.js and APP_VERSION in
src/shared/appVersion.js moved to match — appVersion.test.js fails when they disagree.
They had drifted nine bumps apart before S689 noticed, so the test is doing real work.
CLAUDE.md explains why a plain deploy is not enough.React.lazy and xlsx is imported inside the click handler, so the app fetches its own
code by hashed filename; Vercel replaces those files and the worker's activate deletes the
cached copies on the same event. src/shared/chunkReload.js recognises that failure and reloads
once — never more than once a minute, never while offline. Do not widen its patterns to catch
ordinary errors: a real crash misread as a stale deploy is hidden by the mechanism meant to
recover from it./ plus the one js/css pair named in the
build's asset-manifest.json. A route whose own chunk has not been fetched since the last deploy
still cannot render offline, and the error boundary says so in those words. No Supabase response
is ever cached; anything that must survive a lost connection needs its own IndexedDB store, which
is what Stock Count and POS order-taking have and nothing else does.POS_DECISIONS.md| POS shipped history and everything decided against, rationale intact. Read before proposing POS work. |
SUITE_TODO.md | Crest Suite (the owner layer) open items: the S848 re-analysis index, the owner decisions SU1–SU23 and what the live checks found. Evidence per ID in docs/suite-review-s848/. |
CHANGELOG/ | The session log, S023 to now, split by S-range. CHANGELOG/README.md is the index and holds the convention for adding an entry. |
DOCS-REMEDIATION.md | The open work order this layout came from. T1–T14, with mechanical acceptance criteria. |
CHANGELOG/CHANGELOG/README.mdpublic/manifest.json declared orientation: portrait-primary for a year with nothing to show
for it, because desktop Chrome ignores manifest orientation and that is where the app is
developed, demoed and run on a Windows till. Under display: standalone on Android — a count
tablet or an Android POS terminal added to the home screen — Chrome honours it, and it would have
locked a bolted-down landscape screen to portrait with nothing in the app able to override it
(S764). There are two manifests and they serve different devices: staff.webmanifest is
phones only and keeps portrait-primary. .claude/rules/staff-app.md has the rest.window.innerWidth < 768. iPad portrait is 768, so <
excluded it; landscape is 1024+, a 10" Android is ~800. No tablet had ever reached the one
screen in the product designed for a tablet, and it went unnoticed for as long as it existed
because the phone branch worked and the desktop branch worked (S765). Decide by INPUT METHOD:
matchMedia('(pointer: coarse)') reads the primary pointer, so a touchscreen laptop driven by a
mouse correctly keeps the dense screen — which a width test can never express. That is already
the documented rule for touch SIZING (@media (pointer: coarse), S603); this is the same rule
applied to choosing a whole layout. Keep a narrow-width clause only so a desktop browser dragged
narrow still behaves, and watch the media query as well as resize — a detachable tablet docked
to a keyboard changes its primary pointer without changing its width.localStorage on whatever phone opens the page, and a shared phone may belong to staff.
The guest QR menu and the public booking page are pinned to the default preset in
ThemeContext.js (isPinnedGuestSurface, S767) — pinned at the provider, so the lazy-load
fallback and the error screens are covered too, which a per-page palette never was. A new public,
customer-facing route joins PINNED_GUEST_PREFIXES.top: -<padding>; at top: 0
Chrome pushed it down over the content beneath it (measured on the guest choices sheet, S767).
The bottom edge did not behave symmetrically, so the guest sheets' pinned footers cover the
padding strip with a same-colour shadow instead. Measure it; do not reason it.PRESETS in
src/context/ThemeContext.js is what a user sees; DESIGN.md's frontmatter is the normative
copy the /impeccable hook checks literals against; its prose is what gets read; and
.impeccable/design.json is generated from both. Nothing renders from the lower three, so
nothing fails when they are wrong. Run node scripts/check-design-layers.mjs after moving one.