SuDo is a focused issue tracker and workspace command deck for solo builders, student developers, hackathon teams, and small technical teams. It combines workspace-scoped project management with a compact, dark productivity interface inspired by the clarity and speed of tools such as Linear and Raycast.
The current product includes authenticated workspaces, role-based member management, invitations, projects, assignable issues, comments, labels, search and filters, persisted saved views, activity history, workspace settings, owner-only safe workspace deletion, keyboard-driven commands, and per-user demo workspace seeding. The frontend uses a layered near-black canvas, compact command-deck navigation, restrained acid-lime actions, dense issue tables, contextual drawers, and a responsive product-preview landing page.
Source: github.com/hsusul/SuDo
Live demo: Add the verified Vercel or custom-domain URL here after
20260607033000_saved_views is deployed and post-deploy QA passes.
Demo video: Add the final 60-90 second recording here after capturing it against a sanitized demo workspace.
For a recruiter or interviewer:
- Open the live application and choose
Explore the demo workspace. - Sign up or sign in through Clerk.
- On first-run onboarding, choose
Create demo workspace. - Open the seeded issue pipeline, then inspect labels, comments, activity,
saved views, member settings, and
Cmd/Ctrl + K.
The demo workspace is created for the authenticated user. It is not a shared public database and does not reset or mutate another user's work. Repeating the action returns that user's existing demo workspace instead of duplicating it.
Local demo setup:
cp .env.example .env.local
npm install
npm run prisma:migrate
npm run devSign in once so Clerk creates the local User record, set
DEMO_SEED_USER_EMAIL in the ignored .env.local, then run:
npm run db:seed
npm run db:verify-demoThe CLI seed targets only that existing synced user. Do not configure
DEMO_SEED_USER_EMAIL in production unless an intentional administrative seed
is being performed.
- Multi-tenant authorization is enforced server-side for workspace-scoped reads and writes rather than relying on client state.
- Collaboration includes owner/admin/member permissions, hashed expiring invitations, assignees, and activity history.
- Reliability work includes request-scoped auth caching, read-pure listing, concurrency-safe issue numbers, mutation guardrails, loading/error boundaries, structured server logs, CI, and browser tests.
- The deployment path is designed around Vercel, Clerk, Prisma migrations, and pooled/direct Neon Postgres connections.
- Next.js App Router
- TypeScript
- Tailwind CSS v4
- shadcn/ui
- Clerk authentication
- Prisma 7 ORM
- PostgreSQL, with Neon Postgres as the default production recommendation
- Vercel deployment
- Vitest for unit tests
- Playwright and Clerk testing utilities for public and opt-in authenticated E2E tests
flowchart TB
subgraph Client["Client"]
Browser["React 19 interface<br/>Tailwind UI + command menu"]
end
subgraph Runtime["Vercel runtime"]
App["Next.js App Router<br/>Server Components + Server Actions"]
Services["Domain services<br/>Zod validation + authorization"]
end
subgraph Identity["Identity"]
Clerk["Clerk authentication"]
end
subgraph Data["Data"]
Prisma["Prisma ORM"]
Neon["Neon Postgres"]
end
subgraph Quality["Delivery and verification"]
Actions["GitHub Actions CI"]
Vitest["Vitest unit/service tests"]
Playwright["Playwright browser tests"]
Verifiers["Database relationship verifiers"]
end
Browser --> App
App --> Clerk
App --> Services
Services --> Prisma
Prisma --> Neon
Actions --> Vitest
Actions --> Playwright
Vitest --> Services
Playwright --> Browser
Verifiers --> Neon
Technical request path:
- React and the App Router render the public site and protected workspace UI.
- Clerk authenticates the request; local user synchronization is cached per request.
- Server Actions validate input and call domain services.
- Services recheck workspace membership and role before every scoped read or mutation.
- Prisma persists tenant-scoped records in Neon Postgres.
- Vercel hosts the application; GitHub Actions, Vitest, Playwright, and safe database verifiers cover the delivery path.
Security boundaries include server-side tenant authorization, hashed expiring invite tokens, exact-name destructive confirmation, redacted structured logs, security headers, and a report-only CSP compatible with Clerk.
Core data relationships:
- A
Usercan belong to many workspaces throughWorkspaceMember. - A
Workspaceowns projects, issues, statuses, labels, comments, invitations, saved views, and activity records. - Projects provide issue keys and scope issue numbering.
- Issues can have an assignee, labels, comments, and append-only activity.
- Saved views persist project and filter state within a workspace.
Authorization roles:
owner: full workspace use, member management, invitations, and deletion.admin: full workspace use plus invitations and supported member management.member: product use without member administration or workspace deletion.
Every privileged operation rechecks workspace membership and role on the server. UI visibility is a convenience, not the authorization boundary.
- Responsive product landing page with a CSS-based MacBook product preview.
- Premium Clerk sign-in and sign-up wrappers at
/sign-inand/sign-up. - Protected workspace application at
/app. - Graceful local placeholder mode when Clerk or database env vars are missing.
- Prisma Client generation.
- Prisma schema for users, workspaces, memberships, invitations, projects, issues, labels, comments, activity logs, and demo reset metadata.
- Server-side helper to map the current Clerk user to a local
Userrecord when Clerk andDATABASE_URLare configured. - Workspace onboarding for authenticated users with a configured database.
- Workspace creation that also creates an owner
WorkspaceMemberrow. - Sidebar workspace switcher for changing workspaces and creating additional workspaces after onboarding.
- Responsive workspace command deck with project, issue, view, and settings navigation.
- Centralized server-side workspace authorization helpers for workspace-scoped reads and writes.
- Workspace roles for owners, admins, and members with centralized permission checks.
- Workspace member list and management controls in Settings.
- Hashed, expiring workspace invitation tokens with pending, accepted, revoked, and expired states.
- Local invite-link acceptance flow that verifies the signed-in user's email.
- Dedicated project page at
/app/projectswith project listing, creation, rename/edit, and archive inside the selected workspace. - Server-side project helpers that enforce workspace membership before project reads and writes.
- Dedicated issue page at
/app/issueswith compact rows, creation, edit, and archive inside the selected project. - Issue status and priority editing with server-side project/workspace authorization.
- Workspace-member assignees with an explicit unassigned state in issue forms, rows, and drawers.
- URL-backed issue detail drawer using
?issue=<issueId>for refreshable focused editing. - Issue comments inside the detail drawer with chronological list, author display, timestamp, and composer.
- Compact issue activity history for creation, edits, status, priority, assignee, label, and comment events.
- Workspace labels that can be created, attached to issues, removed from issues, and shown in the issue list and detail drawer.
- Project-scoped custom dropdown filters by status, priority, and label.
- Lightweight issue search by title, description, issue key, and issue number.
- Built-in Views page at
/app/viewswith shortcuts into the existing issue filters. - Workspace-shared persisted saved views with create, open, rename, delete, and tenant-scoped authorization.
- Command menu opened with
Cmd/Ctrl + K, including navigation, create, search, save-view, member, and workspace-switch commands when applicable. - Settings page at
/app/settingswith workspace context and a dedicated danger zone. - Owner-only workspace deletion requiring the exact workspace name, with server-side authorization and deterministic redirect behavior.
- Plain numeric navigation counts plus compact project, issue, and view metadata.
- Reusable panels, page headers, empty states, issue badges, dialogs, buttons, and form controls.
- Add public shared demo reset after the authenticated demo workspace path is deployed and verified.
- Add transactional email delivery for workspace invitations.
- Add notifications only after real delivery and preference semantics are defined.
- Replace per-instance mutation limits with a distributed production limiter.
- Promote the report-only CSP after production violations have been reviewed.
- Add optional error reporting after a provider and privacy policy are chosen.
Install dependencies:
npm installCreate a local environment file:
cp .env.example .envYou can also use .env.local for local secrets. .env.local is ignored by git and is loaded by both Next.js and Prisma CLI commands.
Start the app:
npm run devOpen http://localhost:3000.
The app can build without Clerk or database secrets. Auth routes and protected behavior show setup placeholders until Clerk environment variables are configured.
GitHub Actions runs npm ci, lint, typecheck, unit tests, the production build,
and the repository check script for pushes to main and pull requests. These
checks intentionally run without Clerk or database secrets. CI supplies a
non-secret localhost database URL only so Prisma can generate its client; it
does not connect to a database.
Database verifier scripts are not part of CI because they inspect a configured Postgres database. Run them only from a trusted local or deployment environment where the intended database variables are loaded. The in-process mutation rate limits are basic per-instance abuse guardrails; production-wide enforcement across multiple Vercel instances should use Vercel Firewall or a shared rate limit store.
Current verification baseline:
- 113 Vitest unit/service tests across 27 files.
- 4 public Playwright smoke tests, including security-header assertions.
- 4 authenticated Playwright setup/workflow tests that are intentionally skipped unless dedicated Clerk test accounts are configured.
- Secret-free GitHub Actions for install, lint, typecheck, unit tests, build, and the repository check script.
SuDo uses three browser QA paths:
- Interactive visual review with the Codex in-app Browser or Playwright MCP when available.
- Repeatable public smoke checks with the Playwright test runner.
- Opt-in authenticated Playwright workflows using Clerk test accounts and reusable
storageState.
Run public browser smoke tests:
npm run test:e2eRun headed:
npm run test:e2e:headedOpen Playwright UI:
npm run test:e2e:uiCapture safe public screenshots under test-results/:
npm run qa:screenshotsIf browsers are missing on a new machine:
npx playwright install chromiumThe default E2E suite covers /, /sign-in, /sign-up, signed-out /app
protection, and responsive landing-page smoke checks. It does not hardcode
Clerk credentials or bypass auth.
Authenticated tests use @clerk/testing with a Clerk development instance.
Create dedicated test users in that instance, then add their email addresses to
your ignored .env.local:
E2E_CLERK_USER_EMAIL="owner-test@example.com"
E2E_CLERK_MEMBER_EMAIL="member-test@example.com"Run the authenticated suite:
npm run test:e2e:authE2E_CLERK_MEMBER_EMAIL is optional. Without it, the second-account invite,
acceptance, role-change, and tenant-isolation test is skipped. The owner suite
still covers app smoke, workspace/project/issue workflows, assignees, labels,
comments, activity, filters, saved views, command shortcuts, archiving, and
exact-name deletion.
Clerk auth state is written to playwright/.clerk/owner.json, which is ignored
by git. Never commit storage-state files, test credentials, or screenshots with
private data. GitHub Actions remains secret-free and runs the non-authenticated
verification path only.
Create a Clerk application, then add these values to .env:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=""
CLERK_SECRET_KEY=""
NEXT_PUBLIC_CLERK_SIGN_IN_URL="/sign-in"
NEXT_PUBLIC_CLERK_SIGN_UP_URL="/sign-up"
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/app/issues"
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/app/issues"
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL="/app/issues"
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL="/app/issues"
# Optional local-only authenticated Playwright users
E2E_CLERK_USER_EMAIL=""
E2E_CLERK_MEMBER_EMAIL=""When both NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY are present:
/appis protected bysrc/proxy.ts.- Clerk sign-in/sign-up components render.
- Server helpers can read the current Clerk user.
/appcan sync the Clerk user into the local PrismaUsertable whenDATABASE_URLis also configured.
Do not commit .env files or real secret values.
Prisma is configured for PostgreSQL. Use local Postgres, Neon, Supabase Postgres, or another Postgres-compatible database.
Add a valid DATABASE_URL to .env.local or .env:
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public"If your provider gives you both pooled and direct connection strings, keep DATABASE_URL as the runtime URL and add the direct, non-pooled URL for migrations:
DIRECT_DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public"Generate Prisma Client:
npm run prisma:generateRun the first migration only after DATABASE_URL points at a real development database:
npm run prisma:migrateThe collaboration vertical slice is introduced by migration
20260607023000_collaboration_vertical_slice. Persisted saved views are
introduced by migration 20260607033000_saved_views. Deploy committed
migrations before using these features in an existing environment:
npm run prisma:migrate:status
npm run prisma:migrate:deployPrisma 7 reads environment variables through prisma.config.ts. This repo loads .env first and .env.local second, so local secrets in .env.local can override shared defaults without being committed. Shell-provided environment variables still take precedence for one-off commands. If DIRECT_DATABASE_URL is present, Prisma CLI migration commands use it; otherwise they fall back to DATABASE_URL.
Open Prisma Studio:
npm run prisma:studioRun the seed placeholder:
npm run db:seedThe default seed command is safe and does not fake auth users. To create a demo workspace for an existing synced Clerk user, sign in once so a local User row exists, set DEMO_SEED_USER_EMAIL to that user's email in .env.local, then run:
npm run db:seedThis creates or returns one demo workspace for that existing user. It does not reset or mutate other users' workspaces.
Detailed feature setup and database verifier guides
Once Clerk and DATABASE_URL are configured:
- Sign in through
/sign-in. - Open
/app. - SuDo creates or updates the local
Userrow from the Clerk user. - If the user has no workspace memberships,
/appshows the workspace onboarding form. - Choose either a blank workspace or a seeded demo workspace.
- Creating a blank workspace creates both
Workspaceand ownerWorkspaceMemberrows. - Creating a demo workspace also creates realistic projects, issues, labels, comments, and default issue statuses for the current user.
/app/issues?workspace=<slug>shows the workspace-aware shell.
Projects can be created, edited, listed, and archived from /app/projects. Basic issues can be created, edited, and archived from /app/issues.
After a signed-in user has at least one workspace:
- Open
/app,/app/projects, or/app/issues. - Use the workspace section in the sidebar to switch between workspaces.
- Use the plus button beside
Workspaceto create another workspace. - Creating a workspace validates the name, creates a
Workspace, creates an OWNERWorkspaceMemberfor the current user, and opens the new workspace on/app/projects?workspace=<slug>.
Workspace selection is URL-backed with the workspace=<slug> query param. Switching workspaces intentionally resets project, issue, search, and filter params so data from one workspace is not shown in another workspace context.
The v1 demo strategy is authenticated per-user demo seeding:
- A user signs in through Clerk.
- SuDo syncs the Clerk identity into the local
Usertable. - If the user has no workspaces, onboarding offers
Create demo workspace. - The server action creates one
isDemoworkspace for that user with 3 projects, several issues across statuses/priorities, labels, label attachments, and comments. - If clicked again after a demo exists, the seed helper returns the existing demo workspace instead of endlessly duplicating data.
This avoids fake Clerk users in production and keeps demo data scoped to the authenticated user. Public shared demo reset is intentionally deferred.
Once Clerk, DATABASE_URL, and migrations are configured:
- Sign in and open
/app/projects. - Select or create a workspace.
- Use the Projects panel to create a project with a name and optional description.
- Edit a project row to rename it or change the description.
- Archive a project to remove it from the active project list.
Project actions are server actions. They validate input, derive workspace access on the server, and never rely on client-side workspace checks alone.
Once Clerk, DATABASE_URL, migrations, and at least one active project are configured:
- Sign in and open
/app/issues. - Select a workspace and active project.
- Use the Issues panel to create an issue with a title, optional description, status, and priority.
- Click an issue row to open the detail drawer.
- Double-click an issue row to edit the issue title, description, status, or priority in the centered edit modal.
- Archive the issue to remove it from the active issue list and close the drawer.
Issue actions are server actions. They validate input, derive the workspace from the selected project or issue record on the server, and never trust client-provided workspace access.
Once Clerk, DATABASE_URL, migrations, and at least one active issue are configured:
- Sign in and open
/app/issues. - Create or select an active project.
- Create or open an active issue.
- Use the issue detail drawer comment composer to post a comment.
- Refresh the page with the
?issue=<issueId>URL and confirm the comment persists.
Comment actions are server actions. They validate input, load the issue first, derive workspace access on the server, and never trust client-provided workspace access.
Once Clerk, DATABASE_URL, migrations, and at least one active issue are configured:
- Sign in and open
/app/issues. - Create or select an active project.
- Create or open an active issue.
- Use the issue detail drawer Labels section to create a workspace label.
- Attach an existing workspace label to the issue.
- Remove an attached label from the issue when it no longer applies.
- Confirm labels appear on both the issue row and the issue detail drawer.
Label actions are server actions. They validate input, derive workspace access on the server, and ensure labels can only attach to issues from the same workspace.
Once Clerk, DATABASE_URL, migrations, and at least one active issue are configured:
- Sign in and open
/app/issues. - Select a workspace and active project.
- Use the compact filter bar to filter by status, priority, or workspace label.
- Search by issue title, description, issue key, or issue number.
- Open an issue drawer while filters are active and confirm the filter params remain in the URL.
- Use Clear to return to the full active issue list for the selected project.
Issue filters are URL-backed with status, priority, label, and q query params. The issue query derives the project workspace on the server, verifies workspace membership, and ensures label filtering only applies to labels in the current workspace.
Once Clerk, DATABASE_URL, migrations, and at least one active project are configured:
- Sign in and open
/app/views. - Select a workspace from the sidebar.
- Select a project context if the workspace has multiple active projects.
- Use built-in shortcuts for all active issues, recently updated issues, statuses, high/urgent priority, and labels.
- From
/app/issues, apply filters and chooseSave viewor runSave Current Viewfrom the command menu. - Name the workspace-shared view, then open it from
/app/views. - The creator, owner, or admin can rename or delete the saved view.
- Confirm opening the view restores its project and URL-backed status, priority, label, and search filters.
Saved views are project-scoped and workspace-shared. The service verifies that the project and optional label belong to the active workspace. Members can create views; only the creator or a workspace manager can rename or delete them. Generated shortcut views remain separate from persisted views.
Press Cmd + K on macOS or Ctrl + K elsewhere to open the command menu.
Type to filter commands, use the arrow keys to move, press Enter to run the
selected command, and press Escape to close.
Available commands depend on the current workspace and route:
- Go to Issues, Projects, Views, or Settings.
- Create an issue or project.
- Search issues.
- Save the current issue filters as a view.
- Open workspace members.
- Switch workspaces.
Commands that require a selected project or current issue list are omitted when that context is unavailable.
Once Clerk, DATABASE_URL, migrations, and at least one workspace are configured:
- Sign in and open
/app/settings. - Confirm the selected workspace name, slug, and membership role render.
- Confirm the account context and workspace metadata match the active workspace.
- For an owned workspace, open the danger-zone delete dialog.
- Confirm deletion remains disabled until the exact workspace name is entered.
- Confirm a non-owner cannot invoke the server-side deletion helper.
- After deletion, confirm the user is redirected to another authorized workspace or onboarding when none remain.
Workspace deletion removes the selected workspace and its workspace-scoped records through the existing Prisma relations. Authorization is enforced again on the server; the dialog confirmation is a UX safeguard, not the permission boundary.
Workspace collaboration uses three roles:
| Role | Workspace use | Invite members | Remove members | Change roles | Delete workspace |
|---|---|---|---|---|---|
| Owner | Yes | Yes | Other owners, admins, and members | Admin/member roles | Yes |
| Admin | Yes | Yes | Members | No | No |
| Member | Yes | No | No | No | No |
The workspace owner cannot remove themselves through the member management UI.
Server-side checks also prevent deleting a workspace unless the current
membership role is owner.
Invitation flow:
- An owner or admin opens
/app/settingsand creates an invitation for an email address with the admin or member role. - SuDo stores only a SHA-256 hash of the random invitation token. Invitations expire after seven days and can be revoked while pending.
- Because transactional email is not configured, the settings panel returns a local invite link for the manager to share directly.
- The recipient signs in through Clerk and opens the link.
- SuDo accepts the invitation only when the signed-in account email matches the invited email, then creates the workspace membership transactionally.
Issues can be assigned to any active member of the issue's workspace or left unassigned. Removing a member clears their issue assignments and records the change in each affected issue's activity history.
Activity history is append-only product history, not a notification system. It currently records issue creation and edits, status, priority, assignee, label, comment, project, invitation, and membership changes. Issue-specific events are shown in the issue drawer.
Current collaboration limitations:
- Invitation email delivery is not implemented; invite links must be shared manually.
- There are no notifications, mentions, presence, or real-time updates.
- Owner transfer and promotion to owner are not exposed in v1.
- Rate limiting is an in-process guardrail and is not shared across Vercel instances.
SuDo uses small, quiet count badges for numeric context:
- Sidebar Projects shows active project count for the selected workspace.
- Sidebar Issues shows active issue count for the selected workspace.
- Project rows show active issue count for each project.
- Issue list header shows active or matching result count.
- Views cards show matching issue counts for status, priority, and label shortcuts.
Counts are derived server-side after workspace authorization and only include non-archived records unless a section explicitly says otherwise.
After Clerk keys, DATABASE_URL, and migrations are configured:
- Run
npm run dev. - Open
/. - Open
/sign-inand confirm the real Clerk sign-in UI renders. - Open
/sign-upand confirm the real Clerk sign-up UI renders. - Open
/appin a signed-out browser session and confirm it redirects to/sign-in. - Sign in or sign up manually.
- Return to
/app. - If this Clerk user has no workspace membership, create a workspace.
- Confirm
/app?workspace=<slug>shows the workspace-aware shell. - Verify database rows with Prisma Studio or safe Prisma queries: one local
User, oneWorkspace, and one ownerWorkspaceMemberfor the new workspace.
For command-line smoke tests, send a browser-style HTML request when checking Clerk protection:
curl -H "Accept: text/html" -I http://localhost:3000/appClerk may return a 404 for non-document requests from tools like default curl; that does not mean the browser redirect is broken.
After manually signing in and creating a workspace, verify the persisted auth/workspace foundation:
npm run db:verify-workspaceThe script prints non-secret counts and relationship checks only. Expected local output after a successful first workspace flow:
userCountis at least1.workspaceCountis at least1.membershipCountis at least1.ownerMembershipCountis at least1.duplicateClerkUserGroupsis0.duplicateMembershipPairGroupsis0.allMembershipsLinkExistingRowsistrue.allWorkspacesHaveOwneristrue.
After manually creating or editing projects, verify the persisted project foundation:
npm run db:verify-projectsThe script prints non-secret project summaries and relationship checks only. Expected local output after successful project work:
workspaceCountis at least1.duplicateProjectKeyGroupsis0.allProjectsLinkExistingRowsistrue.- Active and archived project counts match the browser state.
After manually creating, editing, or archiving issues, verify the persisted issue foundation:
npm run db:verify-issuesThe script prints non-secret issue summaries and relationship checks only. Expected local output after successful issue work:
workspaceCountis at least1.projectCountis at least1.duplicateIssueKeyGroupsis0.duplicateProjectIssueNumberGroupsis0.allIssuesLinkExpectedWorkspaceistrue.- Active and archived issue counts match the browser state.
After manually adding comments from the issue detail drawer, verify the persisted comment foundation:
npm run db:verify-commentsThe script prints non-secret comment summaries and relationship checks only. Expected local output after successful comment work:
workspaceCountis at least1.projectCountis at least1.allCommentsLinkCorrectWorkspaceIssueAndAuthoristrue.commentCountand active issue/comment summaries match the browser state.
After manually creating or attaching labels from the issue detail drawer, verify the persisted label foundation:
npm run db:verify-labelsThe script prints non-secret label summaries and relationship checks only. Expected local output after successful label work:
workspaceCountis at least1.duplicateWorkspaceLabelSlugGroupsis0.duplicateIssueLabelGroupsis0.allIssueLabelsLinkCorrectWorkspaceistrue.labelCount,issueLabelCount, and label summaries match the browser state.
After manually creating a few issues with different statuses, priorities, or labels, verify the filter foundation:
npm run db:verify-filtersThe script prints non-secret counts and relationship checks only. Expected local output after successful filter work:
workspaceCountis at least1.invalidFiltersIgnoredistrue.labelFilterStaysInWorkspaceistrue.- Filter counts are numeric when an active project with issues exists, otherwise
null.
After opening /app/views, verify the built-in views foundation:
npm run db:verify-viewsThe script prints non-secret counts and generated link checks only. Expected output after successful views work:
workspaceCountis at least1.activeProjectCountis at least1.generatedViewLinkCountis greater than0.allViewLinksUseSelectedWorkspaceistrue.allViewLinksUseSelectedProjectistrue.allViewLinksTargetIssuesRouteistrue.
After opening /app/settings, verify the settings foundation:
npm run db:verify-settingsThe script prints non-secret membership checks only. Expected output after successful settings work:
workspaceCountis at least1.membershipCountis at least1.ownerMembershipCountis at least1.allWorkspacesHaveOwneristrue.
After opening the project, issue, or views pages, verify the count foundation:
npm run db:verify-countsThe script prints non-secret count summaries only. Expected output after successful count work:
workspaceCountis at least1.activeProjectCountis numeric.activeIssueCountis numeric.projectCountsMatchWorkspaceTotalsistrue.allProjectCountsAreNonNegativeistrue.
After creating a demo workspace from onboarding or by running the safe seed command for an existing synced user, verify the demo shape:
npm run db:verify-demoThe script prints non-secret demo workspace counts and relationship checks only. Expected output after a successful demo seed:
demoWorkspaceCountis at least1.- Each demo workspace has projects, issues, labels, and comments.
ownerMembershipCountis at least1.allIssuesLinkExpectedProjectWorkspaceistrue.allIssueLabelsLinkCorrectWorkspaceistrue.allCommentsLinkCorrectWorkspaceIssueAndAuthoristrue.duplicateProjectIssueNumberGroupsis0.
- If
/appthrows thatclerkMiddleware()was not run, confirm the Clerk proxy is atsrc/proxy.tsfor thissrc/appproject layout. - If
/appshows setup messaging, confirm both Clerk keys andDATABASE_URLare present in.env.localor.env. - If
npm run prisma:migratecannot seeDATABASE_URL, confirmprisma.config.tsstill loads.envand.env.local. - If
npm run prisma:migratereaches Neon but times out on an advisory lock, confirm migrations are usingDIRECT_DATABASE_URLwith a direct, non-pooled connection string. - If Prisma connects but no onboarding rows appear, make sure you completed sign-in and submitted the workspace form in the browser; the database is not seeded with real user workspaces yet.
npm run dev
npm run lint
npm run typecheck
npm run test
npm run check
npm run test:e2e
npm run test:e2e:auth
npm run test:e2e:headed
npm run test:e2e:ui
npm run qa:screenshots
npm run build
npm run prisma:generate
npm run prisma:migrate
npm run prisma:migrate:status
npm run prisma:migrate:deploy
npm run prisma:studio
npm run db:seed
npm run db:verify-workspace
npm run db:verify-projects
npm run db:verify-issues
npm run db:verify-comments
npm run db:verify-labels
npm run db:verify-filters
npm run db:verify-demo
npm run db:verify-views
npm run db:verify-settings
npm run db:verify-counts
./scripts/check.shThe production target is Vercel for the Next.js app, Neon Postgres for hosted Postgres, and Clerk for production authentication. Do not deploy with local development secrets, and do not run destructive Prisma commands against production.
- GitHub repository:
https://github.com/hsusul/SuDo - Vercel account.
- Neon account.
- Clerk account.
- A clean local check before pushing:
npm run check- Create a new Neon project for SuDo production.
- Copy the pooled Neon connection string into
DATABASE_URL. - Copy the direct, non-pooled Neon connection string into
DIRECT_DATABASE_URL. - Keep the pooled URL for runtime traffic and the direct URL for Prisma migrations.
In Clerk:
- Create or switch to a production Clerk application.
- Copy the production publishable key into
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY. - Copy the production secret key into
CLERK_SECRET_KEY. - Configure sign-in URL:
/sign-in. - Configure sign-up URL:
/sign-up. - Configure after sign-in URL:
/app/issues. - Configure after sign-up URL:
/app/issues. - Configure fallback redirect URLs to
/app/issuesif Clerk asks for them. - After the first Vercel deploy, add the Vercel URL and any custom domain to Clerk allowed origins/domains.
The most common production auth bug is a Clerk redirect or allowed-domain mismatch.
In Vercel:
- Import
https://github.com/hsusul/SuDo. - Framework preset:
Next.js. - Install command:
npm install. - Build command:
npm run build. - Output directory: use the Next.js default.
The repo does not need a postinstall Prisma hook because npm run build
already runs prisma generate through prebuild.
Add these in Vercel Project Settings for Production. Add Preview values only if you plan to test preview deployments with a preview Clerk/database setup.
NEXT_PUBLIC_APP_URL="https://your-vercel-domain.vercel.app"
DATABASE_URL="postgresql://USER:PASSWORD@HOST/DATABASE?sslmode=require"
DIRECT_DATABASE_URL="postgresql://USER:PASSWORD@HOST/DATABASE?sslmode=require"
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_live_..."
CLERK_SECRET_KEY="sk_live_..."
NEXT_PUBLIC_CLERK_SIGN_IN_URL="/sign-in"
NEXT_PUBLIC_CLERK_SIGN_UP_URL="/sign-up"
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL="/app/issues"
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL="/app/issues"
NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL="/app/issues"
NEXT_PUBLIC_CLERK_SIGN_UP_FALLBACK_REDIRECT_URL="/app/issues"Notes:
- Never commit real values.
.env.localand.env.productionare ignored. - Vercel environment variable changes require a new deployment.
DATABASE_URLis used by the app at runtime.DIRECT_DATABASE_URLis preferred byprisma.config.tsfor Prisma CLI migration commands.
- Trigger the first Vercel deployment.
- Copy the generated Vercel URL.
- Add that URL to Clerk allowed origins/domains.
- Redeploy in Vercel after Clerk domain and env-var changes.
Run production migrations only with production env vars intentionally loaded:
npm run prisma:migrate:deployprisma migrate deploy applies committed migrations without creating a new
migration, without a shadow database, and without resetting data.
Do not run these against production:
npm run prisma:migrate
prisma migrate dev
prisma migrate reset
prisma db pushIf Neon advisory locks or pooled-connection issues appear, confirm
DIRECT_DATABASE_URL is a direct, non-pooled connection string.
Open the deployed URL in a clean browser session:
/loads./sign-inloads production Clerk UI./sign-uploads production Clerk UI./appredirects signed-out visitors to Clerk.- Sign up or sign in.
- Create a blank workspace or choose
Create demo workspace. - Create a project.
- Create an issue.
- Open the issue drawer.
- Add a comment.
- Create/attach/remove a label.
- Use status, priority, label, and search filters.
- Switch workspaces if more than one exists.
- Open Views and Settings.
- Invite a second Clerk account, accept the invitation with the matching email, and assign an issue to that member.
- Confirm issue activity records the collaboration changes.
- Confirm no secret values appear in browser output, Vercel logs, or docs.
Optional production verifier commands can be run only from a safe environment where production database env vars are intentionally loaded:
npm run db:verify-workspace
npm run db:verify-projects
npm run db:verify-issues
npm run db:verify-comments
npm run db:verify-labels
npm run db:verify-filters
npm run db:verify-demo
npm run db:verify-views
npm run db:verify-settings
npm run db:verify-counts- Clerk redirect mismatch: add the Vercel URL/custom domain to Clerk allowed origins/domains and redeploy.
- Missing Vercel env vars: add every required env var for the Production environment and redeploy.
- Env vars changed but app still behaves the same: trigger a new deployment.
- Prisma cannot find
DATABASE_URL: confirm it exists in the environment where the Prisma command is running. - Neon migration timeout: use a direct, non-pooled
DIRECT_DATABASE_URL. - Vercel build failure around Prisma Client: run
npm run prisma:generateandnpm run buildlocally, then check that migrations and schema are committed. - Runtime database errors after a successful build: verify the production
database URL, SSL settings, and that
npm run prisma:migrate:deployran. - Demo creation fails after sign-in: verify migrations ran and the signed-in
user reached
/appso SuDo could sync a localUserrow.
Do not commit secrets, browser auth state, database URLs, invitation tokens, or screenshots containing private workspace data.
Before release:
- Pull the intended commit and confirm the working tree contains only the release changes.
- Run
npm ci. - Run
npm run lint,npm run typecheck,npm run test,npm run build, andnpm run check. - Run
npm run test:e2e. - Run
npm run test:e2e:authonly when dedicated Clerk test accounts are configured locally. - Run
npm run prisma:migrate:statusagainst the intended database. - Review every pending migration. The saved-view release requires
20260607033000_saved_views. - Confirm Vercel Production has the required Clerk, app URL, pooled
DATABASE_URL, and directDIRECT_DATABASE_URLvalues. - Confirm Clerk allows the production domain.
Release:
- Intentionally load the production Neon environment.
- Run
npm run prisma:migrate:deploy. - Run
npm run prisma:migrate:statusagain and confirm no migration is pending. - Redeploy the exact verified commit to Vercel.
- Complete the post-deploy QA checklist below.
- Review Vercel runtime logs for failed requests, action failures, auth redirects, slow pages, and database connection errors.
Rollback:
- Stop the rollout or promote the previous known-good Vercel deployment.
- Do not use
prisma migrate reset,prisma db push, or manually delete a production migration. - For an additive migration such as saved views, leave the added table in place while rolling back application code.
- For a future destructive migration, prepare and test a forward-fix migration and a Neon restore plan before release.
- Record the failed release commit, symptoms, relevant safe log event names, and recovery action.
next.config.ts applies the following response headers to all routes:
X-Frame-Options: DENYX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-origin- a restrictive
Permissions-Policy - one-year HSTS without preload
Content-Security-Policy-Report-Only
The CSP blocks framing and object embeds while allowing the Clerk and Cloudflare challenge origins needed by authentication. It remains report-only because Next.js development scripts, inline styles, and Clerk can require careful nonce-based configuration. Review browser and deployment violations before promoting the policy to enforcement. The current policy does not use a reporting endpoint, so local browser console inspection and deployment smoke tests are the initial validation path. See the Next.js CSP guide and Clerk CSP guidance.
Server authorization denials, failed mutations, invitation failures, saved-view failures, and workspace deletion attempts emit one-line JSON events suitable for Vercel Runtime Logs. Entries can include event name, operation, environment, opaque record IDs, role, status, and safe error class/code.
Never add these values to log context:
- emails, names, issue titles, descriptions, comments, or request bodies
- cookies, authorization headers, Clerk tokens, invitation tokens or hashes
- database URLs, DSNs, secrets, passwords, or environment values
- raw error messages or stacks from database and provider failures
Unexpected server-action errors return a generic user-facing fallback instead of exposing an arbitrary exception message.
Use the Vercel project dashboard's Logs view during and after deployment. Filter by production environment, deployment, route, status code, and time range. Check:
mutation.failedandauthorization.*structured events- repeated Clerk redirects or missing-user failures
- Prisma connection, pool exhaustion, or migration errors
- slow issue, view, and settings routes
- elevated 4xx/5xx responses after release
Vercel Runtime Logs are available without adding a runtime dependency. Optional paid observability features should be labeled before adoption. See Vercel Runtime Logs.
Sentry is not installed in this pass. The structured logs and existing route error boundaries keep the application dependency-free, but they do not provide cross-request traces, alerting, or source-mapped exception aggregation.
Recommended future setup:
- Install
@sentry/nextjsand use Sentry's official Next.js setup. - Keep
SENTRY_DSNandNEXT_PUBLIC_SENTRY_DSNoptional. - Store
SENTRY_AUTH_TOKENonly in the deployment environment for source-map upload; never expose it to the browser or commit it. - Redact user content and auth data before sending events.
- Verify a test event in Preview before enabling Production alerts.
See Sentry for Next.js.
Current mutation guardrails are process-local. They limit accidental bursts within one warm Vercel instance, but they are not a production-wide abuse boundary and reset when instances recycle.
Preferred production options:
- Use Vercel Firewall rate-limiting rules for coarse route/IP protection where the plan supports it.
- Use a shared Redis-backed limiter, such as Upstash, for authenticated user/workspace mutation keys that must be consistent across instances.
- Keep authorization and validation in server actions even when an edge rule rejects obvious abuse.
Do not use the primary Neon application tables as a high-frequency rate-limit counter without load testing and a cleanup design. See Vercel Firewall rate limiting and Upstash Ratelimit.
- Keep production on a protected Neon branch and use separate development or preview branches for migration rehearsal.
- Point Vercel Preview deployments at preview data and a development Clerk instance. Never let untrusted preview code use production database secrets.
- Use the pooled URL for application traffic and the direct URL for Prisma migration commands.
- Run
prisma migrate status, inspect SQL, test on a branch, and take note of the restore window before destructive migrations. - Neon restore creates a new branch at a selected point in time. Test recovery by restoring to a separate branch, validating schema and representative records, and connecting a non-production app or verifier to that branch.
- Do not overwrite or delete the production branch during a restore exercise.
- Before destructive changes, prefer expand-and-contract migrations, retain backward compatibility for one release, and document the forward recovery path.
References: Neon branching and Neon restore.
The June 7, 2026 audit reports moderate advisories in the direct Next.js and
Prisma toolchain plus transitive postcss, @prisma/dev, and
@hono/node-server packages. No high or critical advisory was reported.
npm audit fix --force is intentionally not used because npm currently proposes
breaking or inappropriate major downgrades for this dependency graph. Follow-up:
- Apply compatible patch updates for Next.js,
eslint-config-next, Clerk, and development tooling in a dedicated upgrade change. - Re-run lint, typecheck, unit tests, build, Playwright, and
npm audit. - Track Prisma and Next.js upstream releases that resolve the remaining transitive advisories.
- Reassess immediately if severity increases or an exploit applies to SuDo's deployed paths.
- Landing page, sign-in, and sign-up render on desktop and mobile.
- Signed-out
/appredirects to sign-in without a loop. - Sign in and sign up with production Clerk.
- Create and switch workspaces.
- Create, rename, and archive a project.
- Create, open, edit, assign, unassign, label, and archive an issue.
- Add a comment and confirm activity history.
- Apply search, status, priority, and label filters.
- Create, open, rename, and delete a saved view.
- Open
Cmd/Ctrl + Kand run navigation and create commands. - Create, revoke, and accept an invitation with the matching account.
- Verify owner/admin/member controls and server-side denials.
- Confirm wrong workspace-delete text stays blocked and exact text deletes.
- Check narrow mobile navigation, dialogs, forms, and issue drawer.
- Confirm security headers on the production response.
- Review Vercel runtime logs and database connection health.
No private or stale screenshots are committed. Capture current production or sanitized local images using these exact paths:
| File | Capture |
|---|---|
public/screenshots/landing.webp |
Landing hero and product preview |
public/screenshots/issues.webp |
App shell with a populated issue pipeline |
public/screenshots/issue-detail.webp |
Issue drawer with assignee, comments, labels, and activity |
public/screenshots/command-menu.webp |
Cmd/Ctrl + K with useful commands visible |
public/screenshots/saved-views.webp |
Persisted views plus generated shortcuts |
public/screenshots/members.webp |
Members, roles, and a pending invitation |
public/screenshots/danger-zone.webp |
Exact-name workspace deletion confirmation |
public/screenshots/mobile.webp |
Narrow issue or navigation workflow |
Capture guidance:
- Use a seeded demo workspace and replace any real email addresses.
- Never capture invite tokens, Clerk internals, browser auth state, or private workspace data.
- Prefer WebP at 1600x1000 or similar, under roughly 500 KB per desktop image.
- Keep browser chrome out of the frame unless it helps establish the live URL.
- Verify text remains readable at GitHub's rendered README width.
After adding reviewed images, place a compact screenshot grid near the top of this README rather than embedding every workflow at full size.
0-10 seconds: "SuDo is a deployed multi-tenant issue tracker for small technical teams. I built it with Next.js, TypeScript, Clerk, Prisma, Neon Postgres, and Vercel."
10-23 seconds: Sign in and create the seeded demo workspace. Open a project and create an issue. Point out the compact issue ID, status, priority, and assignee.
23-36 seconds: Add a label, apply search or priority filters, and save the current filter state as a named view.
36-49 seconds: Open the issue drawer, add a comment, change the assignee, and show the activity timeline recording those changes.
49-61 seconds: Open Settings. Show members, pending invitations, and the owner/admin/member permission model. Briefly show that workspace deletion requires both owner authorization and the exact workspace name.
61-69 seconds: Press Cmd/Ctrl + K and navigate to another product area.
69-75 seconds: "The app is deployed on Vercel with Clerk auth and Neon Postgres. It has 113 unit tests, public and authenticated Playwright coverage, CI, security headers, and redacted structured logging."
Keep the recording conversational. Show one complete workflow rather than opening every page.
- Built and deployed a multi-tenant issue tracker using Next.js 16, TypeScript,
Clerk, Prisma, Neon Postgres, and Vercel, with owner/admin/member RBAC, hashed
invitations, assignees, activity history, saved views, and
Cmd/Ctrl + Kworkflows. - Implemented tenant-scoped Server Actions and authorization across workspaces, projects, issues, comments, labels, memberships, and saved views; added concurrency-safe issue numbering, read-pure queries, and owner-protected destructive operations.
- Established production verification with 113 Vitest tests, 4 public Playwright checks, 4 optional authenticated Playwright workflows, GitHub Actions CI, database relationship verifiers, security headers, and redacted JSON runtime logs.
The difficult part was preserving tenant correctness across many related workflows rather than implementing isolated CRUD screens. Each project, issue, comment, label, assignee, invitation, and saved view must be derived from an authorized workspace on the server. Issue creation also needed concurrency-safe numbering without turning ordinary issue listing into a write.
Clerk establishes identity, while SuDo stores a local user record for relational data. Request-scoped caching avoids repeated Clerk lookups and user upserts. Workspace access helpers load the membership and active workspace together; role helpers then enforce owner/admin/member permissions. Client-provided workspace IDs are never treated as proof of access.
WorkspaceMember models the many-to-many user/workspace relationship and role.
Workspace-owned records carry explicit workspace scope, while projects own
human-readable issue sequences. Invitations store token hashes and expiry
state. Activity records are append-only product history, and saved views store
validated filter configuration as JSON.
Vercel hosts the Next.js application, Clerk provides production auth, and Neon
provides pooled runtime plus direct migration connections. Releases verify
pending migrations, run prisma migrate deploy, redeploy the tested commit,
complete browser smoke checks, and inspect structured runtime logs.
Vitest covers validation, authorization, tenant isolation, issue numbering, read purity, collaboration, deletion safety, and saved views. Public Playwright tests cover responsive routes, auth redirects, and security headers. Authenticated workflows use Clerk test accounts and reusable storage state when credentials are explicitly configured. Database verifiers inspect persisted relationships outside secret-free CI.
The product deliberately favors a strong list/detail workflow over kanban, real-time presence, or notification breadth. Invitations use shareable links because transactional email is not configured. The next production investments would be distributed rate limiting, enforced nonce-based CSP, alerting, email delivery, and fully configured authenticated E2E in a dedicated test environment.
- Authenticated Playwright workflows require dedicated Clerk development test accounts and are skipped in secret-free CI.
- CSP is report-only until Clerk/Next.js violations are reviewed and a nonce-based enforcement strategy is validated.
- Mutation limits are in-process per Vercel instance, not distributed.
- Invitations use secure expiring links but do not yet send transactional email.
- There are no real-time notifications, mentions, or presence indicators.
- Sentry or equivalent alerting and cross-request tracing are not configured.
- Five moderate Next.js/Prisma toolchain and transitive audit advisories are tracked; forced breaking downgrades are intentionally not applied.
- The saved-views migration must be applied with
npm run prisma:migrate:deploybefore deploying that feature version.