Prerequisite: You should have installed Node.js
(matching the repository floor of
>=22.13.0).Installation
Supabase Auth declaration compatibility
The repository pins a Bun patch for@supabase/auth-js@2.117.1. Its credential
interface retains every native PublicKeyCredential member except toJSON,
which the package declares separately with its existing response type. Native
DOM JSON uses an encoded large-blob string; the package’s existing extension
output type uses ArrayBuffer. Treat these as distinct contracts.
The patch changes declarations only. It does not encode extension data, alter
authentication behavior, or qualify browser passkey delivery. Both shipped
declaration formats and the package source carry the same correction. Keep
skipLibCheck disabled when checking this compatibility boundary:
patchedDependencies; do not edit installed
dependencies as a permanent fix. Before updating Auth JS or removing the patch,
check the strict public verification types and the negative native-credential
and encoded-blob controls against the installed replacement.
The finite behavior controls call the installed ESM and CommonJS serialization
functions with typed mocks. They check registration and authentication separately.
The native toJSON branch returns its encoded extension output unchanged; the
fallback returns raw extension buffers unchanged. Stringifying a fallback
large-blob buffer produces {}, so this patch does not prove extension data can
be transmitted correctly. Native registration JSON also requires fields that the
existing fallback does not emit.
The controls retain expected type errors for native DOM JSON and credentials
without toJSON. The package’s serializer signatures still require toJSON and
advertise the package’s raw-extension response type even though the implementation
supports both branches. Those signature limitations remain separate from this
inheritance correction. The mocks exercise pure serialization, not browser
credential acquisition or server verification.
Step 1. Install bun on your machine (if you don’t already have it yet), by running the following command:
macOS/Linux:
Install the repository dependencies
Use the Bun version pinned by the rootpackageManager field. The
version reference is generated from repository
manifests and lockfiles, so it records the current pins without relying on
outdated prose or a globally installed CLI.
bun setup is not a minimal installation command: it can build packages and
probe services. Use development without Docker
for the supported minimal Next.js path. Rust and TanStack Start are not in use
and are not required to work on the active web apps.
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.
First-party package families that publish coordinated releases together may be
explicitly excluded from the age gate in bunfig.toml; keep each trusted
family’s exact-name list aligned with the packages resolved in bun.lock and
cover the list with a policy test.
Discord’s Python environment is resolved separately in apps/discord/uv.lock.
For targeted transitive updates, use uv lock --upgrade-package pyjwt --upgrade-package urllib3, then confirm that unrelated package records and the
project’s Python floor are unchanged. Validate artifact hashes against PyPI and
run the owning locked Python CI, including the offline dependency security tests
in tests/test_dependency_security.py.
PyJWT 2.15.1 includes malformed-payload/JWKS hardening and accepts valid trailing
Base64URL padding while continuing to reject invalid characters. urllib3 2.8.0
bounds chunk-size lines and fixes HTTPS proxy TLS configuration and Deflate
streaming. Proxy-specific trust settings belong in proxy_ssl_context and the
proxy identity options; destination TLS settings are separate. These dependencies
are reached through Supabase Auth/MSAL and Requests, respectively. A locked CI
environment does not establish the version in an already deployed Modal image:
app.py has a separate floating .pip_install(...) recipe. Verify that image’s
resolved versions during the separately authorized deployment process.
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.
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. Every app
configuration overrides TypeScript 7’s types compatibility wildcard with its
explicit runtime globals, so editors with an older bundled language service do
not report Cannot find type definition file for '*' across the app tree.
Development
Start Next.js apps
To develop all apps and packages (without requiring a local setup), run the following command:bun
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
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.
Inactive migration tooling
Rust (apps/backend) and TanStack Start (apps/tanstack-web) are paused and
not in use. Retained commands and Worker entrypoints are future-resumption
references, not steps in the current Next.js setup. Do not start these services
or require their migration gates for routine app development. See the
active runtime map.
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:
--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.
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.
When a satellite proxies a Web-owned API collection through an external rewrite,
use an exact collection rule and a separate nonempty :path+ descendant rule.
A :path* destination can compile to a regex backreference whose empty value
leaves a trailing slash. If Web strips that slash with a relative 308, the
satellite can repeat the same request indefinitely. Infrastructure’s employee
collection regression exercises both installed Next runtime matching and the
deployment rewrite converter in apps/infrastructure/next.config.test.ts.
Preserve satellite session and API guards, and verify the emitted deployment
route plus an authenticated request after the authorized rollout; passing source
tests alone does not establish production availability.
For a production-mode smoke test of apps/web, use the preview deployment
built by CI for the PR’s exact head commit. Do not create a local production
build on this machine. Local development still uses the Portless hostname for
cross-app login and routing checks.
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:
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:
Diagnose a broken local environment
When a local app suddenly cannot reach another app — for example the Inventory landing page throwsInternalApiError: 404 because its server-side call to
https://tuturuuu.localhost/api/... is not routed — run the doctor:
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:
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 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 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:
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:
.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
Whenapps/web feels slow or Activity Monitor reports high Node.js usage, start
with the dev diagnostic before moving routes into another app:
Live Next dev process RSSis the memory held by the runningnext devprocess tree. If this is high, inspect the top processes and the latest trace spans before changing architecture.apps/web/.next/dev/cache/turbopackis generated Turbopack filesystem cache. Multi-GB cache growth can make disk usage look like a live memory leak even when process RSS is modest.apps/web/.next/cachecan include the Next 16.3 Turbopack build filesystem cache from local builds. It speeds repeated localnext buildruns, but it is still local artifact state and must stay out of Turborepo task outputs..turbo/cacheis Turborepo task cache. It is separate from the live Next.js dev server and should not be treated as app RSS.Build memory policyreports Docker andnext buildmemory budgets. Those caps explain build behavior only; do not compare them directly tonext devRSS.Likely pressurecalls out whether the current evidence points to live RSS, cache growth, watcher limits, or build-only memory settings.
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:
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:
FLUSHALL inside the
Redis container.
To stop the local dev environment, run:
bun doctor. Preview the cleanup first with:
Start Local Supabase Instance
To start a local supabase instance (database), run the following command:bun
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
Stop Local Supabase Instance
To stop the local supabase instance, run the following command:standard
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
Running
devx will:- Stop the currently running supabase instance and save current data as backup (if there is any)
- Install all dependencies
- Start a new supabase instance (using backed up data)
- Start all Next.js apps in development mode
bun devrs instead. This will:- Stop the currently running supabase instance (if there is any)
- Install all dependencies
- Start a new supabase instance (with clean data from seed.sql)
- Start all Next.js apps in development mode
Local development
Seed accounts
There are 5 seed accounts that are already set up for local development: 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
Builds run in CI on this machine’s workflow. Open a PR, wait for the relevant app build checks on its exact head commit, and inspect the preview deployment when runtime behavior needs verification.bun setup also invokes package
builds, so use bun install for a new local worktree. Do not run bun run build
or app-level build scripts locally. Run focused tests locally if needed;
bun validate can build workspace dependencies through Turbo. Use the applicable
test, type-check, lint, and build workflows on the PR’s exact head commit
instead of running it here.
Test
To run all tests, run the following command:bun
bun
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.
Verify dependency patches
Check the official stable version and publication time before resolving an update. Keepbunfig.toml’s release-age policy in force. Resolve a small coherent group in
an isolated checkout using the package manager, then compare resolved entries and
workspace declarations in the lockfile. A new nested entry can be required when a
transitive consumer pins the old version exactly; report that constraint rather
than claiming every consumer uses the new version.
For example, DOMPurify 3.4.16 fixes in-place raw-text and hook-detachment behavior.
Test the real browser and isomorphic packages with the owning app configurations,
including safe markup, executable attributes and whole-document forms. An old
version that fails the targeted regression provides a useful negative control.
The contacts preview uses DOMPurify; Mail uses a separate sanitize-html contract,
so tests of one sanitizer do not verify the other. Monaco 0.57.0 pins DOMPurify
3.4.15, including in its current stable registry metadata; upgrading compatible
consumers does not remove that pin.
Queue installs and test stages through ttr resources run after fresh resource
checks, leaving urgent validation ahead of dependency maintenance. A focused test
fixture does not establish full workspace resolution, type-checking or app builds.
Require those results from CI on the exact implementation commit.
Mobile release builds
The native build commands verify or hydrate each flavor’s ignored Firebase SDK configuration from thetuturuuu-mobile Firebase project before compiling. The
Firebase CLI must be authenticated with access to that project.
bun build:ios:ipa is an alias for the same signed release build. To
compile the production app on a machine without signing credentials, use:
bun build:android only produces signed production bundles. Configure the
protected upload keystore through all four ANDROID_KEYSTORE_* environment
variables or apps/mobile/android/key.properties; the command fails before
compilation when signing is absent and verifies the resulting AAB with
jarsigner. The Mobile Store Deployment workflow hydrates these credentials in
its protected runner and executes this exact command. Each store upload derives
its app version from the major and minor values in apps/mobile/pubspec.yaml
and the highest existing TestFlight prerelease version for that series. For
example, an unused 0.11.0 ships as 0.11.0; once that version exists in
TestFlight, later uploads advance to 0.11.1, then 0.11.2. An unused higher
source patch is retained rather than incremented. Android and iOS receive the
same version, while the unique CI build number remains separate. This does
not commit a version bump; Release Please still owns the source version.
Development builds remain available through bun build:android:dev.
TestFlight distribution defaults to all available beta groups. Configure
TESTFLIGHT_BETA_ENABLED=false to disable automatic distribution, or set
TESTFLIGHT_BETA_GROUPS to a comma-separated list of group names or IDs to
limit it. If Apple is still reviewing another build of the same app version,
the new build remains available to internal testers and its external review is
deferred. Reviews of older app versions do not block a newer version.
The Mobile TestFlight Review Queue workflow checks every five minutes and can be
run manually; it submits the newest processed internal build to the selected
external groups without falling back to an older build. It never withdraws an
earlier submission. Once an approved build is available, the retry also expires
superseded builds older than 14 days while retaining the two newest uploads,
the two newest approved builds, builds in review, and builds attached to an App
Store release. The retry workflow accesses App Store Connect only after its
code has been promoted to production.
Validate the store workflow and review queue locally with
node --test scripts/ci/mobile-store-workflow.test.js scripts/mobile-deployment/verify-store.test.mjs.
To hydrate or validate a native Firebase configuration without compiling, run:
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.Diagnosing a first-attempt Playwright failure
trace: 'on-first-retry' records a trace only when Playwright runs a retry.
A suite with no configured retries therefore produces no trace for its initial
failure. For the Inventory / Storefront cache contract, use
trace: 'retain-on-failure' so the first failed navigation has an uploaded trace
without adding automatic reruns.
A URL assertion can complete before a streamed route enters its recovery
boundary. Capture browser errors and recovery details after the failing content
assertion, before deleting the local fixture. Keep the original assertion error,
timeout, and shell-persistence checks intact; diagnostic failures must not mask
it. The cache contract attaches bounded browser errors with Bearer/JWT values
redacted and a failure screenshot. Its trace may contain local fixture request
data, so keep it within the existing failure-artifact access and seven-day
retention policy; never use production credentials or customer records in this
fixture.
Inspect the trace and attached errors before deciding whether the cause belongs
to the application, fixture, or development runtime. A recovery boundary alone
does not establish that a failure is a flake. A push changing this test/config
already triggers e2e-tests.yaml; use that exact-head run rather than creating
a duplicate manual dispatch. Manual dispatch currently selects the full E2E
workflow, rather than an individual cache-contract job.
Validating Undici dependency upgrades
The direct Undici 8.x owners are Infrastructure and Web. Update their package declarations through Bun and review the lock diff: a scoped direct upgrade should change those workspace entries and the shared 8.x resolution. A recursive update can also refresh SDK/Discord consumers on maintained 6.x and 7.x lines; validate those as a separate consumer group rather than assuming the direct owners cover them. Preserve the repository’s minimum release age and lifecycle-script policy. Run the existing managed-cron network and chat link-preview tests in both owning apps using their actual Vitest configs. These checks preserve public-address DNS validation, private-address rejection and redirect handling with injected transport dependencies. They do not establish real socket, HTTP/2 or Worker transport behavior. Web’ssrc/lib/undici-compatibility.test.ts additionally uses
the actual library to preserve a literal __proto__ cookie as an own property of
a null-prototype result and retain an epoch expiry when clearing a cookie.
Require exact-commit Infrastructure and Web type checks and builds on hosted CI
after frozen-lock installation. Do not run local builds. A skipped build job is
not verification. Infrastructure’s existing preview workflow also deploys; use a
CI-only build route when deployment is outside the authorized scope.