Imported from zesaru/emb-app (
AGENTS.md). Install upstream withnpx skills add zesaru/emb-app. Copyright stays with the author.
Repository Guidelines
Application Context
- Internal staff management app for the Embassy of Peru in Japan: attendance, vacations, compensatory time, approvals, user administration, reports, and backups.
- Stack: Next.js 16 App Router, React 19, TypeScript, Supabase Auth/Postgres/Storage/Edge Functions, Tailwind CSS 3, and Radix UI components.
- Keep user-facing messages in Spanish and preserve
Asia/Tokyobusiness-date handling. Distinguish calendar dates from timestamps; test date boundaries when changing calculations. - Treat executable code,
package.json, configuration, and migrations as the source of truth.README.mdstill describes the original starter; planning and E2E status documents may describe older behavior.
Project Structure & Module Organization
app/: Next.js App Router pages, layouts, and API handlers (app/api/*).app/(dashboard)/(routes)/: Staff and admin screens; shared dashboard UI lives inapp/(dashboard)/_components/.app/auth/: Sign-in, sign-out, and callback handlers.proxy.tsis the Next.js request entry point and delegates session handling toutils/supabase/middleware.ts.actions/: Server actions for domain operations (vacations, compensatory time, backups, auth flows).actions/admin/users/andactions/admin/vacation-grants/: User administration and vacation grant operations.components/: Shared UI plus email templates (components/email/*).lib/: Cross-cutting utilities (validation, auth checks, backup services, helpers).lib/vacations/: Grant eligibility, balances, and consumption;lib/compensatorios/andlib/reporting/: Reporting logic.utils/supabase/: Supabase client/server/middleware adapters.types/database.type.ts: Generated database types targeted bypnpm gen-types. Other database type files exist; check imports before editing or consolidating them.store/: Client state using Zustand.test/: Unit test setup, mocks, and suites (test/unit/**).e2e/scenarios/: Playwright end-to-end specs; auth state is stored ine2e/.auth/.supabase/migrations/,supabase/seed.sql,supabase/functions/: Database history, local seed, and Edge Functions. Edge Functions are excluded from the root TypeScript check.scripts/,public/,docs/plan/: Operational scripts, static files, and domain/release context.
Build, Test, and Development Commands
Use Node 24 (.nvmrc and package.json) and pnpm 10, matching CI.
pnpm install --frozen-lockfile: Install the committed dependency graph.pnpm dev: Start local app onhttp://localhost:3000.bash scripts/dev-local-emb.sh: Start isolated local Supabase and launch Next.js using its local credentials without rewriting.env.local; requires Docker.pnpm build/pnpm start: Build and run production output.pnpm test: Run Vitest interactively in local development. Usepnpm test --runfor a single non-interactive run.pnpm test --run test/unit/actions/add-vacations.test.ts: Run a focused unit suite.pnpm test:invitation:local: Run invitation recovery against isolated local Supabase with simulated email delivery.pnpm test:invitation:e2e:local: Run the real admin form, delivery failure/retry, password setup, and invite acceptance against local Supabase. Requires local services; starts its own Next.js server on port 3000 and removes disposable accounts and temporary email artifacts.pnpm test:logout:local: Run logout with mouse/keyboard, failed-request retry, cookie removal, protected-route checks and preservation of another session against local Supabase. Starts its own app on port 3000 and removes disposable accounts; local services must already be running.pnpm test:vacations:local: Run vacation pagination, complete indicators, literal name search, owner access and simulated database-error recovery against local Supabase. Uses UTC on the server and Los Angeles in the browser to check date handling; starts its own app on port 3000 and cleans disposable records.pnpm test:coverage --run: Request coverage; verify the matching Vitest coverage provider is installed (it is not currently declared inpackage.json).pnpm exec tsc --noEmit: Check application TypeScript types.pnpm test:e2e: Run Playwright E2E suite.pnpm test:dashboard:local: Verify mobile dashboard approvals against isolated local Supabase. The launcher verifies a private database/roles backup before creating fixtures and refuses to proceed if local Storage has files requiring a separate backup. Emails are captured; fixtures are cleaned even after worker termination.pnpm exec playwright test e2e/scenarios/smoke-test.spec.ts --project=unauthenticated: Smoke check without the authenticated setup dependency.pnpm exec playwright install chromium: Install the browser needed by the configured projects.pnpm gen-types: Regeneratetypes/database.type.tsfrom the linked Supabase project; verify the target first. This does not generate from the local database.pnpm supabase:start/pnpm supabase:stop: Start/stop local Supabase services.- No lint script is currently defined. Do not report
pnpm lintas a completed check. env:devandenv:staginguse PowerShell and overwrite.env.local; do not assume they work on macOS/Linux.
Local Environment & Database Work
- User requirement: before any operation that changes Supabase data, schema, Auth, Storage, functions, configuration, or existing local/test environments, create a complete backup of the target environment and verify that it finished successfully. Do not proceed if the backup fails or its coverage cannot be confirmed. Identify the project and environment explicitly; never confuse temporary CI services with production.
- This requirement also applies to migrations, resets, restores, sync/backfill scripts and mutating tests. For a new disposable environment with no existing database to back up, explain that fact and obtain the user's agreement before treating it as an exception. Keep backups outside Git and never expose secrets or personal data in logs.
- Local Supabase project ID is
emb-app; API port55421, Postgres55422, and Studio55423are configured insupabase/config.toml. pnpm devuses the existing environment.pnpm dev:allstarts Supabase but does not inject local credentials; use the local launcher above when targeting the isolated database.- Add schema, RPC, and RLS changes as new migrations; preserve already applied migration history. Check the installed CLI's help before choosing flags.
- Keep SQL RPC contracts, action calls, and generated types aligned. Review type-generation diffs for unrelated schema drift.
pnpm supabase:resetresets the local database and seeds it. Restore, sync, and backfill scripts can also change data; inspect their target and options before running them. These are not routine validation commands for unrelated edits.- Preserve RLS and database authorization alongside application checks. Do not replace a failed user-scoped query with a service-role query merely to bypass a permissions error.
Coding Style & Naming Conventions
- TypeScript
strictmode is enabled; keep changes type-safe. - Use 2-space indentation and match the style of the touched file.
- Prefer
@/absolute imports (configured intsconfig.json). - Tests use descriptive names and domain folders (
test/unit/actions/...). - Route folders are lowercase; route-specific UI belongs in local
_components/directories. - Reuse existing
components/ui/primitives and form/validation patterns (react-hook-form, Zod,lib/validation/schemas.ts). - Use Sonner through the existing
ToasterProviderfor mutation success/error feedback;test/unit/components/mutation-feedback-contract.test.tsenforces this convention. - Preserve server action result contracts and revalidate affected paths after successful mutations. Keep privileged code out of client components.
- Dependency overrides exist in both
package.jsonandpnpm-workspace.yaml; keep them consistent when intentionally changing an override and updatepnpm-lock.yamlwith pnpm.
Authorization & Domain Invariants
- Reuse
lib/auth/admin-check.ts: admin checks useusers.admin = 'admin'; super-admin checks useusers.role = 'super_admin'. They are distinct checks. Use active-user guards for operations requiring an active account. compensatorys.view_allinuser_permissionsgrants read access vialib/auth/compensatory-permissions.ts; it does not grant approval or administrative privileges.- Authenticate and authorize each protected action/handler on the server; hidden controls and proxy redirects are not sufficient authorization. Derive the acting user from the verified session.
- Reuse
lib/auth/request-user.tsfor verified session/profile reads during server rendering. ItsReact.cachescope is one render; never persist sessions or permissions across requests. Pass only the needed name/access flags to navigation components. - Use the request-scoped Supabase server adapter for user operations.
lib/supabase/admin.tsexposes a privileged service-role client for authorized server operations only. - Vacation grants coexist with legacy
users.num_vacations. Preserve compatibility unless the task explicitly changes it; consultlib/vacations/, the current migrations, and relevant unit tests. - The admin home shows mobile approval cards and desktop tables using shared mutation controls. Successful mutations refresh the queues. Vacation pending reads include false/null states, exclude cancellations, retain RLS and require an active administrator; a read error must not look like an empty queue. Keep summary counts aligned with those conditions.
- Vacation approval uses
approve_vacation_with_grantsto approve and consume balance atomically. Preserve duplicate-processing protection and grant restoration on forced cancellation; do not split these into independent client writes. - Preserve grant expiry boundaries and consumption ordering (earliest expiry first). Cover insufficient balance, repeat approval/cancellation, and legacy fallback when modifying these flows.
Dashboard Queries & Loading
- The administrative home uses
actions/get-dashboard-approval-summary.tsfor three HEAD counts; it does not build the full report. Normal users must not load administrative approval queues. - Compensatorios uses
actions/list-compensatory-records.ts: server pagination of 25 rows, filters in Supabase, stable date/ID ordering and owner restriction alongside RLS. Monthly reports require a valid month and fetch the complete filtered period in batches; never calculate report totals from one detail page. - The vacation list uses
actions/list-vacation-records.ts: 25 rows per page, eligible active/non-diplomatic users, owner restriction alongside RLS, and a separate summary over all filtered records. Monthly approved days are fetched in bounded batches; the current month/day is Tokyo and the vacation finish is inclusive.lib/vacations/dates.tskeeps calendar days independent of browser/server timezones. Query failures must reach the route's retry boundary instead of appearing as an empty list. - Calendar events come from
app/api/calendar/route.tsandactions/get-calendar-events.ts, with verified active sessions, real date validation, an exclusive end and at most 62 days per request. Preserve team visibility: vacations use the scoped client/RLS; compensatorios use the existing authorized server-only privileged projection of calendar fields. Do not reuse this projection to expose full compensatory records. lib/calendar/events.tsdistinguishes date-only values from timestamps, converts vacation timestamps to Tokyo dates and makes the inclusive vacation finish exclusive for FullCalendar. Keep the calendar independent of the browser timezone and test overlapping periods and boundary days.- Route
loading.tsxfiles sit below the authenticated dashboard layout and share_components/route-loading.tsx. Keep Spanish status messages, decorative placeholders, reduced-motion support and the navigation available; do not show placeholder counts or actionable controls.
Email & Scheduled Jobs
- Reuse
sendOrCaptureEmailinlib/email/dev-email-outbox.ts, React Email templates, and recipient/URL helpers incomponents/email/utils/email-config.ts. EMAIL_DELIVERY_ENABLED=falsecaptures messages indev_email_outbox;EMAIL_TEST_MODEchanges recipients and does not disable delivery. Mock email delivery in unit tests.- Keep notification failures separate from successful business mutations where the existing flow does so.
- Scheduled endpoints are configured in
vercel.json; preserve bearer-token checks usinglib/cron/verify-cron-secret.tswhen changing cron handlers.
Testing Guidelines
- Unit tests:
test/**/*.{test,spec}.{ts,tsx}with shared setup fromtest/setup.ts(jsdomenvironment). - E2E tests:
e2e/scenarios/*.spec.ts; keepauth.setup.tsdedicated to Playwright setup/login state. - Use Vitest APIs (
vi), Testing Library, and existing mocks intest/mocks/; do not introduce Jest configuration for this suite. - For code changes, run focused tests, then
pnpm test --runbefore opening a PR. Run relevant E2E tests for affected flows (at minimum the unauthenticated smoke command for UI/auth changes), and type/build checks when applicable. - Documentation-only changes need reference/command checks and
git diff --check, not an application build or E2E run. - Playwright reads
BASE_URL(defaulthttp://localhost:3000) but itswebServerstill starts a local dev server on port 3000. Do not assume settingBASE_URLdisables that startup. - Auth setup currently creates only
e2e/.auth/admin.json, usingE2E_ADMIN_EMAILandE2E_ADMIN_PASSWORD; theauthenticated-userproject expectsuser.json, which this setup does not create. Supply dedicated test accounts/state before running those projects; do not rely on hardcoded fallback credentials. - Run mutating E2E scenarios against a controlled test environment. Report missing credentials/services or existing failures explicitly rather than treating skipped checks as passing.
- CI runs unit tests and authenticated invitation/logout/vacation flows against ephemeral local Supabase on PRs and main pushes. A private PostgreSQL/roles backup is verified before mutating tests; the job fails if its initially empty Storage contains objects that would require a separate file backup. Never upload these backups as CI artifacts. The separate E2E job still runs only the
unauthenticatedproject on main pushes, using a local Next.js server; it is not a production deployment check.
Commit & Pull Request Guidelines
- Follow the existing conventional commit pattern:
feat:,fix:,test:,chore:,ci:,perf:,security:. - Keep commit subjects short and imperative.
- PRs should include: purpose, scope, linked issue/context, and local test evidence.
- For UI updates, attach screenshots; for env/schema changes, update the tracked environment template and related Supabase artifacts.
Security & Configuration Tips
- Never commit secrets (
.env.local, API keys, service tokens). - The tracked environment template is
.env.staging.example;.env.exampledoes not currently exist. Document new variables with safe placeholders in the appropriate tracked template. - Keep CI/deployment secrets in GitHub/Vercel settings, not in source files.
- Never commit Supabase service-role keys, auth session JSON, database dumps/backups, or test artifacts containing personal data. Do not print environment file contents or credentials while diagnosing configuration.
