|
| 1 | +--- |
| 2 | +name: tenant-onboarding-portal |
| 3 | +description: Guidance for modifying the tenant onboarding portal in ai-hub-tracking. Use when changing the NestJS backend, React/Vite frontend, mock auth or Keycloak integration, Azure Table Storage behavior, or Playwright end-to-end coverage for tenant onboarding workflows. |
| 4 | +--- |
| 5 | + |
| 6 | +# Tenant Onboarding Portal |
| 7 | + |
| 8 | +Use this skill when working in `tenant-onboarding-portal/`. |
| 9 | + |
| 10 | +## Scope |
| 11 | + |
| 12 | +- Backend: `tenant-onboarding-portal/backend/src/` |
| 13 | +- Frontend: `tenant-onboarding-portal/frontend/src/` |
| 14 | +- Local run tooling: `tenant-onboarding-portal/backend/run-local.sh` |
| 15 | +- Tests: `tenant-onboarding-portal/backend/tests/` |
| 16 | +- Portal infra and deployment wiring: `tenant-onboarding-portal/infra/`, `.github/workflows/` |
| 17 | + |
| 18 | +## Architecture |
| 19 | + |
| 20 | +The portal is a NestJS backend that serves a built React SPA. |
| 21 | + |
| 22 | +- The backend owns API routes, auth validation, Azure Table Storage access, and tfvars generation. |
| 23 | +- The frontend authenticates through `/api/auth/config` and `/api/session`, then calls `/api/...` routes with bearer tokens. |
| 24 | +- Built frontend assets are copied into `tenant-onboarding-portal/backend/frontend-dist/` for deployment and E2E runs. |
| 25 | + |
| 26 | +## Auth Model |
| 27 | + |
| 28 | +- Production-style auth uses Keycloak bearer-token validation. |
| 29 | +- Local and E2E runs can use `PORTAL_AUTH_MODE=mock`. |
| 30 | +- Mock auth should preserve the same authorization boundaries as real auth: |
| 31 | + - authenticated user session |
| 32 | + - admin role checks via `oidc_admin_role` |
| 33 | + |
| 34 | +Prefer adding behavior inside the auth abstraction rather than bypassing route guards or hardcoding UI shortcuts. |
| 35 | + |
| 36 | +## Testing Guidance |
| 37 | + |
| 38 | +- Backend tests use Vitest and Supertest. |
| 39 | +- E2E tests use Playwright against the built frontend served by the backend. |
| 40 | +- Prefer mock auth for portal E2E tests. |
| 41 | +- Keep Playwright selectors semantic: |
| 42 | + - labels |
| 43 | + - button text |
| 44 | + - headings |
| 45 | + - links |
| 46 | + |
| 47 | +Avoid brittle CSS selectors unless no accessible selector exists. |
| 48 | + |
| 49 | +## Storage Guidance |
| 50 | + |
| 51 | +- Azure Table Storage is the main persistence layer. |
| 52 | +- Local tests should continue using the in-memory fallback unless the task explicitly needs Azure storage behavior. |
| 53 | +- Preserve request versioning semantics when changing create/update flows. |
| 54 | + |
| 55 | +## Code Formatting and Linting |
| 56 | + |
| 57 | +Both backend and frontend share the **same Prettier config** at `tenant-onboarding-portal/.prettierrc`: |
| 58 | + |
| 59 | +```json |
| 60 | +{ |
| 61 | + "semi": true, |
| 62 | + "singleQuote": true, |
| 63 | + "trailingComma": "all", |
| 64 | + "printWidth": 100, |
| 65 | + "tabWidth": 2 |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +- **Prettier** formats all TypeScript source files. Always run `format` after making code changes. |
| 70 | +- **ESLint** enforces code quality. Both use ESLint 9 flat config with `typescript-eslint` and `eslint-config-prettier` (disables formatting rules that conflict with Prettier). |
| 71 | +- The backend config is at `backend/eslint.config.mjs`; the frontend config is at `frontend/eslint.config.js`. |
| 72 | +- **Pre-commit hooks** run ESLint + Prettier check automatically on changed `backend/` and `frontend/` TypeScript files. A commit will be rejected if lint or format checks fail. |
| 73 | + |
| 74 | +**After every code change**, run from the respective directory: |
| 75 | + |
| 76 | +```bash |
| 77 | +npm run format # auto-format with Prettier |
| 78 | +npm run lint # ESLint check (or lint:fix to auto-fix) |
| 79 | +``` |
| 80 | + |
| 81 | +## JSDoc Documentation |
| 82 | + |
| 83 | +JSDoc is **mandatory** for every function and method in the codebase. This applies to: |
| 84 | + |
| 85 | +- Every exported function or class method in the backend and frontend |
| 86 | +- Every internal (non-exported) function or class method |
| 87 | +- React component functions |
| 88 | + |
| 89 | +JSDoc is **not required** for: |
| 90 | + |
| 91 | +- Inline arrow function callbacks (e.g. inside `useEffect`, `map`, event handlers) |
| 92 | +- Test files (`*.spec.ts`) |
| 93 | + |
| 94 | +### Required JSDoc structure |
| 95 | + |
| 96 | +```ts |
| 97 | +/** |
| 98 | + * One-sentence description of what the function does. |
| 99 | + * |
| 100 | + * @param paramName - Description of the parameter. No `{Type}` — TypeScript owns types. |
| 101 | + * @returns Description of the return value when non-void. |
| 102 | + * @throws Description of the error when the function explicitly documents a failure path. |
| 103 | + */ |
| 104 | +``` |
| 105 | + |
| 106 | +Rules: |
| 107 | +- A **description sentence is required** on every JSDoc block — never leave it empty. |
| 108 | +- Use `@param name - description` format (dash separator, no type annotation). |
| 109 | +- Add `@returns` when the function returns a meaningful non-void value. |
| 110 | +- Add `@throws` only when you explicitly document an error condition. |
| 111 | +- Do **not** add `{Type}` annotations in `@param` or `@returns` — TypeScript provides the types. |
| 112 | + |
| 113 | +### ESLint enforcement |
| 114 | + |
| 115 | +Both packages enforce JSDoc via `eslint-plugin-jsdoc`. The relevant rules are set to **error**: |
| 116 | + |
| 117 | +- `jsdoc/require-jsdoc` on `FunctionDeclaration` and `MethodDefinition` |
| 118 | +- `jsdoc/require-description` — blocks without a description sentence fail lint |
| 119 | +- `jsdoc/require-param` and `jsdoc/require-returns` — missing tags for documented params/returns fail lint |
| 120 | +- `jsdoc/require-param-type` and `jsdoc/require-returns-type` are **off** (TypeScript owns types) |
| 121 | + |
| 122 | +Running `npm run lint` will report any missing JSDoc blocks. |
| 123 | + |
| 124 | +## NestJS Backend Best Practices |
| 125 | + |
| 126 | +- Use NestJS modules, controllers, services, and guards — do not put logic directly in `main.ts`. |
| 127 | +- Dependency injection: always inject services through constructor parameters with proper NestJS providers. |
| 128 | +- Use `@Injectable()` for services, `@Controller()` for route handlers, `@Module()` for feature modules. |
| 129 | +- Decorate request DTOs with class-validator decorators and use `ValidationPipe` globally. |
| 130 | +- Keep controller methods thin — delegate business logic to service classes. |
| 131 | +- Use `@UseGuards()` for auth enforcement; never skip guards with inline token inspection in controllers. |
| 132 | +- `no-explicit-any` is a lint warning (not error) — prefer typed interfaces and DTOs instead. |
| 133 | + |
| 134 | +## React Frontend Best Practices |
| 135 | + |
| 136 | +- State management uses Zustand stores — keep store slices focused and co-located with feature code. |
| 137 | +- Routing uses TanStack Router — define routes in files expected by the router plugin. |
| 138 | +- Use `@bcgov/design-system-react-components` components before reaching for custom HTML or Bootstrap directly. |
| 139 | +- Prefer React Query or similar data-fetching patterns over raw `useEffect` for async state. |
| 140 | +- `no-explicit-any` is a lint warning — always type API responses and component props. |
| 141 | +- React Hooks plugin enforces rules of hooks — do not call hooks conditionally. |
| 142 | + |
| 143 | +## Implementation Rules |
| 144 | + |
| 145 | +1. Preserve the `/api/...` contract used by the React SPA unless the task explicitly requires coordinated frontend and backend changes. |
| 146 | +2. Keep auth behavior centralized in the backend token validator and frontend auth store. |
| 147 | +3. Prefer environment-driven test modes over special-case test-only code paths scattered through controllers and components. |
| 148 | +4. When adding E2E coverage, verify the full user journey end to end instead of only checking page load. |
| 149 | +5. When changing tenant request fields, update validation, API types, frontend form handling, and tfvars generation together. |
| 150 | +6. **Always run `npm run format` after every code change** — the pre-commit hook will reject unformatted code. |
| 151 | + |
| 152 | +## Useful Commands |
| 153 | + |
| 154 | +```bash |
| 155 | +# Backend |
| 156 | +cd tenant-onboarding-portal/backend |
| 157 | +npm run format # auto-format source with Prettier |
| 158 | +npm run lint # ESLint check |
| 159 | +npm run lint:fix # ESLint auto-fix |
| 160 | +npm run format:check # Prettier dry-run (used by pre-commit) |
| 161 | +npm run build |
| 162 | +npm test |
| 163 | +npm run e2e |
| 164 | + |
| 165 | +# Frontend |
| 166 | +cd tenant-onboarding-portal/frontend |
| 167 | +npm run format # auto-format source with Prettier |
| 168 | +npm run lint # ESLint check |
| 169 | +npm run lint:fix # ESLint auto-fix |
| 170 | +npm run format:check # Prettier dry-run (used by pre-commit) |
| 171 | +npm run build |
| 172 | +npm test |
| 173 | +``` |
0 commit comments