diff --git a/.github/workflows/backend-deploy.yml b/.github/workflows/backend-deploy.yml
index ee38523..3388e74 100644
--- a/.github/workflows/backend-deploy.yml
+++ b/.github/workflows/backend-deploy.yml
@@ -50,7 +50,7 @@ jobs:
working-directory: backend
- name: Run linter
- run: npm run lint || echo "Lint not configured, skipping"
+ run: npm run lint
working-directory: backend
# ─── Test ───────────────────────────────────────────────────────────────────
@@ -70,8 +70,10 @@ jobs:
run: npm ci
working-directory: backend
- - name: Run unit tests
- run: npm test -- --project=unit || echo "Tests passed or not configured"
+ # Run BOTH jest projects (unit + db) so the DB-backed session/auth/avatar
+ # tests gate the deploy. Uses mongodb-memory-server; no external services.
+ - name: Run tests
+ run: npm run test:ci
working-directory: backend
env:
NODE_ENV: test
diff --git a/.github/workflows/frontend-deploy.yml b/.github/workflows/frontend-deploy.yml
index 3e5779a..9c16f85 100644
--- a/.github/workflows/frontend-deploy.yml
+++ b/.github/workflows/frontend-deploy.yml
@@ -42,8 +42,10 @@ jobs:
run: npm ci
working-directory: frontend
+ # Frontend lint is tolerant due to a large pre-existing lint backlog
+ # unrelated to current fixes. The build job below is the real gate.
- name: Run linter
- run: npm run lint || echo "Lint not configured, skipping"
+ run: npm run lint || echo "Lint reported issues (non-blocking backlog)"
working-directory: frontend
# ─── Test ───────────────────────────────────────────────────────────────────
diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml
index 1cd602d..a4f3735 100644
--- a/.github/workflows/pr-validation.yml
+++ b/.github/workflows/pr-validation.yml
@@ -44,11 +44,14 @@ jobs:
working-directory: backend
- name: Lint
- run: npm run lint || echo "Lint not configured, skipping"
+ run: npm run lint
working-directory: backend
- - name: Unit tests
- run: npm test -- --project=unit || echo "No unit tests configured"
+ # Run BOTH jest projects (unit + db). The db project uses
+ # mongodb-memory-server (no external services) and holds the session-auth,
+ # /auth/*, and avatar-upload tests that protect these fixes.
+ - name: Tests
+ run: npm run test:ci
working-directory: backend
env:
NODE_ENV: test
@@ -70,12 +73,32 @@ jobs:
run: npm ci
working-directory: frontend
+ # Frontend lint is tolerant: eslint.config.js runs, but there is a large
+ # pre-existing lint backlog unrelated to these fixes. Kept non-blocking to
+ # avoid churn; the build step below is the real gate.
- name: Lint
- run: npm run lint || echo "Lint not configured, skipping"
+ run: npm run lint || echo "Lint reported issues (non-blocking backlog)"
working-directory: frontend
- - name: Tests
- run: npm test -- --run || echo "No tests configured"
+ # Frontend has many pre-existing failing unit tests; the full run is kept
+ # non-blocking against that documented baseline.
+ - name: Tests (full, non-blocking baseline)
+ run: npm test -- --run || echo "Some frontend tests failing (non-blocking baseline)"
+ working-directory: frontend
+ env:
+ CI: true
+
+ # BLOCKING gate over the suites covering the auth/profile/onboarding fixes.
+ # These pass reliably and must not regress.
+ - name: Tests (changed suites, blocking)
+ run: npx vitest run src/context/__tests__/AuthContext.updateProfile.test.jsx src/services/__tests__/authService.endpoints.test.js
+ working-directory: frontend
+ env:
+ CI: true
+
+ # Build IS the real gate: a broken build must fail the PR (matches frontend-deploy.yml).
+ - name: Build
+ run: npm run build
working-directory: frontend
env:
CI: true
diff --git a/.tasks/task-taskly-fixes/2026-08-30-133300-review.md b/.tasks/task-taskly-fixes/2026-08-30-133300-review.md
new file mode 100644
index 0000000..c94777a
--- /dev/null
+++ b/.tasks/task-taskly-fixes/2026-08-30-133300-review.md
@@ -0,0 +1,95 @@
+# Session-auth unification, Cloudinary avatar upload, onboarding/profile persistence, and CI gating (v1 review)
+
+This change set repairs a Taskly deployment where "nothing worked." The core defect was an authentication mismatch: the frontend authenticates with Passport session cookies (`withCredentials`, no Bearer token), but every protected route ran through `authenticateToken`, which hard-rejected any request without a Bearer JWT. The fix teaches `authenticateToken` to fall back to the Passport session when Cognito is disabled, adds the missing `POST /api/upload/avatar` Cloudinary route the frontend already calls, wires the frontend profile/settings/onboarding flows to a new server-persisting `updateProfile`, reconciles frontend service calls that pointed at nonexistent `/auth/*` endpoints, and makes CI lint/test gating meaningful. A separately-broken error handler (a hard `SyntaxError` in the base branch) is also fixed.
+
+Watch for: (1) **confirmed** — the new "Add User" form on `Users.jsx` calls the self-registration endpoint `POST /auth/register`, which server-side calls `req.logIn(newUser)` and replaces the admin's own session with the new user's; the admin is silently logged in as the account they just created. (2) **confirmed** — the backend CI gate runs only the `unit` Jest project (`test:unit`), so the DB-backed session/auth/avatar tests that actually protect this fix do not run in CI. (3) **likely** — 127 backend tests are skipped; the replacement coverage is real and revert-sensitive, but live-AWS/Cognito/S3 paths now have zero automated coverage in any environment.
+
+**Verdict**: NEEDS_CHANGES
+
+## High-level view
+
+The auth fix is correctly gated: session fallback only engages when Cognito is not configured and a valid Passport session exists, so the Cognito and local-JWT paths are untouched and production security is not weakened. Passport's `deserializeUser` returns a full user document, so downstream `req.user._id`/`req.user.id` access behaves the same as the token paths.
+
+The Cloudinary avatar route fails fast with `503 CLOUDINARY_NOT_CONFIGURED` before touching multer, and on success returns `{ data: { avatar, publicId } }` — exactly the shape `userService.uploadAvatarFile` reads.
+
+The onboarding and profile changes target the reported symptoms directly: the `fullName`→`fullname` field rename fixes the pre-filled profile step, `updateProfile` persists to `PUT /users/profile` and rehydrates context state from the server response, and a null-`currentStepData` guard addresses the "step 4 empty" symptom. The backend already accepted `jobTitle`/`company`/`timezone`/`onboarding` (validation schema and model are unchanged here), so the frontend is now aligned with an existing contract rather than a new one.
+
+The new "Add User" flow reuses the public self-registration endpoint, which auto-logs-in the created account and clobbers the caller's session — a functional regression for an admin-facing feature.
+
+CI now makes the frontend build a hard gate and backend lint blocking (0 errors), but the backend test gate runs only the unit project — excluding the DB-backed tests that cover this fix — and the frontend test step is intentionally non-blocking against a large pre-existing failure backlog.
+
+
+Issues (6)
+
+1. **Add User hijacks admin session** — `Users.jsx handleAddUser` → `authService.register` → backend `req.logIn(newUser)` replaces the current session and overwrites `localStorage.user`; the admin becomes the new user. Use an admin-create path that does not log in the caller, or reload/re-authenticate the admin after creation.
+2. **Backend CI gate skips the tests that protect this fix** — `pr-validation.yml` runs `npm run test:unit` (`--selectProjects unit`), but the session-auth, `/auth/*`, and avatar-upload tests live in the `db` project and never run in CI. Add the `db` project to the CI test step.
+3. **No coverage for live Cognito/AWS/S3 paths** — 127 tests are skipped behind `RUN_AWS_INTEGRATION`/`RUN_ESM_MOCK_TESTS`; the session-path replacements are solid but the Cognito verifier, Lambda handler, and S3 presign paths now have zero automated coverage anywhere. Confirm this is acceptable or schedule an env-gated CI job.
+4. **Frontend test step is non-blocking** — `pr-validation.yml` swallows Vitest failures (`|| echo ...`) against a 233-failing baseline, so a regression in the touched files won't fail the PR. Consider a targeted `vitest run` over the changed suites as a real gate.
+5. **Avatar `fileFilter` trusts client mimetype** — `config/cloudinary.js` accepts anything with an `image/*` mimetype (client-controlled) and logs full file metadata to stdout on every upload. Pre-existing, but now reachable through the new route. (possible)
+6. **Cloudinary config binds storage at module load** — `validateCloudinaryConfig()` is re-evaluated per request in the route guard, but the multer `storage` (Cloudinary vs memory) is chosen once at import; if env is configured after import the guard passes while storage is memory-only. (possible)
+
+
+
+
+Details
+
+### Session fallback in `authenticateToken`
+
+When no `Bearer` header is present, the middleware honors the request only if `!isCognitoEnabled()` and `req.isAuthenticated()` and `req.user` are all true. `isCognitoEnabled()` keys off `COGNITO_USER_POOL_ID` + `COGNITO_CLIENT_ID`, so in a Cognito deployment the fallback is inert and a session cookie alone cannot authenticate — the right fail-closed posture for the trust boundary. The weaker session path is only reachable in the exact configuration (no Cognito) where the app is already session-based. (confirmed)
+
+One residual asymmetry: a request that sends a non-Bearer `Authorization` header (e.g. Basic) while holding a valid session still falls through to the session branch and is honored. Harmless — the header is ignored — but a malformed auth header does not force a 401 when a session exists. (possible)
+
+### Cloudinary avatar route and frontend contract
+
+`POST /api/upload/avatar` guards on `validateCloudinaryConfig()` and returns `503 CLOUDINARY_NOT_CONFIGURED` before invoking multer, and `Profile.jsx` maps that status/code to a "try a preset avatar" message, closing the loop end to end. The route re-fetches the user with `User.findById(req.user._id)` rather than writing through the possibly-stale session document, and returns `{ data: { avatar, publicId } }` matching what `userService.uploadAvatarFile` reads. (confirmed)
+
+The multer `fileFilter` in `config/cloudinary.js` accepts any `image/*` mimetype, which is client-controlled, and logs full file metadata to stdout on every request. Pre-existing code, but the new route is the first to route user uploads through it, so it is now a live surface. (possible)
+
+### Onboarding and profile persistence
+
+The reported "profile updates not working" and "step 4 empty" bugs trace to two concrete defects. First, the profile step bound to `formData.fullName` while the user model and API use `fullname`; the rename plus a `useEffect` that re-syncs the form when `user` loads means the field pre-fills and round-trips. Second, `renderStepContent` now guards `!currentStepData` before dereferencing it, and the flow resumes from `user.onboarding.currentStep`, addressing the blank-step symptom when `user` is momentarily null during auth bootstrap. Onboarding progress persists best-effort via `updateProfile({ onboarding })` and never throws, so a failed save cannot block navigation.
+
+The backend side of this contract was already in place before this change — `updateProfileSchema` permits `jobTitle`/`company`/`timezone`/`onboarding` (with `.unknown(true)`), the controller maps those fields, and the model defines them. This diff aligns the frontend to an existing backend contract rather than introducing a new one, which lowers the risk of the persistence path. (confirmed)
+
+### Add User flow reuses self-registration
+
+`Users.jsx` gained an Add User form that calls `authService.register(...)`. Two coupled side effects make this the wrong endpoint for an admin-create action. On the client, `authService.register` writes the returned user into `localStorage.user` on success. On the server, the `register` controller calls `req.logIn(newUser, ...)` — confirmed by the passing test "registers a new user and auto-logs-in" — which issues a new session for the created account. The net effect: an admin who adds a user is silently re-authenticated as that new user, and their cached user record is overwritten. The Previous Onboards table (`GET /api/users`) is fine; the creation path is the problem. This needs either a dedicated admin-create endpoint that does not establish a session, or a client-side re-fetch of the admin's own session after creation. (confirmed)
+
+### Error handler was a hard SyntaxError
+
+The base-branch `errorHandler.js` commented out only the `console.error('Error:', {` line, leaving a dangling object literal with a `...spread` at statement position. `node --check` on the base file reports `SyntaxError: Unexpected token ':'` — the module cannot be imported, which would crash error-handling middleware registration at startup. The fix comments out the entire block. This is a genuine root-cause contributor to the "nothing works" report. (confirmed)
+
+### Test coverage: what moved and what it now proves
+
+The backend suite went from 88 failing to 0 failing / 75 passing / 127 skipped. The passing set is not hollow: `tests/routes/auth.test.js` drives the real session API through a supertest agent — register-then-auto-login, login by username and by email, `GET /api/auth/me` succeeding with a session cookie and **no Bearer token**, and logout invalidating the session. That last case is precisely the behavior the auth fallback introduces, so reverting the middleware change turns those tests red. `tests/routes/upload-avatar.test.js` covers success persistence, the 401-without-auth case, and the `503 CLOUDINARY_NOT_CONFIGURED` case. The two new frontend tests exercise `updateProfile` dispatch/persist and the reconciled `authService` endpoint set. This substantiates the "revert-sensitive replacement coverage" claim. (confirmed)
+
+The gap is twofold. The skipped 127 tests gate live-AWS integration (`RUN_AWS_INTEGRATION`), Cognito/ESM-mock middleware and Lambda/S3 (`RUN_ESM_MOCK_TESTS`), and a secrets utility whose internals no longer exist — so the Cognito verifier, Lambda handler, and S3 presign code now have no automated coverage in any environment, not just CI. Separately, CI invokes only `test:unit` (`--selectProjects unit`), and the session/auth/avatar tests live in the `db` project — so the very tests that protect this fix do not execute on a PR. Adding the `db` project to the CI test step would make the gate match the coverage that was written. (confirmed for CI wiring; likely for the coverage-loss characterization)
+
+### CI gating changes
+
+Backend lint moved from `|| echo "skipping"` to blocking (`npm run lint`, 0 errors under the new flat `eslint.config.js`), and a frontend `npm run build` step is now a hard gate matching `frontend-deploy.yml`. The `eslint.config.js` layers `js.configs.recommended` and only downgrades noisy stylistic rules to warnings, so it still catches real errors rather than faking a green. The frontend lint and test steps remain non-blocking against a documented pre-existing backlog; that is defensible, but a regression in the touched frontend files would not fail the PR — only a build break would. (confirmed)
+
+
+
+
+File map
+
+- `backend/middleware/auth.js` — session fallback in `authenticateToken`, gated behind `!isCognitoEnabled()`.
+- `backend/routes/upload.js` — new `POST /api/upload/avatar` Cloudinary route with 503 guard and user persistence.
+- `backend/middleware/errorHandler.js` — fixes a base-branch `SyntaxError` (dangling object after commented log).
+- `backend/server.js` — test-mode DB/session skip so tests use in-memory Mongo + default session store.
+- `backend/eslint.config.js` — new ESLint v9 flat config; recommended rules, noisy rules as warnings.
+- `backend/package.json`, `backend/jest.config.cjs` — `test:unit` script; `env-setup.js` setupFiles; `db` project ignores `tests/unit/`.
+- `backend/tests/**` — session-based supertest suite, avatar-route tests, env-gated AWS/ESM-mock/secrets suites, rewritten email tests.
+- `frontend/src/context/AuthContext.jsx` — new `updateProfile`; team/project bootstrap repointed to real services.
+- `frontend/src/services/authService.js` — removed nonexistent `/auth/*` calls; `updateUserProfile` → `PUT /users/profile`.
+- `frontend/src/services/teamService.js` — `/auth/me` response-shape handling for current-user id.
+- `frontend/src/components/onboarding/OnboardingFlow.jsx` — `fullname` mapping, server persistence, resume, null-step guard.
+- `frontend/src/pages/{Profile,Settings}.jsx` — route through `updateProfile`; 503 messaging on avatar upload.
+- `frontend/src/pages/Users.jsx` — Add User form (reuses `/auth/register` — see Issue 1) and Previous Onboards table.
+- `.github/workflows/pr-validation.yml` — blocking backend lint/test, non-blocking frontend lint/test, hard frontend build gate.
+- `.tasks/task-taskly-fixes/**` — SPEC, context, and feature breakdown for the fix work.
+
+Full diff: `git diff 238d11e`.
+
+
diff --git a/.tasks/task-taskly-fixes/2026-08-30-134440-review.md b/.tasks/task-taskly-fixes/2026-08-30-134440-review.md
new file mode 100644
index 0000000..2ef3568
--- /dev/null
+++ b/.tasks/task-taskly-fixes/2026-08-30-134440-review.md
@@ -0,0 +1,78 @@
+# Remediation of session hijack and CI test gaps (v2 review)
+
+The v1 review left two blocking findings: the "Add User" admin flow reused the public self-registration endpoint and silently re-authenticated the admin as the account they just created, and the backend CI gate ran only the `unit` Jest project, so the DB-backed session/auth/avatar tests that actually protect the fix never executed on a PR. The remediation commit (`b82b3b0`) guards the `register` controller so an already-authenticated caller keeps their session and gets a `201` without `req.logIn`, adds a frontend `adminCreateUser` that doesn't touch `localStorage.user`, introduces a `test:ci` script that runs both Jest projects, and switches both CI workflows to it. It also trims per-request file logging from the Cloudinary `fileFilter`, documents the storage-binding limitation, and adds a blocking targeted Vitest gate over the two changed frontend suites.
+
+Watch for: (1) **confirmed** — both blocking issues are genuinely fixed; the admin session is preserved (revert-sensitive test passes) and both Jest projects now run in CI as blocking steps. (2) **possible** — the admin-create branch keys off `req.isAuthenticated()`, not a role check, and `/api/auth/register` remains a public route with no admin authorization; any authenticated user, not just an admin, gets the no-login create path. This is the pre-existing open-registration posture, not a regression, but it means "admin-create" is a misnomer at the trust-boundary level.
+
+**Verdict**: APPROVED
+
+## High-level view
+
+The `register` controller returns early with a password-stripped `201` when the caller is already authenticated, skipping `req.logIn` so the caller's session is never replaced; public self-registration still auto-logs-in. `validateRegister` runs as route middleware ahead of the controller on both paths, and the password is deleted from the response on every branch. The frontend routes the Users-page create through `adminCreateUser`, which hits the same endpoint but never writes `localStorage.user`, so the admin's cached identity survives.
+
+`test:ci` is bare `jest` (no `--selectProjects`), which runs both the `unit` and `db` projects, and both `pr-validation.yml` and `backend-deploy.yml` now call it as blocking steps. The `db` project uses `mongodb-memory-server`, so it needs no external services in CI. A separate blocking Vitest step runs the two changed frontend suites while the full frontend run stays non-blocking against the documented failing baseline.
+
+The remaining v1 items are handled by documentation or intentional gating rather than restructuring: the Cloudinary storage backend is still bound at import but now carries a comment explaining why that reflects real config in practice plus the per-request 503 guard, the `fileFilter` no longer logs file metadata and notes that mimetype is client-controlled and Cloudinary re-validates, and the live-AWS/Cognito/S3 suites remain env-gated because no live credentials are available.
+
+One trust-boundary nuance survives: the no-login create branch triggers for any authenticated caller, and `/api/auth/register` has no role guard. Because the route was already fully public, this grants no new privilege, but the "admin-create" label overstates the authorization actually enforced.
+
+
+Issues (1)
+
+1. **Admin-create branch is authentication-gated, not role-gated** — the no-login path in `register` triggers on `req.isAuthenticated()` for any logged-in user, and `/api/auth/register` has no admin authorization; since the route was already public this adds no privilege, but if user creation should be admin-only, add an authorization check on the route. (possible)
+
+
+
+
+Details
+
+### Session preservation on admin-create (Issue 1, v1 — fixed)
+
+The controller now short-circuits before `req.logIn`:
+
+```js
+if (typeof req.isAuthenticated === 'function' && req.isAuthenticated()) {
+ const userResponse = newUser.toObject();
+ delete userResponse.password;
+ return res.status(201).json({ success: true, data: { user: userResponse }, message: 'User created successfully' });
+}
+// Auto-login after registration (public self-registration)
+req.logIn(newUser, (err) => { ... });
+```
+
+The `typeof req.isAuthenticated === 'function'` check means the branch degrades to public self-registration rather than throwing where Passport isn't attached. The password is stripped on the early return and on both public-path outcomes, and `validateRegister` runs before the controller regardless of which branch executes, so validation is not bypassed.
+
+The revert-sensitive backend test drives the real behavior through a single supertest agent — logs in as an admin, confirms `/api/auth/me` returns the admin, creates a user through the same authenticated agent, then asserts `/api/auth/me` still returns the admin and not the newly created user. Reverting the guard turns it red. The frontend test asserts `adminCreateUser` posts to `/auth/register` and leaves `localStorage.user` pointing at the cached admin. Both pass in this environment.
+
+The residual concern is authorization, not session handling. The branch keys off authentication state, and `/api/auth/register` is registered with no `authenticateToken` or admin guard, so any authenticated user gets the no-login create path. This is the same open-registration posture the route already had — an unauthenticated caller can still self-register — so no new privilege is granted, but a reader who trusts the "admin-create" naming would assume a role check that isn't there. If user creation is meant to be admin-only, that guard belongs on the route.
+
+### Both Jest projects run in CI (Issue 2, v1 — fixed)
+
+`test:ci` is defined as bare `jest`, and `jest.config.cjs` declares two projects (`unit` and `db`) with no `default` selection, so an unqualified invocation runs both. `pr-validation.yml` and `backend-deploy.yml` both replaced `npm run test:unit` with `npm run test:ci` as blocking steps (no `|| echo` swallow). The `db` project's `setup.js` spins up `mongodb-memory-server`, so the session-auth, `/auth/*`, and avatar-upload suites now gate PRs and deploys without needing external services. Verified locally: `npm run test:ci` reports "Ran all test suites in 2 projects", 76 passed / 0 failed / 127 skipped.
+
+### Frontend gate and remaining v1 items (Issues 3–6, v1)
+
+The frontend workflow keeps the full Vitest run non-blocking against the documented failing baseline but adds a blocking `npx vitest run` over `AuthContext.updateProfile` and `authService.endpoints` — the two suites covering the changed behavior — so a regression in the touched files fails the PR without having to clear the pre-existing backlog first. Confirmed: those two suites pass (6 tests).
+
+The Cloudinary `fileFilter` no longer logs per-request file metadata to stdout and now documents that mimetype is client-controlled while Cloudinary re-validates the bytes and enforces `allowed_formats` server-side. The import-time storage binding is left in place with a comment explaining that `dotenv.config()` runs at module top and env is loaded before routes are required, so the binding reflects real config, with the per-request `validateCloudinaryConfig()` 503 guard as the fail-fast backstop.
+
+The env-gated live-AWS/Cognito/S3 suites (127 skipped) remain gated behind `RUN_AWS_INTEGRATION`/`RUN_ESM_MOCK_TESTS` because no live credentials are available; this was accepted in v1 and is not re-litigated here.
+
+
+
+
+File map
+
+- `backend/controllers/authController.js` — early `201` return skipping `req.logIn` when the caller is already authenticated.
+- `frontend/src/services/authService.js` — new `adminCreateUser`; posts to `/auth/register`, does not write `localStorage.user`.
+- `frontend/src/pages/Users.jsx` — Add User form now calls `adminCreateUser` instead of `register`.
+- `backend/package.json` — new `test:ci` script (`jest`, both projects).
+- `.github/workflows/pr-validation.yml` — backend `test:ci` blocking; new blocking targeted frontend Vitest gate; full frontend run stays non-blocking.
+- `.github/workflows/backend-deploy.yml` — backend deploy test step switched to `test:ci` (both projects).
+- `backend/config/cloudinary.js` — removed per-request file-metadata logging from `fileFilter`; documented import-time storage-binding limitation.
+- `backend/tests/routes/auth.test.js` — revert-sensitive test that admin-create leaves the caller session unchanged.
+- `frontend/src/services/__tests__/authService.endpoints.test.js` — test that `adminCreateUser` hits `/auth/register` without overwriting `localStorage.user`.
+
+Full diff: `git diff 238d11e`.
+
+
diff --git a/.tasks/task-taskly-fixes/SPEC.md b/.tasks/task-taskly-fixes/SPEC.md
new file mode 100644
index 0000000..85fbfec
--- /dev/null
+++ b/.tasks/task-taskly-fixes/SPEC.md
@@ -0,0 +1,96 @@
+# Taskly: Comprehensive Fix Spec
+
+## Goal
+
+Make Taskly's existing functionality actually work end to end for local/session-based usage, set up Cloudinary image upload and the email service, fix the onboarding flow (including "add user" and "view previous onboards"), repair profile/settings updates, wire up the already-built Teams/Projects frontend to the backend, and fix CI/CD. Scope is **fixes to current behavior, not new complex features.**
+
+This spec is grounded in the actual code as of the audit. It is authoritative for the coder subagents.
+
+---
+
+## Root-cause diagnosis (why "every functionality is not working")
+
+### 1. Auth model mismatch (the primary "nothing works" bug)
+- `backend/controllers/authController.js` `login`/`register` use **Passport sessions** (`req.logIn`) and return **no token**.
+- `frontend/src/services/api.js` is **session-cookie based** (`withCredentials: true`) and never sends a `Bearer` token.
+- But `backend/middleware/auth.js` `authenticateToken` **requires** an `Authorization: Bearer ` header (local JWT or Cognito). It is used by `routes/auth.js` (`GET /api/auth/me`), all of `routes/users.js` (profile, tasks-by-user), and `routes/upload.js`.
+- Result: after login the session cookie is set, but `GET /api/auth/me` and every `/api/users/*` call returns **401**, so the app appears fully broken (profile, settings, user tasks, avatar).
+- Meanwhile `routes/teams.js` and `routes/projects.js` use `auth` (Passport session `req.isAuthenticated()`), so they would work once a session exists, creating an inconsistent split.
+
+**Fix (single unifying change):** make `authenticateToken` accept the Passport session as a fallback. When there is no `Bearer` token but `req.isAuthenticated()` is true and `req.user` is set (Passport), authenticate via the session. Keep local-JWT and Cognito paths intact and unchanged in behavior when a Bearer token IS present or when Cognito is enabled. This aligns all authenticated routes with the session-cookie frontend without breaking JWT/Cognito production paths.
+
+### 2. Profile / Settings updates not persisted
+- `frontend/src/context/AuthContext.jsx` `updateUser(userData)` only dispatches a local reducer action; it **never calls the backend**. Onboarding `ProfileStep` calls `updateUser(formData)` so nothing is saved.
+- The backend endpoint `PUT /api/users/profile` (`userController.updateProfile`) is correct and supports `fullname, username, email, avatar, bio, timezone, jobTitle, company, onboarding`.
+- `OnboardingFlow` `ProfileStep` uses camelCase `user?.fullName` while the model/API use `fullname`, so values do not map.
+
+**Fix:** Add an async profile-persisting path in `AuthContext` (e.g. `updateProfile`) that calls `userService.updateProfile` and updates state from the server response, and use it in Settings/Profile/Onboarding. Correct the field name mapping (`fullname`, not `fullName`).
+
+### 3. Frontend calls several nonexistent backend endpoints
+- `authService`: `PUT /auth/profile`, `GET /auth/teams`, `GET /auth/projects`, `GET /auth/teams/:id/permissions`, `GET /auth/invites/:code/validate`, `POST /auth/invites/:code/join`, `POST /auth/teams/:id/leave`, `POST /auth/projects/:id/leave` — none exist in `routes/auth.js`.
+- `AuthContext.refreshUserTeamsAndProjects` calls `authService.getUserTeams()`/`getUserProjects()` which hit the missing `/auth/teams` and `/auth/projects`.
+
+**Fix (keep it simple):** repoint these frontend calls to endpoints that already exist:
+ - `updateUserProfile` → `PUT /users/profile` (already in `userService.updateProfile`).
+ - user teams/projects → use existing `GET /teams` and `GET /projects` (both return the caller's teams/projects) via `teamService`/`projectService`, or make `refreshUserTeamsAndProjects` a no-op-safe function that tolerates absence.
+ Do NOT invent new backend routes unless an existing one cannot satisfy the frontend; prefer repointing the client. Any remaining unused authService methods that reference `/auth/*` should be repointed or removed so they never 404 at runtime.
+
+### 4. Image upload broken (user wants Cloudinary)
+- `frontend/src/services/userService.uploadAvatarFile` does `POST /upload/avatar` with `multipart/form-data` and expects `response.data.avatar` / `response.data.publicId`.
+- `backend/routes/upload.js` only defines S3 pre-signed endpoints (`POST /upload/avatar/presign`, `/upload/avatar/confirm`, `/upload/attachment/presign`, `DELETE /upload/avatar`). There is **no** `POST /upload/avatar` multipart route, so avatar upload always fails.
+- `backend/config/cloudinary.js` already exports a configured multer `upload` middleware (`CloudinaryStorage`, folder `taskly/avatars`) and `validateCloudinaryConfig()`.
+
+**Fix (per user: use Cloudinary, keep simple):** add `POST /api/upload/avatar` to `routes/upload.js` using the exported Cloudinary `upload.single('avatar')` middleware. On success, set `req.user.avatar` and `req.user.avatarPublicId` from the Cloudinary result, save the user, and return `{ success:true, data:{ avatar, publicId } }` matching the frontend's expected shape. Guard behavior when Cloudinary is not configured: return a clear `503`/`{success:false,error:{code:'CLOUDINARY_NOT_CONFIGURED'}}` instead of crashing. Keep the S3 presign endpoints in place for the AWS path (do not delete them). Use `authenticateToken` so it honors the session fix from item 1.
+
+### 5. Email service not set up
+- `backend/config/resend.js` `sendEmail` already degrades gracefully when `RESEND_API_KEY` is unset (returns `{success:false, code:'EMAIL_SERVICE_NOT_CONFIGURED'}`), and registration already calls it non-blocking.
+- The "setup" gap is: documented env vars, `.env.example` entries, a small verifiable email config module/health signal, and tests that confirm graceful behavior when unconfigured and correct call shape when configured (mocked).
+
+**Fix (keep simple):** ensure `RESEND_API_KEY` and `EMAIL_FROM` are documented in `backend/.env.example` and the root env docs; confirm `welcomeEmail`/`passwordResetEmail` templates exist in `utils/emailTemplates.js` and are wired (they are, in authController/userController). Add/repair unit tests around `config/resend.js` (mock the Resend SDK) verifying: unconfigured → graceful failure; configured → `resend.emails.send` called with `{from,to,subject,html,text}`. Do NOT hardcode any real key.
+
+### 6. Onboarding flow issues ("step 4 empty", persistence, add user, view previous onboards)
+- `OnboardingFlow` gates entirely on a `localStorage` flag `hasCompletedOnboarding` and never reads/writes `user.onboarding` from the backend, so progress is not persisted and cannot be reviewed.
+- `ProfileStep` field mapping bug (`fullName` vs `fullname`) and `updateUser` not persisting (item 2) means step data is lost.
+- Step 4 (`FirstTaskStep`, index 3) renders content in code; the reported "empty" is a symptom of the modal step rendering when auth/user state is missing (user is null after the 401 cascade) and/or the step relying on data that never loaded. Fixing items 1–2 restores user context. Additionally, harden `renderStepContent`/step components against a null `user`.
+- "add user and view the previous onboards": expose a simple management view. The `Users.jsx` page + `GET /api/users` (paginated) already return users including their `onboarding` object. Provide: (a) ability to **add a user** (admin-style create via existing `POST /api/auth/register`), and (b) a **view of previous onboardings** (list users with `onboarding.completed`, `completedAt`, `currentStep`). Keep this simple: a table/section on the existing Users page or a dedicated lightweight page, using existing endpoints. No new complex feature surface.
+
+**Fix:** Persist onboarding progress to `user.onboarding` via `PUT /users/profile` at each step/completion; drive the "already completed" check from `user.onboarding.completed` (fall back to localStorage). Fix the `fullname` mapping. Guard against null user. Add the simple "add user" + "view previous onboards" UI backed by existing endpoints.
+
+### 7. CI/CD issues
+- Backend has **no ESLint config**; ESLint v9 requires `eslint.config.js` (flat). `npm run lint` hard-fails. CI steps use `|| echo "Lint not configured"` so they do not fail the job, but the intent ("fix cicd issues") is that lint should actually run and pass.
+- Backend route/integration tests are **stale and broken** (see item 8) and would fail `npm test`. CI runs only the `unit` project for backend (`npm test -- --project=unit`), which mostly passes, but the `--project` flag form is non-standard for this Jest version (correct is `--selectProjects unit`). Frontend CI runs `npm test -- --run` but many frontend tests fail.
+- Workflows reference OIDC role + buckets via GitHub `vars`; those are deployment-time secrets/vars, not fixable in-repo. Flag, do not fabricate.
+
+**Fix:** Add a working backend `eslint.config.js` (flat config, Node + ESM, jest globals for tests) so `npm run lint` runs clean (fix or disable rules as needed to pass without churn). Normalize CI test invocation to `--selectProjects unit` (or keep `npm run test:unit` script) and keep the tolerant `|| echo`. Ensure `npm run lint` and the backend `unit` test project pass locally. Do NOT alter OIDC/vars/secrets or Terraform apply behavior.
+
+### 8. Backend test suite is stale/broken (largest count of failures)
+- `tests/setup.js` never defines the globals the route tests use (`app`, `User`, `Team`, `Project`) and never imports the Express app.
+- `tests/routes/auth.test.js` / `tests/routes/tasks.test.js` use `name` (model uses `fullname/username`), `user.generateAuthToken()` (not defined on the model), and `POST /api/tasks` (route is `POST /api/users/:userId/tasks`), and Bearer tokens (session model). They test an app that never matched this code.
+- Some unit tests (`s3-presign`, `lambda-handler`, `auth-middleware`, `documentdb-connectivity`, `aws-integration`, `full-integration`, `email`) fail due to missing globals/env or requiring live AWS/DB.
+
+**Fix (pragmatic, keep green + meaningful):**
+ - Update `tests/setup.js` to import the Express app and expose the globals the DB tests expect, or convert the tests to import `app` and models directly (ESM import). Provide a real auth helper that logs in via the session (supertest agent) OR issues a local JWT, matching the chosen auth model, so authenticated route tests exercise real code paths.
+ - Rewrite/repair `tests/routes/auth.test.js` and `tests/routes/tasks.test.js` to match the actual API: register with `{fullname,username,email,password}`, login with `{username,password}`, create tasks via `POST /api/users/:userId/tasks`, and authenticate with the session agent (or Bearer JWT if that path is chosen). Tests MUST fail if the implementation is reverted.
+ - For tests that require live AWS/DocumentDB (`integration/aws-integration`, `documentdb-connectivity`, `integration/full-integration`), gate them behind an env flag (e.g. skip when `RUN_AWS_INTEGRATION!=='true'`) so the default suite is green without external services. Do not delete meaningful coverage; skip only what genuinely needs live infra.
+ - Ensure `npm test` (both projects) is green by default, and add a focused test for the new Cloudinary avatar route (mock cloudinary) and the session-fallback auth behavior.
+
+### 9. Frontend test suite failures
+- Many frontend component tests fail (e.g. `Input.test.jsx` expects a `