Overview
Lisa is a cloud-based AI productivity platform for professionals who want a single assistant that knows their tasks, events, and projects — and can act on them in natural language. Unlike conventional to-do apps, Lisa puts a conversational AI front-and-centre: users type (or will soon speak) requests like “schedule my client call for Thursday at 3pm” or “what’s overdue this week?” and Lisa responds, creates, and organises on their behalf. The platform pairs that AI chat interface with a structured workspace model — workspaces, projects, tasks, and recurring events — backed by a persistent PostgreSQL database, phone-call reminders via Twilio, and a Stripe-managed subscription tier system.
Pixelvise designed and engineered Lisa end-to-end — from brand identity and visual language to the React frontend, Node.js API, database schema, AI integration, and production deployment on Netlify with a Railway-hosted PostgreSQL backend.







The Problem
Productivity tools have spent the last decade adding more structure. The result is friction: users spend more time managing their system than doing the work. A specific set of gaps drove the design of Lisa:
- Task tools don’t talk back. You can create a task in Todoist, but you can’t ask it “what should I do this afternoon?” and get a reasoned answer.
- Calendar and tasks are still siloed. Scheduling a task requires opening a second app and manually bridging the two.
- Reminders are dumb. A push notification fires once, gets dismissed, and is gone. Nobody calls you back if you miss it.
- Multi-context work is unsupported. Freelancers and small teams juggle client workspaces that share no structure and no memory.
- Admin overhead is invisible. SaaS operators have no dashboard to monitor plans, users, and usage — so billing problems go unnoticed.
What Lisa Does
Conversational AI workspace management
The centrepiece of Lisa is a streaming Gemini 2.5 Flash chat panel. Users give natural language instructions — create tasks, schedule events, list overdue items, summarise the week — and the AI parses intent using a structured function-calling layer (tools.js) that translates messages directly into database writes. The AI has full context of the active workspace: its projects, tasks, events, and recent history.
Workspaces, projects, and tasks
Work is organised in three tiers. Workspaces are top-level containers (one per subscription by default, addable at $20/month). Each workspace holds Projects, and each project holds Tasks with priority, due date, start time, subtasks, notes, and optional recurrence rules. A full-screen modal handles task creation and editing. Tasks can also be created directly from chat without opening a modal.
Full calendar view
A built-in full-screen calendar renders tasks and events on a drag-and-drop monthly grid. Events support location, notes, and recurrence. The calendar is accessible from both the right drawer’s mini-view and a full-screen overlay, giving users a time-aware picture of the workspace.
Smart reminders with phone callbacks
Lisa does not just push notifications — it calls. Twilio powers both SMS and voice-call reminders that fire via a server-side cron job (cron-job.org, every minute). Users set reminder preferences per notification type: before_event, task_reminder, daily_digest, weekly_digest, and more. The cron endpoint evaluates pending reminders, fires them, and injects the reminder as a chat message so the conversation thread captures all activity.
Right drawer — live schedule and task list
A collapsible right drawer surfaces today’s events, upcoming tasks, and a mini calendar. Events are grouped by date label (Today, Tomorrow, day name). Tasks show due date and project. Both are toggleable as done directly from the drawer without opening a full modal.
Library — workspace-wide search and browse
The Library drawer provides a searchable, filterable view across all projects, tasks, and events in the active workspace. It is the fastest way to find something without asking the AI.
Billing, plans, and Stripe
Three subscription tiers — Pro ($20/month), Team ($49/month), Enterprise ($99/month) — are defined in the database and managed through a checkout flow using Stripe. The checkout page renders live plan data from the API. PDF invoices are generated server-side and downloadable from the user’s billing tab.
Admin portal
A separate /admin route, protected by role check, gives operators a full management panel: user list with plan and status badges, per-user subscription management and ban controls, plan CRUD, promo code creation, platform settings (API keys, SMTP, Twilio config), and system statistics.
Our Role at Pixelvise
- Product strategy — feature scoping, tier design, and the decision to make AI chat the primary surface rather than a sidebar widget
- Brand & visual design — the dark-mode-first glass morphism aesthetic, Apple-inspired colour palette (#5E5CE6, #BF5AF2, #007AFF), animated SVG blob identity mark
- UI/UX design — three-panel layout (sidebar, chat, drawer), mobile bottom-nav, bottom-sheet modals, responsive grid across all breakpoints
- Frontend engineering — React 19, Vite, custom CSS design system (no Tailwind), Framer Motion, Lucide React, streaming chat rendering
- Backend engineering — Node.js/Express API (
api/index.js, 1 400+ lines), JWT auth, bcrypt, PostgreSQL on Railway, Twilio, Stripe webhooks, invoice PDF generation - AI integration — Google Gemini 2.5 Flash with streaming, structured tool-calling for workspace mutation, per-day quota tracking
- Deployment — Netlify (SPA + serverless functions proxy), Railway (PostgreSQL), cron-job.org (reminder scheduler)
- Admin portal — full operator dashboard with stats, user management, billing, and settings
The Stack
| Layer | Technology |
|---|---|
| Framework | React 19 + Vite 7 |
| Styling | Custom CSS design system (CSS variables, no utility framework) |
| Animation | Framer Motion 12 |
| AI model | Google Gemini 2.5 Flash (@google/generative-ai) |
| Auth | JWT (jsonwebtoken) + bcryptjs, localStorage token |
| Database | PostgreSQL on Railway (pg pool) |
| Backend | Node.js/Express API — single handler (api/index.js) |
| Reminders | Twilio Voice + SMS, server-side cron via cron-job.org |
| Payments | Stripe (Checkout Sessions, Customer Portal, Webhooks) |
| File storage | Netlify Blobs (avatar uploads) |
| Deployment | Netlify (frontend + API proxy) |
| Native shell | Capacitor 8 (iOS target, in progress) |
| Icons | Lucide React |
| Date handling | date-fns 4 |
Why this stack?
Single-file API handler. The entire backend lives in api/index.js — one 1 400-line file that handles auth, workspaces, tasks, events, chat, billing, cron, and admin. This is deliberate: for a product at this stage, a monolithic handler is easier to reason about, deploy atomically, and refactor than a micro-service graph. Netlify Functions proxy it in production.
No UI framework. Lisa’s visual language — dark glass cards, animated gradients, Apple-system colour tokens — is specific enough that Tailwind or shadcn would add abstraction without benefit. Every pixel is a hand-written CSS variable. This also keeps the bundle lean: there are no component library tree-shaking surprises.
Gemini 2.5 Flash with structured tool-calling. The model’s structured output support lets tools.js define a typed function schema (createTask, updateEvent, listTasks, setReminder, etc.) that the model calls directly. This keeps AI output predictable and avoids the prompt-parsing fragility of open-ended completions.
Key Engineering Challenges
1. Streaming AI responses into a React chat list
Problem: Gemini’s streaming API yields tokens asynchronously. Naively appending tokens to state caused a re-render per token — dozens per second — which made the chat stutter on lower-end devices.
Solution: Token accumulation is handled in a streamingRef (useRef) outside React state. The ref is updated on every token; state is only updated via a batched setMessages that maps over the message list to find the streaming entry by ID. This reduces renders to React’s own batching cadence rather than one-per-token.
2. Cross-browser reminder delivery without a persistent server process
Problem: Lisa’s reminder system needs to fire at specific times regardless of whether the user has a browser tab open. A client-side setInterval approach fails entirely when the tab is closed or the device sleeps.
Solution: All reminder firing was moved server-side. A cron-job.org job POSTs to /api/cron/reminders every minute, authenticated by a secret header. The endpoint queries the reminders table for unfired rows where trigger_at <= now(), fires each via Twilio, marks them fired, and injects the reminder as a chat message into the user’s conversation. The client polls for new messages every 10 seconds and surfaces them in the chat thread — the user sees the reminder in context even if they weren’t in the app when it fired.
3. Multi-workspace data isolation with a flat PostgreSQL schema
Problem: All user data (tasks, events, projects, chat history) lives in a shared PostgreSQL database. Naively querying without workspace scoping would leak data across workspaces and users.
Solution: Every data table carries both user_id and workspace_id foreign keys. The API always resolves the authenticated user from the JWT before any query, and every SELECT/INSERT/UPDATE/DELETE is filtered by user_id. Workspace allocation is tracked in a separate workspace_allocations table tied to a subscription; the API validates that the requested workspace_id belongs to the calling user before any mutation.
4. Recurring events and tasks in a relational schema
Problem: Storing recurring rules (daily, weekly, monthly, custom RRULE) without exploding the rows — while still allowing the UI to display the next N occurrences — required a clean separation between the canonical rule and its projections.
Solution: Recurrence is stored as a JSON column (recurrence) on tasks and events. A pure-function expandRecurring utility in src/services/recurrence.js takes the raw rows and a date range, and projects virtual instances — each with a synthetic _virtualId and _originalId. These virtual rows render in the sidebar and calendar without any additional DB writes. Completing a virtual occurrence writes back to the original row via _originalId.
5. PDF invoice generation without a headless browser
Problem: Generating a branded, printable invoice PDF on demand is typically solved with Puppeteer or a paid service. Neither was acceptable at this scale — Puppeteer adds ~350 MB to the deployment and SaaS PDF APIs add per-invoice cost.
Solution: The /api/billing/invoice/:id/pdf endpoint returns a self-contained HTML document with an embedded block and an inline window.print() call in a . When the user clicks "Download PDF", the browser opens the URL, the page auto-triggers the print dialog, and the user saves as PDF via the native browser PDF renderer. No server-side rendering, no dependencies — a 40-line HTML string does the job.
Design Philosophy
Lisa's visual identity is built around one concept: ambient intelligence.
- Dark glass surfaces — every card uses
background: rgba(255,255,255,0.04)withbackdrop-filter: blur(24px), placing content inside a layered, luminous depth field rather than a flat white panel - Apple system palette —
#5E5CE6(indigo),#BF5AF2(purple),#007AFF(blue),#30D158(green),#FF375F(red),#FF9F0A(amber) — borrowed from Apple's dynamic colour system, where the hue carries semantic meaning - Animated SVG blob identity mark — the Lisa blob is a three-layer SVG: a background glow layer, a main shape layer with a slow organic morph animation, and a highlight layer. Each rendered instance gets unique gradient IDs to prevent collisions in React trees. It appears in the sidebar header, loading states, and auth pages
- Typography — system-ui body, no external font load; headings use weight and tracking rather than a custom display face; letter-spacing
-0.02emon key labels gives a polished, native-app feel - Mobile-first — below 768px the three-panel layout collapses to a single-column view with a bottom navigation bar (Menu / Chat / Calendar / Tasks). The sidebar and right drawer slide in as full-height overlays with a dark scrim dismiss. Modals become bottom sheets with rounded top corners
Results
| Metric | Value |
|---|---|
| Lines of JavaScript/JSX | ~18 000 |
| API surface (routes handled) | 60+ REST endpoints |
| Build size (minified + gzip) | ~420 KB JS |
| PostgreSQL tables | 13 (users, plans, subscriptions, workspaces, projects, tasks, events, reminders, chat_messages, invoices, promos, usage_logs, admin_settings) |
| AI requests tracked per day | Per-user quota with DB-backed counter |
| Deployment target | Netlify + Railway PostgreSQL |
Reflections
Building Lisa revealed the sharp edge of conversational UI: the moment the AI makes a mistake — creates the wrong task, misparses a date — users lose trust faster than with any traditional form. Every tool-call schema had to be hardened iteratively: field types, fallbacks, and sanitisation were added in response to real misparses. The sanitizeText function in db-api.js that strips markdown artifacts from AI-generated titles was an early lesson.
The decision to move all reminders server-side (rather than a client setInterval) was the right call, but it added operational complexity: the cron job is now a critical dependency. Losing the cron-job.org integration would silently stop all reminders. Operational visibility — logs, dead-letter queues, alerting — is the most important next engineering investment.
Packaging Lisa as a Capacitor-wrapped iOS native app is already underway. The web layer works as-is; the main challenge is bridging Twilio's voice SDK for in-app audio when the device screen is locked. If you'd like to discuss the project or commission something similar, get in touch at [email protected].