尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Your next opportunity is out there. Wonder finds it.
Monorepo (npm workspaces). The web app lives in apps/web (Next.js 16, App Router, TypeScript, Tailwind v4).
npm install
npm run dev # http://localhost:3000
npm run check # lint + typecheck + tests + production build
npm run e2e -- e2e/auth.spec.ts --project=chromium # chosen Playwright specs against a local build (docs/testing/README.md)
npm run a11y # axe-core accessibility audit against a production build (needs Chromium: `npx playwright install chromium`, or set PW_CHROMIUM_PATH)
Copy apps/web/.env.example to apps/web/.env.local (and set the same variables in Vercel):
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SUPABASE_URL | Supabase project URL (SUPABASE_URL also accepted). |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY | Publishable key. Used by the browser for Supabase Auth (sign-in, sign-up, magic links) and by the server/proxy to verify session tokens. Data access itself stays service-role and server-side. |
SUPABASE_SERVICE_ROLE_KEY | Service role key. Server-side only — never exposed to the browser. RLS is on with no anon policies. |
SECRET_ENCRYPTION_KEY | 32+ char key that encrypts BYOK provider secrets at rest (AES-256-GCM). Required in production (WONDER_SECRET_KEY also accepted). |
WONDERJOBS_AI_KEY / WONDERJOBS_AI_MODEL / WONDERJOBS_AI_PROVIDER | Optional. API key (and model) that powers "WonderWorks AI" drafting for every account on the platform's bill. WONDERJOBS_AI_PROVIDER picks the vendor — anthropic (default, model claude-sonnet-5), openai (gpt-5-mini), or gemini (gemini-2.5-flash); WONDERJOBS_AI_MODEL overrides the default model for whichever vendor is chosen. Without a key for the chosen vendor, drafts are deterministic templates and the AI settings page says so; users can still connect their own key to any of the three. |
ADZUNA_APP_ID / ADZUNA_APP_KEY | Optional. Enables the Adzuna India job source (free developer tier). |
Without the Supabase variables the app runs in local-only mode (no sign-in; a browser is one tenant by cookie, with browser localStorage + in-memory server state; nothing is pre-filled). With them, visitors sign in with Supabase Auth, every store syncs per user to wonderjobs.app_state, provider keys go to wonderjobs.ai_provider_secrets (ciphertext only) and external actions are audited in wonderjobs.action_audit. All tables live in the dedicated wonderjobs schema. Schema source: apps/web/supabase/migrations/.
Runs search live public sources through GET /api/jobs/search (one request per source, server-side, cached 15 minutes): company career sites (public Greenhouse, Lever and Ashby boards listed in apps/web/src/server/jobs/providers.ts; applications go straight to the employer), Remotive, Jobicy, Remote OK, Himalayas, Arbeitnow (Europe, off by default) and Adzuna India (needs keys). Every posting is normalized by services/jobs/normalize.ts: skills, seniority, industry, work mode and salary are derived from the posting's own text with deterministic rules, then scored against the account's Career DNA. Wonder never submits an application on an employer's site; the apply stage hands off with materials ready and the tracker records the submission when the candidate marks it. There is no demo and no sample data in the product; tests use their own sample candidate and postings (apps/web/src/testing/sample, apps/web/e2e/fixtures/sample.ts).
/sign-in, /sign-up): email + password, a magic link, or Google, through Supabase Auth. Password fields have a reveal toggle. Forgot password (/forgot-password) emails a single-use recovery link that lands on /reset-password via /auth/callback?token_hash=…&type=recovery. Sessions live in cookies (wj-auth, chunked) so the route proxy and API routes can verify them; the proxy (apps/web/src/proxy.ts) guards /app/* and /onboarding, refreshes expired sessions and mirrors the verified user id into wj_user for client-side state namespacing. API routes answer 401 without a session.tenant_id = Supabase user id). Local copies are namespaced per user, so a shared device never mixes accounts; sign-out revokes the refresh token, clears cookies and drops that user's local copy.<origin>/auth/callback to Redirect URLs, otherwise confirmation, magic-link and password-reset emails point at localhost. "Confirm email" is on by default; the built-in SMTP only delivers to project members and is rate-limited (~2 mails/hour), so connect an SMTP provider (Authentication → SMTP settings) before real sign-ups, or turn confirmation off for password sign-ups.components/auth/GoogleButton.tsx) needs one-time setup:
https://<project-ref>.supabase.co/auth/v1/callback. Configure the OAuth consent screen (app name, support email) and publish it.<origin>/auth/callback.
Until step 2 is done the button shows "Google sign-in isn't switched on for this deployment yet".Schedules fire on the server, so a run happens at its time whether or not anyone has the app open.
GET /api/cron/scheduled-runs, invoked by Vercel Cron (vercel.json). Each tick asks the database which tenants have a schedule due (wonderjobs.due_schedule_tenants, migration 0004) and fires at most one schedule per tenant.CRON_SECRET environment variable in Vercel → Settings → Environment Variables (any long random string). Vercel sends it as Authorization: Bearer … on scheduled invocations. Without it the endpoint refuses to run — it will not stand open. To test by hand: curl -H "Authorization: Bearer $CRON_SECRET" https://<origin>/api/cron/scheduled-runs.vercel.json ships 0 2 * * * (a build with a more frequent schedule is rejected on Hobby). A daily cron is a safety net, not a scheduler: it cannot fire an 08:00 schedule at 08:00. So the browser keeps firing schedules at their proper time while a tab is open, and the two never double-fire — a schedule that already ran in the last 12 hours is skipped by both (domain/workflow/schedule.ts, and the same rule in the SQL).
On Pro, change the schedule to */15 * * * * and set CRON_INTERVAL_MINUTES=15. Any value at or below 60 tells the app the cron is punctual enough to own scheduling outright, and the browser stands down entirely. The default when the variable is unset is 1440 (daily).0006 is what lets the cron ask the database who needs one.A scheduled run that finds strong matches can nudge the candidate's phone or desktop, not just leave a line in the app.
npm run push:keys once (from apps/web) and add the three variables it prints —
VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT (a mailto: the push service can reach you
at, which RFC 8292 requires). Apply migration 0005 for the wonderjobs.push_subscriptions table.
Without the keys the feature simply doesn't appear — no broken toggle, no empty promise.server/push/webPush.ts implements RFC 8291 and RFC 8292 on
node:crypto, and the unit tests check it against RFC 8291's own published test vector./help is public: user guide, FAQ and an assistant that answers from the guide and links the matching section (POST /api/help/ask; uses the platform model when WONDERJOBS_AI_KEY is set). Linked as Get Help in the avatar menu.wonderjobs.contact_messages (migration 0003) with the signed-in tenant when there is one, and you can always read them in Supabase → Table Editor. To also get an email copy, set CONTACT_NOTIFY_EMAILS (comma-separated recipients) and your mailbox's SMTP settings, SMTP_HOST, SMTP_PORT, SMTP_USER and SMTP_PASS (for GoDaddy email: smtpout.secureserver.net, 465, the full address, its password); without them, notifications are logged server-side instead of emailed. Public company pages: /about, /privacy, /terms, /security, /cookies.The app wears the WonderWorks identity (docs/brand/wonderstar-identity.png, docs/WONDERWORKS_PLATFORM.md §9).
apps/web/public/brand/ holds it as SVG: the mark, the horizontal logo, the stacked logo with the tagline,
the app icon, and white-on-dark versions of both logos (tone="dark"). components/brand/WonderLogo.tsx
exposes WonderMark, WonderLogo and WonderLockup; the favicon and PWA icons are generated from the mark at
request time. Colours are the tokens at the top of apps/web/src/app/globals.css; the type is Poppins
(next/font/google, in app/layout.tsx). To change the mark or colours, edit and re-run:
pip install fonttools
python3 apps/web/scripts/brand-svg.py <dir with Poppins p400.woff and p600.woff> # the SVGs
node apps/web/scripts/brand-assets.mjs # the PNGs (email logo, icon mark)
WonderWorks is an installable PWA (app/manifest.ts, generated icons, public/sw.js). On Chrome/Edge/Android, "Install app" appears in the avatar menu once the browser decides the page qualifies; there's no such API on iOS Safari or Firefox, so people there use the browser's own "Add to Home Screen". The service worker deliberately caches nothing — this app is local-first and syncs real state already (store/remoteStorage.ts), so a caching layer on top would risk showing stale jobs or applications — it only shows a small offline page if a navigation's request fails outright.
Apply the schema once per Supabase project, either:
apps/web/supabase/migrations/ (in order) into Supabase → SQL Editor and run it, orDATABASE_URL="postgresql://…" npm run db:migrate (idempotent; records applied files in public._wonderjobs_migrations), orPOST /api/admin/migrate with Authorization: Bearer <SUPABASE_SERVICE_ROLE_KEY> and body { "databaseUrl": "postgresql://…" }. Runs only the bundled migrations; useful when your machine can't reach Postgres directly (Supabase's direct host is IPv6-only — the route falls back to the IPv4 pooler automatically).docs/testing/README.md — what runs where: fast CI on pull requests, the nightly E2E suite, E2E on your machine.docs/PROGRESS.md — high-level progress tracker: every epic and story, done or not, updated after each story.docs/IMPLEMENTATION_TRACKER.md — requirement-by-requirement status and deviations.