Author: Manus AI Last Updated: February 2026 Version: 1.0
Surviving to Thriving is a real-time classroom check-in tool designed for instructors who want a quick pulse on how their students are feeling. Each student opens a link on their phone or laptop, enters their name, picks an emoji, and slides a 1–10 scale from "barely surviving" to "really thriving." The teacher watches responses stream in live on a password-protected dashboard, complete with group statistics and outlier alerts.
The application was built with the Marymount Manhattan College brand palette in mind, using the official Pantone 286 C blue (#0033A0) as the primary accent color, paired with warm gold highlights and the DM Serif Display typeface for an elegant, institutional feel.
The application follows a monolithic full-stack architecture where the frontend and backend are bundled into a single deployable unit. In production, the Express server serves the pre-built React SPA alongside the API and WebSocket endpoints.
┌─────────────────────────────────────────────────────┐
│ Render (Free Tier) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────┐ │
│ │ React SPA │ │ Express + │ │ Socket │ │
│ │ (Vite build)│◄──│ tRPC API │──►│ .io │ │
│ └──────────────┘ └──────┬───────┘ └────────┘ │
│ │ │
└────────────────────────────┼────────────────────────┘
│ MySQL protocol (SSL)
┌────────▼────────┐
│ TiDB Cloud │
│ (Free Starter) │
└─────────────────┘
The table below summarizes each layer and its role.
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | React 19, Tailwind CSS 4, Vite 7 | Single-page application served as static files |
| UI Components | shadcn/ui (Radix primitives), Framer Motion | Accessible, animated interface components |
| Emoji Picker | emoji-mart v5 | Full searchable emoji library with categories |
| Client Routing | Wouter | Lightweight client-side routing |
| API Layer | tRPC 11 over Express 4 | Type-safe RPC between client and server |
| Real-time | Socket.io v4 | WebSocket push for live submission updates |
| ORM | Drizzle ORM 0.44 | Type-safe SQL query builder and schema management |
| Database | TiDB Cloud Starter (MySQL-compatible) | Persistent storage for sessions and submissions |
| QR Codes | qrcode (npm) | Server-side QR code generation as data URIs |
The React SPA defines four primary routes, each serving a distinct role in the classroom workflow.
| Route | Component | Access | Description |
|---|---|---|---|
/ |
Home.tsx |
Public | Landing page with branding, feature overview, and a link to the teacher dashboard |
/s/:code |
StudentSubmit.tsx |
Public (via unique link) | Student submission form — name, emoji picker, 1–10 slider |
/teacher/login |
TeacherLogin.tsx |
Public | Password entry form that gates access to the dashboard |
/teacher |
TeacherDashboard.tsx |
Password-protected | Live dashboard showing all submissions, statistics, QR codes, and session management |
The student submission URL includes a unique 8-character code (e.g., /s/Qgy92taA) generated by the nanoid library each time the teacher creates a new session. This ensures that every class check-in has its own isolated link, preventing cross-session contamination.
The application uses three custom tables in a MySQL-compatible database (TiDB Cloud). The schema is defined in drizzle/schema.ts and managed through Drizzle Kit migrations.
This table stores each check-in session that a teacher creates. Each session has a unique code that becomes part of the shareable student URL.
| Column | Type | Description |
|---|---|---|
id |
int (auto-increment, PK) |
Surrogate primary key |
code |
varchar(32) (unique) |
Short alphanumeric code used in the URL path /s/{code} |
label |
varchar(255) |
Optional human-readable name (e.g., "Monday 9am Section") |
isActive |
int (default 1) |
Whether the session is currently accepting submissions (1 = active, 0 = closed) |
createdAt |
timestamp |
When the session was created |
Each student response is stored as a row in this table, linked to its parent session by sessionId.
| Column | Type | Description |
|---|---|---|
id |
int (auto-increment, PK) |
Surrogate primary key |
sessionId |
int (FK to survey_sessions.id) |
Which session this submission belongs to |
studentName |
varchar(255) |
The name the student entered |
emoji |
varchar(32) |
The emoji character the student selected |
rating |
int |
The 1–10 scale value |
ipAddress |
varchar(64) |
Client IP address captured at submission time |
createdAt |
timestamp |
When the submission was recorded |
This table is part of the Manus OAuth scaffold and is not actively used by the core classroom check-in functionality. It exists to support the platform's built-in authentication system. The teacher dashboard uses a simpler password-based authentication mechanism instead.
All API endpoints are defined as tRPC procedures in server/routers.ts. The tRPC layer provides end-to-end type safety — the same TypeScript types are shared between server and client, eliminating the need for manual API documentation or schema validation on the frontend.
Teacher access is protected by a simple password comparison. The password is read from the TEACHER_PASSWORD environment variable at server startup. There is no user registration or OAuth flow for the teacher — just a single shared password.
| Procedure | Type | Input | Description |
|---|---|---|---|
teacher.login |
Mutation | { password: string } |
Validates the password and returns a token if correct |
teacher.verify |
Query | { token: string } |
Checks whether a stored token is still valid |
The "token" returned by teacher.login is the password itself, stored in the browser's localStorage. Every subsequent teacher-only request includes this token for verification. This is a deliberately simple scheme appropriate for a single-teacher classroom tool.
These procedures handle creating, listing, activating, deactivating, resetting, and deleting survey sessions. All require the teacher token.
| Procedure | Type | Input | Description |
|---|---|---|---|
session.create |
Mutation | { label?, token } |
Creates a new session with a random 8-character code |
session.list |
Query | { token } |
Returns all sessions ordered by creation date (newest first) |
session.getByCode |
Query | { code } |
Looks up a session by its URL code (used by the student page) |
session.activate |
Mutation | { id, token } |
Re-opens a session for submissions |
session.deactivate |
Mutation | { id, token } |
Closes a session so no more submissions are accepted |
session.reset |
Mutation | { id, code, token } |
Deletes all submissions for a session and emits a WebSocket reset event |
session.delete |
Mutation | { id, token } |
Permanently deletes a session and all its submissions |
| Procedure | Type | Input | Description |
|---|---|---|---|
submission.submit |
Mutation | { sessionCode, studentName, emoji, rating } |
Records a student submission, emits it via WebSocket, and checks for outliers |
submission.listBySession |
Query | { sessionCode, token } |
Returns all submissions for a session (teacher-only) |
When a student submits a response, the server performs an outlier check if there are at least 3 submissions in the session. The algorithm calculates the mean and standard deviation of all ratings, then flags the new submission as an outlier if it deviates by more than 2 standard deviations from the mean and the rating is extreme (1–2 or 9–10). When an outlier is detected, the server attempts to send a push notification to the project owner via the notifyOwner helper. This notification channel depends on the Manus platform's built-in notification API and will silently fail if the required environment variables (BUILT_IN_FORGE_API_URL, BUILT_IN_FORGE_API_KEY) are not configured.
The application uses Socket.io to push live updates from the server to the teacher's browser without requiring page refreshes or polling. The WebSocket server is initialized in server/socket.ts and attached to the same HTTP server that runs Express.
The communication flow works as follows. When the teacher opens a session on the dashboard, the browser establishes a Socket.io connection to the server at the /api/ws path. The client then emits a join-session event with the session code, which causes the server to add that socket to a named room (session:{code}). When a student submits a response, the server saves it to the database and then broadcasts a new-submission event to all sockets in that session's room. The teacher's dashboard receives this event and appends the new submission to the displayed list without any manual refresh. Similarly, when the teacher clicks "Reset," the server emits a session-reset event that clears the submission list on any connected client.
The client-side WebSocket logic is encapsulated in a custom React hook at client/src/hooks/useSocket.ts, which manages the connection lifecycle and provides onNewSubmission and onSessionReset callback registrations.
| Event | Direction | Payload | Purpose |
|---|---|---|---|
join-session |
Client → Server | Session code string | Subscribe to a session's room |
new-submission |
Server → Client | Full submission object | Push a new student response to the dashboard |
session-reset |
Server → Client | None | Notify the dashboard that submissions were cleared |
The application relies on two external services for hosting and data persistence.
Render hosts the Node.js application as a Web Service on the free tier. The build process installs pnpm, runs pnpm install to fetch dependencies, then executes pnpm build which uses Vite to bundle the React frontend and esbuild to bundle the Express server. In production, the single node dist/index.js process serves both the API and the static frontend files.
Render's free tier has an important operational characteristic: the service spins down after approximately 15 minutes of inactivity. The next incoming request triggers a cold start that takes 30–60 seconds. For classroom use, the teacher should visit the dashboard a minute or two before class to wake the server. Upgrading to Render's paid tier ($7/month) eliminates cold starts entirely.
The Render dashboard is accessible at dashboard.render.com. The service name is surviving-to-thriving and the public URL is https://surviving-to-thriving.onrender.com.
TiDB Cloud Starter provides a free MySQL-compatible database with 25 GiB of storage and 250 million Request Units per month. The application connects via the standard MySQL protocol over SSL. The connection string is stored in the DATABASE_URL environment variable on Render.
The TiDB Cloud console is accessible at tidbcloud.com. The cluster is named "Cluster0" and is located in the US East (N. Virginia) region. The database name is surviving_to_thriving.
The source code is hosted in a GitHub repository at matthewstraub/surviving-to-thriving. Render is configured to auto-deploy from the main branch — any push to main triggers a new build and deployment automatically.
The application requires the following environment variables to be set on Render. These are configured in the Render dashboard under the service's Environment section.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | Full MySQL connection string including SSL parameter, pointing to the TiDB Cloud database |
TEACHER_PASSWORD |
Yes | The password used to access the teacher dashboard |
JWT_SECRET |
Yes | A random string used for signing session cookies (part of the Manus auth scaffold) |
NODE_ENV |
Yes | Must be set to production so the server serves the built static files |
NODE_VERSION |
Recommended | Set to 22.13.0 to match the development environment |
The following variables are part of the Manus platform scaffold but are not required for the core check-in functionality when hosting on Render.
| Variable | Effect if Missing |
|---|---|
VITE_APP_ID |
Manus OAuth login button will not work (not needed — teacher uses password auth) |
OAUTH_SERVER_URL |
Same as above |
VITE_OAUTH_PORTAL_URL |
Same as above |
BUILT_IN_FORGE_API_URL |
Outlier push notifications will not send (app still works normally) |
BUILT_IN_FORGE_API_KEY |
Same as above |
OWNER_OPEN_ID |
Admin role auto-assignment will not work (not needed) |
The table below maps each important file to its role in the application, organized by layer.
| File Path | Purpose |
|---|---|
drizzle/schema.ts |
Database table definitions (users, survey_sessions, submissions) |
server/db.ts |
Database query helpers (CRUD operations for sessions and submissions) |
server/routers.ts |
All tRPC API procedures (teacher auth, session management, submissions) |
server/socket.ts |
WebSocket server initialization and event emission functions |
server/_core/index.ts |
Express server entry point, Vite middleware, and Socket.io registration |
server/_core/env.ts |
Environment variable loading and validation |
server/_core/notification.ts |
Push notification helper for outlier alerts |
client/src/App.tsx |
React router configuration (all four routes) |
client/src/pages/Home.tsx |
Landing page with branding and teacher dashboard link |
client/src/pages/StudentSubmit.tsx |
Student submission form with emoji picker and slider |
client/src/pages/TeacherLogin.tsx |
Password entry form for teacher access |
client/src/pages/TeacherDashboard.tsx |
Live dashboard with stats, submissions list, QR code, and session management |
client/src/hooks/useSocket.ts |
Custom React hook wrapping Socket.io client connection |
client/src/index.css |
Global styles, MMM brand color variables, and emoji picker width overrides |
client/index.html |
HTML shell with Google Fonts (DM Serif Display, Inter) |
server/survey.test.ts |
Vitest test suite for backend procedures (19 tests) |
RENDER_DEPLOYMENT_GUIDE.md |
Step-by-step deployment instructions for Render + TiDB Cloud |
package.json |
Dependencies, scripts, and build configuration |
To change the teacher password, go to the Render dashboard, navigate to the surviving-to-thriving service, click Environment in the left sidebar, find the TEACHER_PASSWORD variable, update its value, and save. Render will automatically redeploy the service with the new password. The change takes effect in 2–3 minutes.
Any changes pushed to the main branch of the GitHub repository will trigger an automatic redeployment on Render. The typical workflow is to edit the code locally (or via GitHub's web editor), commit, and push. Render will detect the new commit, rebuild the application, and swap to the new version with zero downtime.
The TiDB Cloud console provides a SQL Editor for running ad-hoc queries. If you need to manually inspect or clean up data, navigate to tidbcloud.com, select your cluster, and open the SQL Editor. Some useful queries include the following.
To view all sessions and their submission counts:
SELECT s.id, s.code, s.label, s.isActive, s.createdAt,
COUNT(sub.id) AS submissionCount
FROM survey_sessions s
LEFT JOIN submissions sub ON sub.sessionId = s.id
GROUP BY s.id
ORDER BY s.createdAt DESC;To manually delete old sessions and their submissions:
DELETE FROM submissions WHERE sessionId IN (
SELECT id FROM survey_sessions WHERE createdAt < DATE_SUB(NOW(), INTERVAL 90 DAY)
);
DELETE FROM survey_sessions WHERE createdAt < DATE_SUB(NOW(), INTERVAL 90 DAY);Render provides a Logs tab in the service dashboard that shows real-time server output. Look for lines prefixed with [Socket] for WebSocket connection events, [Database] for database connection issues, and [Notification] for outlier alert delivery status.
On Render's free tier, the server hibernates after 15 minutes of inactivity. To avoid the 30–60 second cold start delay during class, either visit the app URL a couple of minutes beforehand, or set up a free external monitoring service like UptimeRobot to ping the URL every 14 minutes. Alternatively, upgrading to Render's Starter plan ($7/month) keeps the service running continuously.
If you modify the database schema in drizzle/schema.ts, you need to generate and apply migrations. Run pnpm db:push locally with the DATABASE_URL environment variable pointing to your TiDB Cloud database. This command generates SQL migration files in the drizzle/ directory and applies them to the database. Commit the generated migration files to Git so they are tracked in version control.
Run pnpm update locally to update packages to their latest compatible versions, test the application, and push to main. Key dependencies to keep an eye on include socket.io and socket.io-client (must be the same major version on both sides), drizzle-orm and drizzle-kit (must be compatible versions), and emoji-mart, @emoji-mart/react, and @emoji-mart/data (must all be from the same major release).
The following table summarizes the typical workflow for using the app in a classroom setting.
| Step | Who | Action |
|---|---|---|
| 1 | Teacher | Visit https://surviving-to-thriving.onrender.com and click "Teacher Dashboard" |
| 2 | Teacher | Enter the teacher password and sign in |
| 3 | Teacher | Type a session label (e.g., "Wednesday 2pm") and click "New Session" |
| 4 | Teacher | Click "Show QR Code" and project it on screen, or click "Copy Link" and share it |
| 5 | Students | Scan the QR code or open the link on their devices |
| 6 | Students | Enter their name, pick an emoji, slide the scale, and tap "Submit Check-In" |
| 7 | Teacher | Watch responses appear in real time on the dashboard |
| 8 | Teacher | Review the group average, range, standard deviation, and any flagged outliers |
| 9 | Teacher | Before the next class, click "Reset" to clear submissions (or create a new session) |
The teacher dashboard is protected by a shared password stored as an environment variable on the server. This password is never exposed to the client except as a localStorage token after successful login. Student submissions are public endpoints — anyone with the session link can submit. The session codes are random 8-character strings generated by nanoid, making them impractical to guess. IP addresses are captured with each submission for audit purposes but are not displayed on the dashboard. The database connection uses SSL encryption as required by TiDB Cloud. All traffic between the browser and Render is encrypted via HTTPS, which Render provides automatically with managed TLS certificates.
This documentation was generated for the Surviving to Thriving project hosted at https://surviving-to-thriving.onrender.com with source code at github.com/matthewstraub/surviving-to-thriving.