feat: infrastructure hardening, tailwind 4 migration, and ai type-safety#61
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR modernizes Sentry to v10 for enhanced observability, adds recursive PII scrubbing to sanitize sensitive data in logs, configures ESLint flat config for TypeScript support, implements database startup health checks with explicit pool management in Prisma, pins Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Browser Client
participant App as Next.js App<br/>(auth-provider)
participant Sentry as Sentry Service
participant Server as Backend<br/>(beforeSend)
rect rgba(100, 200, 150, 0.5)
Note over Client,Server: Initial Page Load - User Auth
Client->>App: Load page / auth state
App->>Client: Sentry.init(DSN)<br/>enable replays & masking
end
rect rgba(100, 150, 200, 0.5)
Note over Client,Server: User Authentication
Client->>App: User logs in via Supabase
App->>App: getUser() from Supabase
App->>Server: Sentry.setUser({id, email})
Server->>Sentry: User context linked
end
rect rgba(200, 150, 100, 0.5)
Note over Client,Server: Event Capture & PII Scrubbing
Client->>Client: Error / breadcrumb event
Client->>Sentry: Send event
Sentry->>Server: beforeSend hook
Server->>Server: Recursive scrub(event)<br/>- Remove headers (cookie, auth)<br/>- Sanitize keys (cpf,<br/>password, etc.) → [SCRUBBED]<br/>- Handle nested objects & arrays
Server->>Sentry: Return sanitized event
Sentry->>Sentry: Store safe event data
end
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 17
🧹 Nitpick comments (1)
docs/.obsidian/workspace.json (1)
194-231: Avoid committing volatilelastOpenFiles/active-tab workspace churn.Lines 194–231 are machine-local editor state and will create noisy diffs/conflicts over time. Consider keeping this file stable (or excluding volatile keys from version control).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/.obsidian/workspace.json` around lines 194 - 231, The commit includes machine-local editor state keys "lastOpenFiles" and "active" which cause noisy diffs; remove those keys from the JSON (or revert the file to a stable template) before committing and stop tracking volatile workspace churn by either adding this workspace file to .gitignore or updating your pre-commit/CI to strip "lastOpenFiles" and "active" (or other Obsidian-specific volatile keys) from the file automatically; locate the keys "lastOpenFiles" and "active" in the workspace JSON and ensure only stable settings remain in version control.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.env.example:
- Line 63: Update the malformed comment on line 63 to clearly separate
instructions for NEXT_PUBLIC_SENTRY_DSN and SENTRY_AUTH_TOKEN: replace the
merged note ("Cli# Sentry Observability...") with two concise guidance lines
indicating where to obtain each value (e.g., "NEXT_PUBLIC_SENTRY_DSN: get from
Sentry → Project → Settings → Client Keys / DSN" and "SENTRY_AUTH_TOKEN: get
from Sentry → Settings → API Keys or Auth Tokens"), and remove the truncated
project identifier so the comment reads unambiguous guidance for
NEXT_PUBLIC_SENTRY_DSN and SENTRY_AUTH_TOKEN.
In `@CHANGELOG.md`:
- Around line 8-27: The changelog contains a duplicate "Unreleased" section
(e.g., the header "## [Unreleased] — 2026-04-11 — Sentry & Build Stability");
consolidate by keeping a single "Unreleased" heading and merging these new
entries into that section, and move any older or previously-unreleased entries
into dated/versioned headings below (creating or updating versioned headers as
needed) so there is only one Unreleased block in the file.
In `@docs/CURRENT-STATE.md`:
- Line 100: Update the managed-debt note that currently names the wrong package;
replace the incorrect package token `@pg/types` with the correct dependency name
`@types/pg` in the sentence describing Prisma Type Overrides (the note
referencing pinning to resolve Prisma 7/Next 15 conflicts) so the documentation
matches the actual pin in this PR.
- Around line 20-23: The table rows "ESLint quality gate", "TypeScript
typecheck", "Unit tests", and "Ops documentation" contain counts that conflict
with other sections (notably the unit test total/warning counts shown
elsewhere); update these entries so they match the source of truth (CI/test
runner and linter reports) and then search for and replace all other occurrences
of those same values (e.g., the unit test total and warning counts in the other
summary block) so every instance in the document is consistent; verify "Unit
tests" count and any warning/error counts are identical to the test results and
correct any mismatched numbers found in the other section referenced.
In `@docs/decisions/ADR-003-infrastructure-observability-hardening.md`:
- Around line 1-60: Run Prettier on the ADR file
(ADR-003-infrastructure-observability-hardening.md) to fix CI formatting
failures; open the file (the document titled "ADR-003: Infrastructure &
Observability Hardening") and apply your project's Prettier config (e.g.,
npm/yarn run prettier --write or your editor's format command) so the markdown
spacing, headings, lists, and image links are normalized, then re-run lint/CI
and commit the reformatted file.
- Around line 55-60: The markdown uses local file:// image URLs (e.g.,
file:///home/.../student_dashboard_view_1775903233752.png and
workout_generator_final_result_1775903924170.png) which won't render in the
repo; move those PNGs into the repository (suggested folder docs/assets/) and
update the image references in ADR-003 to use repo-relative paths (e.g.,
 and );
ensure the filenames match exactly and commit the assets alongside the updated
docs/decisions/ADR-003-infrastructure-observability-hardening.md.
In `@docs/observability/SLOS.md`:
- Line 37: The table entry incorrectly claims CPF/email logged to stdout is
fully mitigated; update the mitigation column for the CPF/email plaintext stdout
row to "Partial / Not mitigated" and adjust the mitigation text to state that
Sentry's beforeSend only scrubs events sent to Sentry (reference beforeSend) and
does not affect generic stdout logging, and add a short remediation note that
stdout logging must be scrubbed at source or via the logging pipeline (e.g.,
implement scrubbing in the logger or add a log-processor filter) to be
considered fully mitigated.
- Around line 34-38: The document lost a formal SLO entry for SLO-03; restore a
dedicated SLO-03 section that states the objective, numeric target, and clear
measurement method (e.g., "SLO-03: Prevent server error stack traces being
exposed to clients — Target: 99.9% of requests do not return stack traces in
production; Measurement: periodic scans of 5xx responses and Sentry/Next.js
server-side error sampling over a 30-day window"), and replace the current
risk-register row that references the same concern with a brief summary linking
to SLO-03; ensure you reference the same wording used elsewhere ("Server error
exposes stack trace to client", "SLO-03") so other tables/tooling remain
consistent.
In `@docs/security/THREAT-MODEL.md`:
- Around line 51-56: Update the remaining action item in THREAT-MODEL.md that
still says to implement Sentry's beforeSend in the future: locate the action
list mentioning "implement beforeSend" and change it to indicate beforeSend is
implemented/active (matching the rows for "CPF/email logged to stdout" and
"Server error exposes stack trace"), remove the TODO/future wording, and mark
the status as mitigated (e.g., "✅ Mitigated (beforeSend active)") so the
document no longer contradicts the Sentry/beforeSend entries.
In `@eslint.config.mjs`:
- Around line 3-4: The ESLint config imports `@typescript-eslint/eslint-plugin`
and `@typescript-eslint/parser` but those packages are missing from package.json;
add both "@typescript-eslint/eslint-plugin" and "@typescript-eslint/parser" to
devDependencies in package.json and install them (e.g., run npm install
--save-dev `@typescript-eslint/eslint-plugin` `@typescript-eslint/parser` or your
project package manager equivalent) so the imports in eslint.config.mjs resolve
and the lint step can run; update the lockfile/commit the package.json changes.
In `@sentry.server.config.ts`:
- Around line 24-35: The allowlist entries in sensitiveKeys are mixed case so
comparisons using key.toLowerCase() never match camelCase names (e.g.,
biometiaHash, fotoUrl); update sensitiveKeys to use all-lowercase strings (e.g.,
"biometriahash", "fotourl") or normalize both sides when filtering, and apply
the same fix to the other sensitive-key lists mentioned later in the file (the
other sensitiveKeys usages around the Sentry config / beforeSend filter). Ensure
you reference and change the sensitiveKeys constant and any code that does
key.toLowerCase() comparisons so matching is reliably case-insensitive.
In `@src/app/aluno/meus-treinos/meus-treinos-client.tsx`:
- Around line 165-184: The mapped block that builds novosExercicios (mapping
workout.exercicios, using EXERCICIOS_POR_GRUPO, finding exercicioMaster and
calling Sentry.captureMessage) is not formatted to Prettier rules; re-run your
formatter (or adjust spacing/line breaks) on the novosExercicios mapping so the
arrow function, the find callback, the Sentry.captureMessage call and the
returned object follow project Prettier configuration and fix the CI failure.
- Around line 170-183: The code logs hallucinated exercises via
Sentry.captureMessage but still returns an object with descricao: '' which
persists invalid data into Treino.exercicios; change the mapping logic where
exercicioMaster is resolved (the block that currently calls
Sentry.captureMessage and then returns the exercise object) to either (a) reject
the workout by throwing/returning an error when exercicioMaster is missing so
the whole workout is rejected/returned to the user, or (b) drop that exercise
from the returned exercises array and flag/notify the user that exercises were
removed; ensure you remove the branch that returns an object with descricao: ''
and instead omit the exercise (or throw) and propagate an error/notification
upstream so downstream screens never receive the hallucinated exercise.
In `@src/app/globals.css`:
- Around line 60-79: Run Prettier to fix formatting in the CSS that’s failing
the Prettier check: format the `@keyframes` blocks (accordion-down, accordion-up,
circular-progress, float, glow-pulse) and the surrounding CSS in this file using
your project's Prettier config (e.g., run the repo’s prettier script or npx
prettier --write on the file), then commit the resulting formatting-only changes
so CI/Prettier --check passes.
In `@src/components/providers/auth-provider.tsx`:
- Around line 46-53: Replace calls to Sentry.setUser that pass the Supabase
user's email so only the opaque user id is sent: locate the Sentry.setUser
usages in this file (the blocks that inspect sbUser and call Sentry.setUser({
id: sbUser.id, email: sbUser.email }) at both occurrences) and remove the email
property so they call Sentry.setUser({ id: sbUser.id }) when sbUser exists (keep
Sentry.setUser(null) in the else branches unchanged).
In `@src/lib/prisma.ts`:
- Around line 40-43: Clamp the level before doing percentage math to avoid
divide-by-zero or negative xpReq: compute a safeLevel = Math.max(aluno.nivel ??
1, 1) (or similar) and use that safeLevel when calculating xpReq and the
progress percent instead of using nivel directly; update references to
nivel/xpReq/exp in the function that returns the progress percent so xpReq is
always >= 1.
In `@src/utils/supabase/server.ts`:
- Around line 40-43: Sentry is leaking user.email because event.user isn't
scrubbed; update the Sentry initialization's beforeSend hook to scrub event.user
in addition to event.breadcrumbs and event.extra by reusing the existing
sensitiveKeys and scrub() logic and removing/redacting any PII in
event.user.email (and other user fields) before returning the event;
specifically modify the beforeSend hook used by the server Sentry init (the
function that references sensitiveKeys and scrub()) to process event.user, and
add an equivalent beforeSend hook to the client Sentry init (the
instrumentation-client initialization) so that Sentry.setUser calls do not
transmit raw PII from either server or client.
---
Nitpick comments:
In `@docs/.obsidian/workspace.json`:
- Around line 194-231: The commit includes machine-local editor state keys
"lastOpenFiles" and "active" which cause noisy diffs; remove those keys from the
JSON (or revert the file to a stable template) before committing and stop
tracking volatile workspace churn by either adding this workspace file to
.gitignore or updating your pre-commit/CI to strip "lastOpenFiles" and "active"
(or other Obsidian-specific volatile keys) from the file automatically; locate
the keys "lastOpenFiles" and "active" in the workspace JSON and ensure only
stable settings remain in version control.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 1d5eb906-e8f7-4069-967d-47163b42544e
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (25)
.env.example.sentryclircCHANGELOG.mdCLAUDE.mddocs/.obsidian/graph.jsondocs/.obsidian/workspace.jsondocs/CURRENT-STATE.mddocs/decisions/ADR-003-infrastructure-observability-hardening.mddocs/observability/SLOS.mddocs/security/THREAT-MODEL.mdeslint.config.mjsinstrumentation-client.tsinstrumentation.tsnext.config.tspackage.jsonsentry.edge.config.tssentry.server.config.tssrc/ai/flows/workout-generator-flow.tssrc/app/aluno/meus-treinos/meus-treinos-client.tsxsrc/app/design-system.csssrc/app/globals.csssrc/components/providers/auth-provider.tsxsrc/instrumentation-client.tssrc/lib/prisma.tssrc/utils/supabase/server.ts
💤 Files with no reviewable changes (2)
- instrumentation-client.ts
- src/app/design-system.css
|
|
||
| # Sentry (error tracking) | ||
| # Get from sentry.io → Project → Settings → Client Keys | ||
| # Get from sentry.io → Project → Settings → Cli# Sentry Observability (smartmanagementesystem: ufrn-universidade-federal-do-r) |
There was a problem hiding this comment.
Fix the malformed Sentry setup guidance comment.
Line 63 currently merges two notes (Cli# ...) and is easy to misread when setting NEXT_PUBLIC_SENTRY_DSN vs SENTRY_AUTH_TOKEN.
Suggested doc fix
-# Get from sentry.io → Project → Settings → Cli# Sentry Observability (smartmanagementesystem: ufrn-universidade-federal-do-r)
+# DSN: sentry.io → Project Settings → Client Keys (DSN)
+# Auth token: sentry.io → Organization Settings → Auth Tokens📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Get from sentry.io → Project → Settings → Cli# Sentry Observability (smartmanagementesystem: ufrn-universidade-federal-do-r) | |
| # DSN: sentry.io → Project Settings → Client Keys (DSN) | |
| # Auth token: sentry.io → Organization Settings → Auth Tokens |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.env.example at line 63, Update the malformed comment on line 63 to clearly
separate instructions for NEXT_PUBLIC_SENTRY_DSN and SENTRY_AUTH_TOKEN: replace
the merged note ("Cli# Sentry Observability...") with two concise guidance lines
indicating where to obtain each value (e.g., "NEXT_PUBLIC_SENTRY_DSN: get from
Sentry → Project → Settings → Client Keys / DSN" and "SENTRY_AUTH_TOKEN: get
from Sentry → Settings → API Keys or Auth Tokens"), and remove the truncated
project identifier so the comment reads unambiguous guidance for
NEXT_PUBLIC_SENTRY_DSN and SENTRY_AUTH_TOKEN.
| ## [Unreleased] — 2026-04-11 — Sentry & Build Stability | ||
|
|
||
| ### Added | ||
|
|
||
| - **Sentry v10 Modernization**: migrated to Next.js 15 standards using `src/instrumentation-client.ts` for navigation tracing. | ||
| - **Deep PII Scrubbing**: implemented a recursive sanitization engine in `sentry.server.config.ts` to protect student data (CPF, health hashes) in server logs. | ||
| - **Privacy-First Replays**: enabled Sentry Replay with strict `maskAllInputs: true` and `maskAllText: true` to prevent PII leakage. | ||
| - **DB Connection Heartbeat**: added a surgical `SELECT 1` check in `instrumentation.ts` to verify database health on application boot. | ||
| - **Full-stack User Traceability**: linked Supabase UUID/Email to Sentry context on both client and server. | ||
| - **Vercel CI/CD Integration**: Linked Sentry to Vercel for automated environment variable synchronization and sourcemap deployments. | ||
|
|
||
| ### Changed | ||
|
|
||
| - **Prisma 7 Type Elevation**: removed generic `as any` casts by pinning `@types/pg` to version `8.11.11` via package `overrides`. | ||
| - **Database Pool Governance**: implemented `max: 20`, `idleTimeout`, and `connectionTimeout` in `src/lib/prisma.ts` for improved resilience. | ||
|
|
||
| ### Fixed | ||
|
|
||
| - **ESLint Flat Config**: repaired missing `@typescript-eslint` plugin and parser configuration in `eslint.config.mjs` that was breaking production builds. | ||
|
|
There was a problem hiding this comment.
Consolidate duplicate Unreleased sections.
This adds another Unreleased block while the file already has multiple existing ones. Please keep a single Unreleased section and move older entries under dated/versioned headings.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@CHANGELOG.md` around lines 8 - 27, The changelog contains a duplicate
"Unreleased" section (e.g., the header "## [Unreleased] — 2026-04-11 — Sentry &
Build Stability"); consolidate by keeping a single "Unreleased" heading and
merging these new entries into that section, and move any older or
previously-unreleased entries into dated/versioned headings below (creating or
updating versioned headers as needed) so there is only one Unreleased block in
the file.
| | ESLint quality gate | ✅ Done | 0 errors — `any` + unused vars | | ||
| | TypeScript typecheck | ✅ Clean | 0 errors (strict mode) | | ||
| | Unit tests | ✅ Passing | 18/18 (Vitest) | | ||
| | Ops documentation | ✅ Done | Runbook, SLOs, threat model | |
There was a problem hiding this comment.
Sync reported counts across this document.
The updated values conflict with other sections (e.g., unit test total and warning counts). Please reconcile these numbers so readiness status is unambiguous.
Also applies to: 33-39
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/CURRENT-STATE.md` around lines 20 - 23, The table rows "ESLint quality
gate", "TypeScript typecheck", "Unit tests", and "Ops documentation" contain
counts that conflict with other sections (notably the unit test total/warning
counts shown elsewhere); update these entries so they match the source of truth
(CI/test runner and linter reports) and then search for and replace all other
occurrences of those same values (e.g., the unit test total and warning counts
in the other summary block) so every instance in the document is consistent;
verify "Unit tests" count and any warning/error counts are identical to the test
results and correct any mismatched numbers found in the other section
referenced.
|
|
||
| 1. **Sentry Boundary Casts**: Use of `any` in `beforeSend` (suppressed with `eslint-disable`) is required due to Sentry SDK interface rigidness. | ||
| 2. **Server console.log**: 31 warnings in Server Actions are preserved to ensure minimal observability in the terminal during development. | ||
| 3. **Prisma Type Overrides**: We use `@pg/types` pinning to resolve Prisma 7/Next 15 conflicts until `@prisma/adapter-pg` upstream fixes land. |
There was a problem hiding this comment.
Fix dependency name typo in managed debt note.
Line [100] references @pg/types, but the pinning in this PR is for @types/pg.
Proposed fix
-3. **Prisma Type Overrides**: We use `@pg/types` pinning to resolve Prisma 7/Next 15 conflicts until `@prisma/adapter-pg` upstream fixes land.
+3. **Prisma Type Overrides**: We use `@types/pg` pinning to resolve Prisma 7/Next 15 conflicts until `@prisma/adapter-pg` upstream fixes land.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 3. **Prisma Type Overrides**: We use `@pg/types` pinning to resolve Prisma 7/Next 15 conflicts until `@prisma/adapter-pg` upstream fixes land. | |
| 3. **Prisma Type Overrides**: We use `@types/pg` pinning to resolve Prisma 7/Next 15 conflicts until `@prisma/adapter-pg` upstream fixes land. |
🧰 Tools
🪛 markdownlint-cli2 (0.22.0)
[warning] 100-100: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/CURRENT-STATE.md` at line 100, Update the managed-debt note that
currently names the wrong package; replace the incorrect package token
`@pg/types` with the correct dependency name `@types/pg` in the sentence
describing Prisma Type Overrides (the note referencing pinning to resolve Prisma
7/Next 15 conflicts) so the documentation matches the actual pin in this PR.
| # ADR-003: Infrastructure & Observability Hardening | ||
|
|
||
| ## Status | ||
| Accepted | ||
|
|
||
| ## Context | ||
| As part of the modernization for Next.js 15, Sentry 10, and Prisma 7, several structural challenges were identified: | ||
| 1. **PII Vulnerability**: Server-side error logs and client-side session replays were potentially capturing sensitive student data (CPF, health hashes). | ||
| 2. **Type Safety Decay**: The move to Prisma 7 with the `pg` driver adapter introduced a version conflict in `@types/pg`, leading to the widespread use of `as any` in the database layer. | ||
| 3. **Connection Governance**: Migrating to a driver adapter pattern (required for some Next.js environments) necessitated more explicit connection pool management to prevent exhaustion in serverless or PgBouncer environments. | ||
|
|
||
| ## Decisions | ||
|
|
||
| ### 1. Recursive PII Scrubbing (Sentry) | ||
| Instead of relying on basic header scrubbing, we implemented a dedicated recursive sanitization engine in `sentry.server.config.ts`. This engine traverses all event breadcrumbs and extra data, replacing values for a defined list of sensitive keys (`cpf`, `password`, `biometriaHash`, etc.) with `[SCRUBBED]`. | ||
|
|
||
| ### 2. Dependency Pinning via Overrides | ||
| To resolve the `ClientBase` return type mismatch between `@types/pg@v8.18+` and `@prisma/adapter-pg`, we decided to: | ||
| - Pin `@types/pg` to version `8.11.11` in both `devDependencies` and the `overrides` field of `package.json`. | ||
| - This ensures that all internal and external consumers of the `pg` types are aligned with the Prisma runtime requirements, allowing for the complete removal of unsafe type assertions in `src/lib/prisma.ts`. | ||
|
|
||
| ### 3. Connection Resilience & Heartbeat | ||
| We enhanced the `PrismaClient` initialization with: | ||
| - **Pool Governance**: Explicit `idleTimeout` and `connectionTimeout` to handle transient network issues. | ||
| - **Boot Heartbeat**: Integrated a `SELECT 1` check into the Next.js `instrumentation.ts` with a 5-second `Promise.race` timeout to verify database health during application startup, preventing "zombie" boot states. | ||
|
|
||
| ### 4. Genkit AI Flow Contract Hardening | ||
| To prevent technical debt in AI integration, we implemented: | ||
| - **Strict Schema Enforcement**: All Genkit flows now utilize Zod schemas for input, output, and streaming, eliminating `as any` casts in the frontend. | ||
| - **Hallucination Detection**: Added Sentry monitoring to log unrecognized exercise suggestions, ensuring that AI-generated plans remain within physiological and logistical constraints defined in `src/lib/constants.ts`. | ||
|
|
||
| ### 5. Tailwind 4 & OKLCH Unified Theme | ||
| Migrated the entire design system to Tailwind 4 (Oxide engine) to leverage: | ||
| - **Native Custom Properties**: Replaced legacy HSL `:root` variables with a unified `@theme` block. | ||
| - **OKLCH Color Space**: Adopted OKLCH tokens for consistent vibrancy and accessibility across the "Premium Dark" interface. | ||
|
|
||
| ## Consequences | ||
|
|
||
| ### Positive | ||
| - **Privacy Compliance**: Strict adherence to data protection standards for student information. | ||
| - **Developer Experience**: Restored full IDE intellisense and type-checking in the database and AI layers. | ||
| - **Aesthetic Excellence**: Unified, vibrant UI with optimized build performance via Tailwind 4. | ||
| - **Operational Visibility**: Sentry alerts for both infrastructure failures and AI "hallucinations". | ||
|
|
||
| ### Negative | ||
| - **Maintenance**: We must monitor Prisma and Genkit SDK updates to revert specific type overrides or casts once upstream packages reach full alignment with Next.js 15. | ||
|
|
||
| --- | ||
|
|
||
| ## Visual Documentation | ||
|
|
||
| ### 1. Premium Dark Interface (Tailwind 4) | ||
| The new design system ensures a high-fidelity, high-contrast experience using OKLCH color tokens. | ||
|
|
||
|  | ||
|
|
||
| ### 2. AI Workout Generator (Genkit) | ||
| Strictly typed integration between the AI service and the student portal. | ||
|
|
||
|  |
There was a problem hiding this comment.
Address the Prettier failure for this ADR file.
CI already reports formatting issues here; please run Prettier on this file before merge.
🧰 Tools
🪛 GitHub Actions: CI
[warning] 1-1: Prettier --check reported formatting issues in this file.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/decisions/ADR-003-infrastructure-observability-hardening.md` around
lines 1 - 60, Run Prettier on the ADR file
(ADR-003-infrastructure-observability-hardening.md) to fix CI formatting
failures; open the file (the document titled "ADR-003: Infrastructure &
Observability Hardening") and apply your project's Prettier config (e.g.,
npm/yarn run prettier --write or your editor's format command) so the markdown
spacing, headings, lists, and image links are normalized, then re-run lint/CI
and commit the reformatted file.
| if (!exercicioMaster) { | ||
| Sentry.captureMessage(`AI Hallucination: Exercise "${ex.nomeExercicio}" not found in constants.`, { | ||
| level: 'warning', | ||
| extra: { workout: workout.nome, goal: data.objetivo }, | ||
| }); | ||
| } | ||
|
|
||
| return { | ||
| nomeExercicio: ex.nomeExercicio, | ||
| series: ex.series, | ||
| repeticoes: ex.repeticoes, | ||
| observacoes: ex.observacoes, | ||
| descricao: exercicioMaster?.descricao || '', | ||
| }; |
There was a problem hiding this comment.
Don’t persist hallucinated exercises after detecting them.
This branch reports the mismatch to Sentry, but it still saves the unknown nomeExercicio with an empty descricao. That bakes invalid AI output into Treino.exercicios, so downstream screens drift even though the hallucination was detected. Reject the workout (or drop the invalid exercise/workout and notify the user) once exercicioMaster is missing.
💡 Minimal fix
if (!exercicioMaster) {
Sentry.captureMessage(`AI Hallucination: Exercise "${ex.nomeExercicio}" not found in constants.`, {
level: 'warning',
extra: { workout: workout.nome, goal: data.objetivo },
});
+ throw new Error(`A IA retornou um exercício inválido: ${ex.nomeExercicio}`);
}
return {
nomeExercicio: ex.nomeExercicio,
series: ex.series,
repeticoes: ex.repeticoes,
observacoes: ex.observacoes,
- descricao: exercicioMaster?.descricao || '',
+ descricao: exercicioMaster.descricao,
};🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/aluno/meus-treinos/meus-treinos-client.tsx` around lines 170 - 183,
The code logs hallucinated exercises via Sentry.captureMessage but still returns
an object with descricao: '' which persists invalid data into Treino.exercicios;
change the mapping logic where exercicioMaster is resolved (the block that
currently calls Sentry.captureMessage and then returns the exercise object) to
either (a) reject the workout by throwing/returning an error when
exercicioMaster is missing so the whole workout is rejected/returned to the
user, or (b) drop that exercise from the returned exercises array and
flag/notify the user that exercises were removed; ensure you remove the branch
that returns an object with descricao: '' and instead omit the exercise (or
throw) and propagate an error/notification upstream so downstream screens never
receive the hallucinated exercise.
| @keyframes accordion-down { | ||
| from { | ||
| height: 0; | ||
| } | ||
| to { | ||
| height: var(--radix-accordion-content-height); | ||
| } | ||
| from { height: 0; } | ||
| to { height: var(--radix-accordion-content-height); } | ||
| } | ||
| @keyframes accordion-up { | ||
| from { | ||
| height: var(--radix-accordion-content-height); | ||
| } | ||
| to { | ||
| height: 0; | ||
| } | ||
| from { height: var(--radix-accordion-content-height); } | ||
| to { height: 0; } | ||
| } | ||
| @keyframes circular-progress { | ||
| 0% { | ||
| stroke-dashoffset: 100; | ||
| } | ||
| 100% { | ||
| stroke-dashoffset: 0; | ||
| } | ||
| 0% { stroke-dashoffset: 100; } | ||
| 100% { stroke-dashoffset: 0; } | ||
| } | ||
| @keyframes float { | ||
| 0%, | ||
| 100% { | ||
| transform: translateY(0); | ||
| } | ||
| 50% { | ||
| transform: translateY(-10px); | ||
| } | ||
| 0%, 100% { transform: translateY(0); } | ||
| 50% { transform: translateY(-10px); } | ||
| } | ||
| @keyframes glow-pulse { | ||
| 0%, | ||
| 100% { | ||
| opacity: 0.5; | ||
| } | ||
| 50% { | ||
| opacity: 1; | ||
| } | ||
| 0%, 100% { opacity: 0.5; } | ||
| 50% { opacity: 1; } | ||
| } |
There was a problem hiding this comment.
Prettier gate is failing for this file.
CI reports Prettier --check failure here. Please run Prettier on src/app/globals.css and commit the formatting-only update so the quality gate passes.
Also applies to: 101-111, 130-139
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/app/globals.css` around lines 60 - 79, Run Prettier to fix formatting in
the CSS that’s failing the Prettier check: format the `@keyframes` blocks
(accordion-down, accordion-up, circular-progress, float, glow-pulse) and the
surrounding CSS in this file using your project's Prettier config (e.g., run the
repo’s prettier script or npx prettier --write on the file), then commit the
resulting formatting-only changes so CI/Prettier --check passes.
| if (sbUser) { | ||
| Sentry.setUser({ | ||
| id: sbUser.id, | ||
| email: sbUser.email, | ||
| }); | ||
| } else { | ||
| Sentry.setUser(null); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In @sentry/nextjsv10, are values set viaSentry.setUser({ email })sent as top-levelevent.userdata, and doesbeforeSend automatically scrub those user fields or only the event payload that the hook explicitly mutates?
💡 Result:
Yes, in @sentry/nextjs v10, values set via Sentry.setUser({ email }) are sent as top-level event.user data in Sentry events. The setUser method sets user context on the current scope (typically the isolation scope in Next.js for request isolation), and when an event is captured, the SDK merges scope data—including the user—into the event payload as the top-level event.user object before applying processors like beforeSend. The beforeSend callback receives the fully processed event with event.user already populated from the scope. It does not automatically scrub user fields; scrubbing must be done explicitly in the callback if desired, e.g., beforeSend(event) { if (event.user) delete event.user.email; return event; }. beforeSend only scrubs what the hook explicitly mutates—it processes the complete event payload, including merged scope data like event.user, but automatic scrubbing of user fields requires manual implementation or server-side rules. No automatic scrubbing of setUser fields occurs in the SDK itself; server-side data scrubbing in Sentry projects handles patterns like PII but not specifically user fields unless configured.
Citations:
- 1: https://docs.sentry.io/platforms/javascript/guides/nextjs/enriching-events/identify-user/
- 2: https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/apis/
- 3: https://docs.sentry.io/platforms/javascript/guides/nextjs/data-management/sensitive-data
- 4: https://docs.sentry.io/platforms/javascript/enriching-events/identify-user/
- 5: https://docs.sentry.io/enriching-error-data/scopes/?platform=javascript
🏁 Script executed:
find . -name "sentry.server.config.ts" -o -name "sentry*config*" | head -20Repository: EmiyaKiritsugu3/PWeb_Project
Length of output: 121
🏁 Script executed:
cat -n sentry.server.config.tsRepository: EmiyaKiritsugu3/PWeb_Project
Length of output: 2886
Remove user email from Sentry context; use only the opaque user ID.
Sentry.setUser({ id, email }) sends raw email addresses as top-level event.user.email data to Sentry. The sentry.server.config.ts beforeSend hook scrubs email from breadcrumbs and extra fields but does not scrub event.user itself. The opaque Supabase user ID is sufficient for correlation without exposing direct PII to a third-party service.
Sentry.setUser({ id: sbUser.id });
Applies to: lines 46-53 and 69-76.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/providers/auth-provider.tsx` around lines 46 - 53, Replace
calls to Sentry.setUser that pass the Supabase user's email so only the opaque
user id is sent: locate the Sentry.setUser usages in this file (the blocks that
inspect sbUser and call Sentry.setUser({ id: sbUser.id, email: sbUser.email })
at both occurrences) and remove the email property so they call Sentry.setUser({
id: sbUser.id }) when sbUser exists (keep Sentry.setUser(null) in the else
branches unchanged).
| const nivel = aluno.nivel ?? 1; | ||
| const exp = aluno.exp ?? 0; | ||
| const xpReq = nivel * 1500; | ||
| return Math.min(Math.round((exp / xpReq) * 100), 100); |
There was a problem hiding this comment.
Clamp level before percentage math to avoid invalid progress values.
If nivel is 0 (or negative due to bad data), xpReq becomes non-positive and percentage math becomes incorrect. Clamp to at least 1 before computing.
Proposed fix
- const nivel = aluno.nivel ?? 1;
+ const nivel = Math.max(aluno.nivel ?? 1, 1);
const exp = aluno.exp ?? 0;
const xpReq = nivel * 1500;
return Math.min(Math.round((exp / xpReq) * 100), 100);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const nivel = aluno.nivel ?? 1; | |
| const exp = aluno.exp ?? 0; | |
| const xpReq = nivel * 1500; | |
| return Math.min(Math.round((exp / xpReq) * 100), 100); | |
| const nivel = Math.max(aluno.nivel ?? 1, 1); | |
| const exp = aluno.exp ?? 0; | |
| const xpReq = nivel * 1500; | |
| return Math.min(Math.round((exp / xpReq) * 100), 100); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/prisma.ts` around lines 40 - 43, Clamp the level before doing
percentage math to avoid divide-by-zero or negative xpReq: compute a safeLevel =
Math.max(aluno.nivel ?? 1, 1) (or similar) and use that safeLevel when
calculating xpReq and the progress percent instead of using nivel directly;
update references to nivel/xpReq/exp in the function that returns the progress
percent so xpReq is always >= 1.
| Sentry.setUser({ | ||
| id: user.id, | ||
| email: user.email, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Locate Sentry init/scrubbing hooks =="
rg -n -C3 --type=ts --type=tsx 'Sentry\.init|beforeSend|beforeBreadcrumb|setUser|scrub|sanitize|PII|user\.email'
echo
echo "== Locate all explicit Sentry user email attachments =="
rg -n -C2 --type=ts --type=tsx 'Sentry\.setUser\(\{[^}]*email|email:\s*user\.email|email:\s*sbUser\.email'
echo
echo "== Cross-check security docs for claimed mitigation =="
rg -n -C2 'beforeSend|Sentry active|CPF|email|PII' docs/security/THREAT-MODEL.md docs/observability/SLOS.md 2>/dev/null || trueRepository: EmiyaKiritsugu3/PWeb_Project
Length of output: 157
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Read the exact code in question =="
head -n 50 src/utils/supabase/server.ts | tail -n 20
echo
echo "== Locate Sentry init with beforeSend/beforeBreadcrumb =="
rg -n -C5 'Sentry\.init|beforeSend|beforeBreadcrumb' --type=ts
echo
echo "== Search for all setUser calls with email =="
rg -n -C3 'setUser.*email|email.*user' --type=ts
echo
echo "== Check for sanitization or scrubbing logic =="
rg -n -C3 'sanitize|scrub|PII|email.*scrub|strip.*email' --type=ts
echo
echo "== List Sentry-related configuration files =="
fd -e ts -e tsx -e js -e json 'sentry|instrumentation' 2>/dev/null || trueRepository: EmiyaKiritsugu3/PWeb_Project
Length of output: 10240
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Full sentry.server.config.ts =="
cat sentry.server.config.ts
echo
echo "== Check if client config has beforeSend =="
cat src/instrumentation-client.ts
echo
echo "== Check edge config =="
cat sentry.edge.config.tsRepository: EmiyaKiritsugu3/PWeb_Project
Length of output: 3830
Add event.user scrubbing to Sentry's beforeSend hook to prevent PII leakage.
The code at lines 40-43 of src/utils/supabase/server.ts (and identically in src/components/providers/auth-provider.tsx) sends user.email to Sentry via Sentry.setUser(). While sentry.server.config.ts includes a beforeSend hook with an 'email' entry in sensitiveKeys and a scrub() function, the scrubbing is only applied to event.breadcrumbs and event.extra—not to event.user. Email is exposed in the user context on both server and client paths.
Add this to the beforeSend hook in sentry.server.config.ts:
if (event.extra) {
event.extra = scrub(event.extra) as Record<string, any>;
}
+ if (event.user) {
+ event.user = scrub(event.user) as Record<string, any>;
+ }
return event;The client config (src/instrumentation-client.ts) has no beforeSend hook; add one there as well to sanitize user context before transmission.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/utils/supabase/server.ts` around lines 40 - 43, Sentry is leaking
user.email because event.user isn't scrubbed; update the Sentry initialization's
beforeSend hook to scrub event.user in addition to event.breadcrumbs and
event.extra by reusing the existing sensitiveKeys and scrub() logic and
removing/redacting any PII in event.user.email (and other user fields) before
returning the event; specifically modify the beforeSend hook used by the server
Sentry init (the function that references sensitiveKeys and scrub()) to process
event.user, and add an equivalent beforeSend hook to the client Sentry init (the
instrumentation-client initialization) so that Sentry.setUser calls do not
transmit raw PII from either server or client.
Overview
This PR implements a comprehensive stabilization and modernization of the PWeb stack, focusing on infrastructure resilience, privacy, design excellence, and AI reliability.
Key Changes
instrumentation.ts.anycasts in critical paths (Sentry, AI, Prisma) and pinned@types/pgfor Next.js 15 compatibility.CURRENT-STATE.md,CHANGELOG.md).Verification
Refer to
docs/decisions/ADR-003-infrastructure-observability-hardening.mdfor detailed architectural rationale.Summary by CodeRabbit
New Features
Bug Fixes
Documentation