Skip to main content
Prerequisite: You should have installed Node.js (version 22 or higher, matching the repo’s engines.node of >=22).

Installation

Step 1. Install bun on your machine (if you don’t already have it yet), by running the following command: macOS/Linux:
curl -fsSL https://bun.sh/install | bash
Windows:
powershell -c "irm bun.sh/install.ps1 | iex"

Why Bun?

We chose bun as our runtime and package manager based on its design goals, which align perfectly with our platform’s needs:
  • 4x Faster Startup: Bun processes start significantly faster than Node.js, improving development experience and CI/CD performance
  • Built-in TypeScript & JSX Support: No need for additional transpilation setup - bun natively executes .ts, .tsx, and .jsx files
  • All-in-One Toolkit: Combines runtime, package manager, bundler, test runner, and script runner in a single executable
  • Web Standards Compatibility: Implements modern Web APIs like fetch, WebSocket, and ReadableStream out of the box
  • Node.js Compatibility: Drop-in replacement for Node.js with full compatibility for existing projects
  • Better Performance: Powered by JavaScriptCore engine with reduced memory usage and faster execution
These benefits make bun an ideal choice for our monorepo architecture and development workflow. Step 2. Configure Tiptap Pro Registry:
This step is no longer needed.
Step 3. After configuring Tiptap Pro registry, you can install all dependencies by running the following command:
bun install
This repository’s Bun configuration enforces a minimum release age of 1 day for new npm package versions. If Bun skips a just-published release during install, wait for the package to age past the gate or choose an older published version. To complete the initial setup, please restart your IDE so that it can recognize the newly installed dependencies. Additionally, some recommended VS Code Extensions may only work after restarting your IDE.
If you’re using VS Code, you can install following the recommended extensions that will help you with the development process: Biome (this repo standardizes on Biome for linting and formatting via biome.json, not ESLint or Prettier), Vitest, Tailwind CSS IntelliSense, Version Lens, Error Lens, Pretty TypeScript Errors, Material Icon Theme.

Zed

This monorepo tracks project-level Zed settings in .zed/settings.json. Opening the repository root in Zed can otherwise trigger expensive scans across generated output and local dependency trees, especially node_modules, .turbo, .next, package dist directories, Flutter build output, Rust target, and Python virtualenv/cache folders. Keep large generated directories in file_scan_exclusions. Zed’s file_scan_exclusions setting replaces the default list instead of extending it, so include Zed’s default VCS/system exclusions whenever adding project exclusions. The project uses TypeScript 7 RC’s tsc for compiler checks and a single TypeScript language server path for TypeScript, TSX, and JavaScript; avoid enabling competing TypeScript language servers for the same language unless you are deliberately comparing language server behavior.

Development

Start Next.js apps

To develop all apps and packages (without requiring a local setup), run the following command:
bun
bun dev
This command will start all Next.js apps in development mode. You can access the platform app by visiting the following URL: https://tuturuuu.localhost
Local app development uses Portless, so each app gets a stable Tuturuuu subdomain instead of relying on its fallback port. The app package dev scripts run the repo’s Portless-safe wrapper, and the underlying framework command remains available through dev:app for direct port-based debugging. The wrapper only removes stale static aliases for the same app when their default backend port is closed, then starts Portless with the original app config. This fixes old worktree aliases such as zalo-qr-chat-setup.tuturuuu.localhost without cleaning certificates, deleting unrelated routes, or touching other apps’ active aliases. Package-local bun dev --force delegates to portless run --name <app> --force so Portless can take over a live route when you explicitly request it. Root app-specific commands such as bun dev:chat, bun dev:calendar, and bun dev:edu reuse app servers that are already running. The launcher checks active Portless routes first, then the app’s default localhost port; if it finds a direct localhost listener, it tries to register a matching Portless alias so cross-app auth can still use the *.tuturuuu.localhost URL. Pass --force or --no-reuse when you intentionally want the command to include already-running app workspaces. Force mode starts the package dev --force path so Portless can take over the matching route instead of leaving a stale static alias in place. bun dev:tanstack-web starts the TanStack Start migration frontend at https://tanstack.tuturuuu.localhost, with fallback port 7824. It is the dedicated frontend for the Next.js-to-TanStack migration and should call Rust-owned APIs through packages/internal-api helpers instead of direct browser access to protected apps/web routes. See platform/architecture/tanstack-rust-migration for the route manifest, Docker, E2E, benchmark gates, and Cloudflare Workers preparation. For the Cloudflare-compatible preview shape, start the Worker entrypoints instead of the normal Portless dev wrapper:
bun wrangler dev --config apps/backend/wrangler.jsonc
bun --cwd apps/tanstack-web run preview:cloudflare
Use the same environment variable names as deployment: BACKEND_INTERNAL_TOKEN, BACKEND_PUBLIC_ORIGIN, and BACKEND_INTERNAL_URL. Keep values in your shell or ignored local env files; do not add literal tokens or account-specific origins to docs, Wrangler config, package manifests, or source. After deploying preview Workers, run bun smoke:cloudflare with BACKEND_WORKER_ORIGIN, TANSTACK_WEB_WORKER_ORIGIN, and BACKEND_INTERNAL_TOKEN to verify backend health/readiness, protected migration status, missing/invalid token rejection, and the TanStack root shell. bun dev:web is intentionally lean: by default it starts only apps/web and does not start the @tuturuuu/types or @tuturuuu/supabase package watch builds. Use this default for app-route and UI work so the local process tree stays small. When you are actively editing those package sources and need their dist output rebuilt live, opt in explicitly:
bun dev:web --with-shared-watchers
You can also pass --no-shared-watchers to other root app launchers when you want the same low-memory posture for a focused app session. apps/web does not mount React Query Devtools in its default development provider. Keep the default off for faster cold route compiles and lower Turbopack memory pressure; add a local, temporary devtools mount only while debugging query cache behavior, then remove it before committing. Keep the always-mounted public shell small. The mobile menu drawer, marketing footer body, report-problem dialog, and authenticated dropdown-only affordances should stay behind dynamic imports when possible, and shell icons should use @tuturuuu/icons/lucide-static instead of the root @tuturuuu/icons entrypoint. This prevents the public /login compile graph from pulling large Radix, footer, or full icon-package chunks into every bun dev:web session. bun dev:chat also manages the local chat realtime sidecar. It reuses localhost:7817 when a sidecar is already running; otherwise it starts apps/chat-realtime so apps/web can proxy chat SSE traffic for apps/chat.
AppLocal URL
Platformhttps://tuturuuu.localhost
Calendarhttps://calendar.tuturuuu.localhost
Chathttps://chat.tuturuuu.localhost
CMShttps://cms.tuturuuu.localhost
Drivehttps://drive.tuturuuu.localhost
Externalhttps://external.tuturuuu.localhost
Financehttps://finance.tuturuuu.localhost
Hivehttps://hive.tuturuuu.localhost
Hive Realtimehttps://realtime.hive.tuturuuu.localhost
Infrahttps://infra.tuturuuu.localhost
Inventoryhttps://inventory.tuturuuu.localhost
Learnhttps://learn.tuturuuu.localhost
Mailhttps://mail.tuturuuu.localhost
Meethttps://meet.tuturuuu.localhost
Mindhttps://mind.tuturuuu.localhost
Novahttps://nova.tuturuuu.localhost
Playgroundhttps://playground.tuturuuu.localhost
QRhttps://qr.tuturuuu.localhost
Rewisehttps://rewise.tuturuuu.localhost
Shortenerhttps://shortener.tuturuuu.localhost
Taskshttps://tasks.tuturuuu.localhost
Teachhttps://teach.tuturuuu.localhost
Trackhttps://track.tuturuuu.localhost
When debugging without Portless, run an app’s dev:app script directly. If auth redirects or absolute links must stay on the raw listener port, set BASE_URL, WEB_APP_URL, or the app-specific URL environment variable to that legacy fallback port for the direct debug session. Keep runtime defaults, auth redirects, and cross-app links on the Portless origins during local development. Use the shared local app URL helpers instead of hard-coding localhost fallbacks in satellite app constants or proxies, so a browser session that starts on tasks.tuturuuu.localhost stays under the tuturuuu.localhost namespace through login and return URLs. For a native production-mode smoke test of apps/web, run the native build and then start it through the same Portless hostname:
bun run build:web
cd apps/web
bun run start
bun run start delegates to Portless and runs the built Next.js server through start:app, so the browser URL remains https://tuturuuu.localhost while the server listens on the backend port that Portless injects through PORT. Use bun dev:edu when working on the education apps together. It starts Learn, Teach, their shared packages, and the central web app so cross-app login can complete locally. Next.js still prints the internal listener port, for example http://localhost:4803, because Portless assigns a free backend port to the app process. App dev:app scripts also print a Portless URL: line from the PORTLESS_URL environment variable; use that URL in the browser:
Portless URL: https://tuturuuu.localhost
If Turbo cannot prompt for the Portless sudo password while starting app dev scripts, run the Portless setup helper first:
bun portless:setup
The helper starts the Portless HTTPS proxy on port 443 before Turbo launches package dev scripts. It no-ops when the proxy is already responding and skips in non-interactive shells or CI. bun setup runs this helper after dependency installation so fresh local checkouts are ready before the build step, then invokes Turborepo through the repo-local turbo:local helper instead of any globally installed Turbo binary. To install Portless as an OS startup service instead of only starting the current proxy daemon, run:
bun portless:setup -- --service

Diagnose a broken local environment

When a local app suddenly cannot reach another app — for example the Inventory landing page throws InternalApiError: 404 because its server-side call to https://tuturuuu.localhost/api/... is not routed — run the doctor:
bun doctor
It checks the Node runtime, Docker daemon, local Supabase ports, local Redis ports, apps/web Redis env wiring, app-local Supabase env consistency, whether the Portless proxy is responding on port 443, and the health of every registered Portless route. The report uses colored OK, WARN, and FAIL statuses in interactive terminals. Supabase env mismatch warnings are compact by default so secret-like values stay out of the normal output; use the verbose view when you need redacted per-file fingerprints:
bun doctor --verbose
The most common failure after overlapping or crashed dev sessions is a stale routing table: the proxy still points a live app route at a dead dev-server port, so cross-app internal API calls return 404. The doctor flags those routes as DEAD and exits non-zero. Static aliases for apps you simply are not running are reported as warnings, not failures, and include an exact cleanup command. An initialized but empty Portless route table is treated as healthy because no apps may be running yet. Apply the safe automatic Portless repair (start the proxy, or reset it when routes are stale) and re-run the checks:
bun doctor --fix
bun setup deliberately no-ops when a proxy is already responding, so it does not clear a stale routing table on its own. To force a clean restart — stop the proxy, prune orphaned dev servers from crashed sessions, then start a fresh proxy that apps re-register against — run:
bun portless:reset
After a reset, restart your dev servers (for example bun dev:inventory) so each app re-registers its route with the new proxy. If bun doctor still warns about an inactive static alias after a reset, that alias was created with portless alias and is not deleted by proxy resets. Start the app if you still use the alias, or remove the alias shown in the doctor output:
bunx portless alias --remove <alias-name>
If the doctor warns that app env files use mismatched Supabase values, choose the intended source app and keep these three keys together: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY, and SUPABASE_SECRET_KEY. For quick local development, copy apps/web/.env.local to app-local .env.local files:
bun dev:sync:apps
Preview the target files first with:
bun dev:sync:apps:dry-run
This sync compares app env files only. The root .env.local can intentionally target a different Docker or deployment workflow without keeping the app mismatch warning alive.

Diagnose web dev memory and cache pressure

When apps/web feels slow or Activity Monitor reports high Node.js usage, start with the dev diagnostic before moving routes into another app:
bun diagnose:dev:web
Read the report by pressure type:
  • Live Next dev process RSS is the memory held by the running next dev process tree. If this is high, inspect the top processes and the latest trace spans before changing architecture.
  • apps/web/.next/dev/cache/turbopack is generated Turbopack filesystem cache. Multi-GB cache growth can make disk usage look like a live memory leak even when process RSS is modest.
  • .turbo/cache is Turborepo task cache. It is separate from the live Next.js dev server and should not be treated as app RSS.
  • Build memory policy reports Docker and next build memory budgets. Those caps explain build behavior only; do not compare them directly to next dev RSS.
  • Likely pressure calls out whether the current evidence points to live RSS, cache growth, watcher limits, or build-only memory settings.
If the report points at Turbopack cache growth, stop the web dev server and preview the targeted cleanup first:
bun clean:dev:web --dry-run
Then remove the web Next dev output:
bun clean:dev:web
Use bun clean:dev:web --all-next-dev --dry-run when several app dev caches exist and you want to inspect every apps/*/.next/dev target before deleting them. Add --include-turbo-cache only when the report also calls out .turbo/cache growth after branch or dependency graph churn. Avoid the broad root bun clean command for this problem because it deletes dependency and build state that is unrelated to the current dev-server slowdown. Then restart the targeted app command you were using, such as bun dev:web. Avoid deleting unrelated app output while other agents or local sessions are working in the same checkout. If live RSS is the real pressure, use the latest .next/dev/trace spans and the matching process tree to reduce the compiled import graph first. Favor narrow package subpath imports and lazy-loaded settings panels before splitting an app boundary. A dedicated infrastructure app only reduces apps/web memory when infrastructure pages leave the web route graph; running apps/web and an infrastructure app together can increase total RAM. Native local dev leaves Watchpack polling off unless the environment explicitly sets WATCHPACK_POLLING. This keeps file watching lighter on macOS and Linux. If the diagnostic reports watcher EMFILE errors or route discovery stops updating on your machine, restart with polling enabled:
WATCHPACK_POLLING=true bun dev:web
If the doctor warns that apps/web is missing Redis env, configure the local SRH bridge and start the Redis stack in one step:
bun redis:setup
bun redis:setup writes UPSTASH_REDIS_REST_URL=http://localhost:8079 and the matching local token into apps/web/.env.local, then runs bun redis:start. Restart bun dev:web after setup so the app picks up the new env values. To clear local Redis data while keeping the same local env wiring, run:
bun redis:reset
This starts the local Redis stack if needed, then runs FLUSHALL inside the Redis container. To stop the local dev environment, run:
bun stop
This stops this repo’s Portless-registered Next.js dev servers, prunes Portless routes, removes stale static aliases for this project, stops local Supabase, and finishes by running bun doctor. Preview the cleanup first with:
bun stop --dry-run

Start Local Supabase Instance

To start a local supabase instance (database), run the following command:
bun
bun sb:start
This command will start a local supabase instance on your machine. You can access the supabase instance by visiting the following URL: http://localhost:8003
You need to have Docker installed and running on your machine to start a local supabase instance.

Stop Local Supabase Instance

To stop the local supabase instance, run the following command:
standard
bun sb:stop
bun stop is broader than Supabase. It stops this repo’s local dev servers, cleans stale Portless aliases, stops Supabase, and verifies the result with bun doctor.

Better Development Experience

In case you want to run all local development servers, run the following command:
bun
bun devx
Running devx will:
  1. Stop the currently running supabase instance and save current data as backup (if there is any)
  2. Install all dependencies
  3. Start a new supabase instance (using backed up data)
  4. Start all Next.js apps in development mode
If you want to have the same procedure without the backup, you can run bun devrs instead. This will:
  1. Stop the currently running supabase instance (if there is any)
  2. Install all dependencies
  3. Start a new supabase instance (with clean data from seed.sql)
  4. Start all Next.js apps in development mode
In case you don’t want to run a local supabase instance, you can run bun dev instead.

Local development

Seed accounts

There are 5 seed accounts that are already set up for local development:
  1. local@tuturuuu.com
  2. user1@tuturuuu.com
  3. user2@tuturuuu.com
  4. user3@tuturuuu.com
  5. user4@tuturuuu.com
You can use any of these accounts to log in to the app and quickly test the functionality of the app, since they are already set up with the necessary data.

Authentication

A local mail server (InBucket) is automatically set up by Supabase to handle authentication emails.
You can access the mail server by visiting the following URL: http://localhost:8004

Build

To build all apps and packages, run the following command:
bun
bun run build

Test

To run all tests, run the following command:
bun
bun run test
To run the Flutter mobile quality gate, use:
bun
bun check:mobile
bun check:mobile runs Dart format, Flutter analyze, and Flutter tests for apps/mobile. Flutter test output is streamed by default so slow suites still show progress instead of appearing stuck.
Tests are still a work in progress. We’re currently working on adding tests to all packages to ensure the best quality possible.

Git Conventions

Tuturuuu uses standardized conventions for Git commits and branch naming. For more information, see the Git Conventions guide to learn how to format your commits and branch names to align with our workflow.