Skip to content

NRI waitlist landing, lifelike cursor, signup notifications & deploy hardening#2

Merged
beepboop2025 merged 6 commits into
mainfrom
feat/nri-waitlist-landing
Jun 21, 2026
Merged

NRI waitlist landing, lifelike cursor, signup notifications & deploy hardening#2
beepboop2025 merged 6 commits into
mainfrom
feat/nri-waitlist-landing

Conversation

@beepboop2025

Copy link
Copy Markdown
Owner

Pre-launch interest-capture flow for the OperatorOS NRI pivot, plus the deploy fixes needed to ship it on a VPS. Goal: a polished, working demo that generates interest before building the full subscription machinery.

Frontend

  • CustomCursor — bespoke lifelike pointer (trailing ring + magnetic snap to interactive elements, morph/press states, data-cursor-label), 60fps via direct DOM writes, with reduced-motion & touch fallback. Mounted globally.
  • Landing — CTAs now open a Textura waitlist modal (WaitlistProvider) with explicit submit states (no silent failures); demo-login hint + SEO/OG meta.
  • Dashboard — admin-only "Export leads" button (authenticated blob download).

Backend

  • Waitlist — public idempotent POST /api/waitlist, admin GET, and GET /export.csv; model, schema, route, Alembic migration 004_waitlist.
  • Notifications — in-app bell (notification_service) + Celery email/Telegram delivery, dispatched off the request path via BackgroundTasks (broker-failure-safe).
  • Seed — relocated to app/seed.py (python -m app.seed) so it's baked into the backend image; India/NRI demo data fills every dashboard surface.

Fixes / hardening

  • config.py: CORS_ORIGINS NoDecode — fixes pydantic-settings 2.x boot crash on a fresh build.
  • requirements.txt: pin bcrypt==4.0.1 — passlib 1.7.4 incompatibility that breaks all logins on a fresh build.
  • prod compose: close Postgres/Redis ports to the internet; pass DOMAIN to Caddy (was issuing TLS for the wrong domain).
  • Makefile: make deploy (build → migrate → seed, idempotent).

🔒 Security (please review)

  • CSV formula injection — waitlist fields are public/attacker-supplied; export.csv prefixes values starting with = + - @ to neutralize Excel/Sheets formula execution.
  • Telegram HTML injection — interpolated user values are html.escape()d before sending with parse_mode=HTML (flagged by automated review, fixed in this branch).
  • Lead export and admin GET are server-enforced via require_api_key_or_admin; the UI role gate is defense-in-depth only.

Verification

  • ✅ backend: all touched files compile, app assembles, waitlist routes register, migration chain linear (single head 004)
  • ✅ frontend: tsc --noEmit clean, vite build succeeds
  • ⚠️ Not verified in sandbox (no Docker/DB/SMTP): live image builds, real DB writes, actual email/Telegram sends — exercise on first make deploy

Deploy

export DOMAIN=… && make deploy (set .env from .env.example first). Demo login seeded: demo@operatoros.in / OperatorOS#2026.

🤖 Generated with Claude Code

beepboop2025 and others added 6 commits June 21, 2026 17:25
…ploy hardening

Pre-launch interest-capture flow for the OperatorOS NRI pivot, plus the
deploy fixes needed to ship it on a VPS.

Frontend
- CustomCursor: bespoke lifelike pointer (trailing ring + magnetic snap,
  morph/press states, reduced-motion & touch fallback), mounted globally
- Landing: CTAs route to a Textura waitlist modal (WaitlistProvider) with
  explicit submit states; demo-login hint + SEO/OG meta
- Dashboard: admin-only "Export leads" button (authenticated blob download)

Backend
- Waitlist: public idempotent POST + admin GET + CSV export
  (formula-injection guarded); model, schema, route, migration 004
- Notifications: in-app bell (notification_service) + Celery email/Telegram
  delivery (HTML-escaped), dispatched off the request path via BackgroundTasks
- Demo seed relocated to app/seed.py (runnable as `python -m app.seed`)

Fixes / hardening
- config: CORS_ORIGINS NoDecode (pydantic-settings 2.x boot crash)
- requirements: pin bcrypt==4.0.1 (passlib 1.7.4 incompatibility)
- prod compose: close Postgres/Redis ports, pass DOMAIN to Caddy
- Makefile: `make deploy` (build + migrate + seed, idempotent)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Automates the full server-side deploy: Docker install, firewall, nip.io
domain fallback (real HTTPS without buying a domain), .env generation with
fresh secrets, then build + migrate + seed. User only needs to provision
the box; everything after `git clone` is one command.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_redis_url_without_password asserted CELERY_* defaults but didn't set them,
so they fell through to CI service-container env vars (localhost) and failed.
Pass all three URLs explicitly. Fixes the failing backend pytest job.
Migration 001 hardcoded several non-native Enum columns as VARCHAR(11), but
the ORM models size them to their longest value. The mismatch made valid
values un-insertable (caught by a real seed run against Postgres):
  - clients.entity_type        11 -> 15  (private_limited / public_limited)
  - compliance_tasks.task_type 11 -> 17  (professional_tax)
  - compliance_tasks.status    11 -> 12  (under_review)

Without this, the app cannot onboard a Pvt Ltd client or set a task to
\"under review\" — a pre-existing defect in core functionality.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- seed: add 3 compliance tasks due within the next 7 days so the dashboard
  \"Upcoming Deadlines\" panel is populated instead of empty
- fix dashboard \"Recent Activity\": the panel read activity.items, but the API
  returns typed buckets (recent_queries / recent_documents / recent_computations).
  Merge them into one time-sorted ActivityItem feed so seeded queries/computations
  actually render (frontend<->API shape mismatch)
- vite: add preview-server /api proxy (changeOrigin:false) so the prod build can
  be run locally against the dockerized backend, mirroring prod nginx Host behaviour

Verified live: both panels populate against the real seeded backend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds 20 tests (suite 159 -> 179, all green):
- test_waitlist: POST create, idempotency-on-email (no duplicate row), email
  normalisation, admin-only list/export auth, CSV export + formula-injection
  guard, and _csv_safe unit cases
- test_notification_service: notify_staff fans out one notification per active
  staffer in its own session, swallows DB errors without raising (signup stays
  safe), enqueue_signup_delivery is broker-failure-safe, create_notification adds
- test_migrations: DB-free guard that the Alembic graph has a single head and a
  linear chain including 004_waitlist and 005_fix_enum_lengths

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@beepboop2025 beepboop2025 merged commit 9d57454 into main Jun 21, 2026
2 checks passed
@beepboop2025 beepboop2025 deleted the feat/nri-waitlist-landing branch June 21, 2026 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant