Skip to content

feat: medium-priority polish (#59, #60, #62) + fix auth error envelope#67

Merged
GRACENOBLE merged 3 commits into
mainfrom
medium-priority
Jun 25, 2026
Merged

feat: medium-priority polish (#59, #60, #62) + fix auth error envelope#67
GRACENOBLE merged 3 commits into
mainfrom
medium-priority

Conversation

@GRACENOBLE

@GRACENOBLE GRACENOBLE commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Summary

Bug fix (found during testing)

FirebaseAuth middleware was returning a flat string error body:

{"error": "missing or invalid Authorization header"}

The mobile ApiErrorResponse expects the standard nested envelope:

{"error": {"code": "UNAUTHORIZED", "message": "..."}}

Updated both error paths in auth.go; added a body format assertion to the middleware test to prevent regression.

Docs updated

  • backend/docs/routing.md — pprof routes table + note about Swagger exclusion
  • backend/docs/middleware.mdLocalNetworkOnly consumers list
  • web/docs/routing.md — fixed stale font name (Manrope, not Geist Sans); added real route groups
  • web/docs/data-fetching.md — backend fetch utility pattern, envelope unwrapping, fallback pattern
  • mobile/docs/architecture.md — ROUTE_SETTINGS, navigation graph section
  • mobile/docs/compose-conventions.md — stateless screen signature example

Test plan

  • go vet ./... — clean
  • go test ./internal/transport/middleware/... — all pass including new envelope format assertion
  • pnpm lint — 0 errors
  • pnpm build — clean
  • ./gradlew lint — BUILD SUCCESSFUL
  • ./gradlew test — BUILD SUCCESSFUL
  • ./gradlew connectedAndroidTest — requires emulator

Summary by CodeRabbit

  • New Features

    • Added a Settings screen with profile details and a sign-out action.
    • Added navigation from Home to Settings in the mobile app.
    • Added a dashboard profile card in the web app showing name, email, and user ID.
  • Bug Fixes

    • Improved fallback handling so the web dashboard still shows profile info when the backend can’t be reached.
    • Standardized authentication error responses for clearer client-side handling.
  • Documentation

    • Updated backend, mobile, and web docs to reflect the new settings/profile flows and routing details.

…elope

- mobile: SettingsScreen (profile + sign-out) wired into AppNavGraph (#59)
- web: dashboard page fetches /api/v1/me, ProfileCard Server Component (#60)
- backend: /debug/pprof/* routes gated by LocalNetworkOnly() (#62)
- fix: FirebaseAuth middleware now returns nested error envelope
  {"error":{"code":"UNAUTHORIZED","message":"..."}} instead of flat string
  so mobile ApiErrorResponse can deserialise 401 responses correctly
- docs: routing.md, middleware.md, data-fetching.md, architecture.md,
  compose-conventions.md all updated; web/docs/routing.md stale font fixed
@github-actions github-actions Bot added area: backend Go REST API area: web Next.js web app area: mobile Android app (Kotlin + Jetpack Compose) labels Jun 25, 2026
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@GRACENOBLE, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 minutes and 20 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4809bb31-edbf-4b62-b125-7b2bf89d7230

📥 Commits

Reviewing files that changed from the base of the PR and between f5c4830 and 8dcfafa.

📒 Files selected for processing (8)
  • backend/internal/transport/handlers/pprof_handler_test.go
  • backend/internal/transport/middleware/local_network.go
  • mobile/app/src/androidTest/java/com/company/template/settings/SettingsScreenTest.kt
  • mobile/app/src/main/java/com/company/template/home/HomeScreen.kt
  • mobile/app/src/main/java/com/company/template/settings/SettingsScreen.kt
  • web/app/(dashboard)/dashboard/page.tsx
  • web/docs/routing.md
  • web/lib/user-profile.ts
📝 Walkthrough

Walkthrough

The PR updates backend auth error responses and pprof routing, adds a mobile settings screen and navigation path, introduces web profile fetching and dashboard rendering, and marks template status items complete.

Changes

Backend transport updates

Layer / File(s) Summary
Structured auth failures
backend/internal/transport/middleware/auth.go, backend/internal/transport/middleware/auth_test.go
FirebaseAuth unauthorized responses now use a structured error object with code and message, and the missing-header test checks for the new JSON shape.
pprof routes and access checks
backend/internal/transport/handlers/routes.go, backend/internal/transport/handlers/pprof_handler_test.go, backend/docs/middleware.md, backend/docs/routing.md
/debug/pprof is registered as a LocalNetworkOnly() Gin group with index, cmdline, profile, symbol, trace, and /:profile handlers, and tests cover loopback access and public-IP blocking.

Mobile settings flow

Layer / File(s) Summary
Settings screen UI
mobile/app/src/main/java/com/company/template/settings/SettingsScreen.kt, mobile/app/src/androidTest/java/com/company/template/settings/SettingsScreenTest.kt
SettingsScreen renders avatar, display name, email, and sign-out UI with null fallbacks, and Compose tests cover populated, null, and click behavior.
Settings navigation wiring
mobile/app/src/main/java/com/company/template/home/HomeScreen.kt, mobile/app/src/main/java/com/company/template/navigation/AppNavGraph.kt, mobile/docs/architecture.md, mobile/docs/compose-conventions.md
HomeScreen exposes a settings callback and button, AppNavGraph adds a settings destination, and the mobile docs describe the new screen and navigation structure.

Web profile dashboard flow

Layer / File(s) Summary
User profile fetch helper
web/lib/user-profile.ts, web/lib/user-profile.test.ts, web/docs/data-fetching.md
fetchUserProfile reads BACKEND_URL, sends X-User-Id, unwraps the backend envelope, and the tests cover success, header usage, error cases, and missing config.
Dashboard profile render
web/app/(dashboard)/dashboard/ProfileCard.tsx, web/app/(dashboard)/dashboard/page.tsx, web/app/(dashboard)/__tests__/dashboard.test.tsx, web/docs/routing.md
ProfileCard renders displayName, email, and uid, and the dashboard page fetches the profile with a session-derived fallback before passing it into the card; tests and routing docs describe the updated page.

Template status note

Layer / File(s) Summary
First-week friction status
TEMPLATE_STATUS.md
The first-week friction table marks issues #50#58 done, and the legend note now says PR #66 was merged.

Sequence Diagram(s)

Backend pprof access flow:

sequenceDiagram
  participant Client
  participant Handler
  participant LocalNetworkOnly
  participant netHttpPprof
  Handler->>LocalNetworkOnly: wrap /debug/pprof group
  Client->>Handler: GET /debug/pprof/heap
  Handler->>LocalNetworkOnly: enforce local-network access
  alt loopback/private IP
    LocalNetworkOnly->>netHttpPprof: dispatch heap handler
    netHttpPprof-->>Client: 200 OK
  else public IP
    LocalNetworkOnly-->>Client: 403 Forbidden
  end
Loading

Web dashboard profile flow:

sequenceDiagram
  participant DashboardPage
  participant fetchUserProfile
  participant GoBackend
  participant ProfileCard
  DashboardPage->>DashboardPage: build fallbackProfile from session
  DashboardPage->>fetchUserProfile: request current-user profile
  fetchUserProfile->>GoBackend: GET /api/v1/me with X-User-Id
  GoBackend-->>fetchUserProfile: { data: UserProfile }
  fetchUserProfile-->>DashboardPage: resolved profile
  alt fetchUserProfile fails
    DashboardPage->>ProfileCard: render fallbackProfile
  else fetchUserProfile succeeds
    DashboardPage->>ProfileCard: render backend profile
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

A rabbit hopped through routes and docs,
and sniffed the pprof nooks and locks.
It found a profile, bright and true,
with settings buttons tucked in view,
then nibbled clover by the fox. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the PR’s main scope: medium-priority polish plus the auth error envelope fix.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch medium-priority

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/docs/middleware.md (1)

100-100: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale FirebaseAuth error-body doc — update to the nested envelope.

This PR changed both unauthorized branches in auth.go to return {"error":{"code":"...","message":"..."}}, but this line still documents the flat {"error": "..."} body. Update it so the docs match the new envelope the mobile client parses.

📝 Suggested doc update
-- On failure (missing header, malformed header, or token verification error): aborts with `401 Unauthorized` and a JSON body `{"error": "..."}`.
+- On failure (missing header, malformed header, or token verification error): aborts with `401 Unauthorized` and a JSON body `{"error":{"code":"UNAUTHORIZED","message":"..."}}`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/docs/middleware.md` at line 100, The unauthorized response
documentation is stale and still describes a flat error body, while auth.go now
returns the nested envelope parsed by the mobile client. Update the middleware
docs entry for the failure path to match the new JSON shape used by the
unauthorized branches in auth.go, referencing the FirebaseAuth middleware
behavior and the returned error envelope.
🧹 Nitpick comments (3)
web/app/(dashboard)/__tests__/dashboard.test.tsx (1)

56-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Brittle class assertion. toContain('mono') would also match unintended tokens; asserting the exact font-mono token is more robust.

-    expect(uidEl.className).toContain('mono')
+    expect(uidEl.className).toContain('font-mono')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/`(dashboard)/__tests__/dashboard.test.tsx around lines 56 - 57, The
test in the dashboard suite uses a brittle class-name assertion that can match
unintended tokens; update the expectation on uidEl to verify the exact font-mono
token instead of a partial mono substring. Locate the assertion around uidEl in
the dashboard test and make it assert the precise monospace class so the check
is specific and stable.
web/app/(dashboard)/dashboard/page.tsx (1)

24-29: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider logging the swallowed fetch error.

The bare catch {} discards the error entirely, so backend outages on the profile path become invisible in server logs. A console.error (or your structured logger) preserves observability without changing the fallback behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/`(dashboard)/dashboard/page.tsx around lines 24 - 29, The
fetchUserProfile fallback in dashboard/page.tsx is swallowing the error in a
bare catch, so backend failures on the profile path are not visible in logs.
Update the try/catch around fetchUserProfile(fallbackProfile.uid) to capture the
thrown error and log it with console.error or the existing structured logger
before assigning fallbackProfile, while keeping the current fallback behavior
unchanged.
web/docs/data-fetching.md (1)

151-151: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Typo in rule text: .res.json() should be .json().

-- Always assert `res.ok` before calling `.res.json()`.
+- Always assert `res.ok` before calling `.json()`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/docs/data-fetching.md` at line 151, The rule text in the data fetching
docs has a typo: the response parsing method is written as .res.json() instead
of .json(). Update the sentence in the affected documentation copy to use the
correct method name so the guidance matches the actual Response API.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/internal/transport/handlers/routes.go`:
- Around line 59-70: The pprof protection in LocalNetworkOnly is bypassable
because it trusts gin.Context.ClientIP(), which can be influenced by
X-Forwarded-For when the Gin engine has no trusted proxy restriction. Fix this
by tightening proxy trust in the routes setup for the engine (for example via
SetTrustedProxies) or by changing LocalNetworkOnly to base the internal-network
check on c.Request.RemoteAddr instead of ClientIP(). Update the routes.go pprof
group and the middleware symbol accordingly so the restricted debug handlers
cannot be reached through spoofed forwarded headers.

In
`@mobile/app/src/androidTest/java/com/company/template/settings/SettingsScreenTest.kt`:
- Around line 50-62: The null-state test is matching a shared dash placeholder
text twice, so `onNodeWithText("—")` becomes ambiguous in
`settingsScreen_nullDisplayNameAndEmail_showsDashes`. Update this test to verify
the number of matching nodes instead of asserting a single node, using the
existing `composeTestRule` and `SettingsScreen` setup. Keep the placeholder
check tied to the two null fields by counting all nodes with the dash text and
asserting the expected total. If needed, add the matching Compose testing import
for the count-based assertion.

In `@web/app/`(dashboard)/dashboard/page.tsx:
- Around line 17-29: The profile lookup in dashboard/page.tsx should not call
fetchUserProfile when session.user?.id is missing, because fallbackProfile.uid
can be empty and causes an ambiguous backend request. Update the logic around
fallbackProfile and the fetchUserProfile call to guard on a non-empty uid first,
and only attempt the fetch when a real user id exists; otherwise keep using
fallbackProfile directly.

In `@web/docs/routing.md`:
- Around line 55-65: The directory-tree fenced block in the routing docs is
missing a language identifier, so update the Markdown fence used in the app
routes overview to specify text for the tree snippet. Locate the fenced block
that shows the app/(auth) and app/(dashboard) structure and change the opening
fence to a language-tagged one so it satisfies MD040 while keeping the content
unchanged.

In `@web/lib/user-profile.ts`:
- Around line 40-41: The user profile fetch in userProfile currently returns
body.data without verifying the ApiEnvelope shape, so a successful response with
missing or empty data can leak undefined into ProfileCard. Add a minimal
presence check in userProfile after res.json() to confirm body and body.data are
present before returning, and throw or reject through the existing try/catch
path when the envelope is invalid so callers fall back cleanly.

---

Outside diff comments:
In `@backend/docs/middleware.md`:
- Line 100: The unauthorized response documentation is stale and still describes
a flat error body, while auth.go now returns the nested envelope parsed by the
mobile client. Update the middleware docs entry for the failure path to match
the new JSON shape used by the unauthorized branches in auth.go, referencing the
FirebaseAuth middleware behavior and the returned error envelope.

---

Nitpick comments:
In `@web/app/`(dashboard)/__tests__/dashboard.test.tsx:
- Around line 56-57: The test in the dashboard suite uses a brittle class-name
assertion that can match unintended tokens; update the expectation on uidEl to
verify the exact font-mono token instead of a partial mono substring. Locate the
assertion around uidEl in the dashboard test and make it assert the precise
monospace class so the check is specific and stable.

In `@web/app/`(dashboard)/dashboard/page.tsx:
- Around line 24-29: The fetchUserProfile fallback in dashboard/page.tsx is
swallowing the error in a bare catch, so backend failures on the profile path
are not visible in logs. Update the try/catch around
fetchUserProfile(fallbackProfile.uid) to capture the thrown error and log it
with console.error or the existing structured logger before assigning
fallbackProfile, while keeping the current fallback behavior unchanged.

In `@web/docs/data-fetching.md`:
- Line 151: The rule text in the data fetching docs has a typo: the response
parsing method is written as .res.json() instead of .json(). Update the sentence
in the affected documentation copy to use the correct method name so the
guidance matches the actual Response API.
🪄 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: b071c0ae-1d75-4621-8e77-6d63d380f2f1

📥 Commits

Reviewing files that changed from the base of the PR and between b3fa3ee and f5c4830.

📒 Files selected for processing (20)
  • TEMPLATE_STATUS.md
  • backend/docs/middleware.md
  • backend/docs/routing.md
  • backend/internal/transport/handlers/pprof_handler_test.go
  • backend/internal/transport/handlers/routes.go
  • backend/internal/transport/middleware/auth.go
  • backend/internal/transport/middleware/auth_test.go
  • mobile/app/src/androidTest/java/com/company/template/settings/SettingsScreenTest.kt
  • mobile/app/src/main/java/com/company/template/home/HomeScreen.kt
  • mobile/app/src/main/java/com/company/template/navigation/AppNavGraph.kt
  • mobile/app/src/main/java/com/company/template/settings/SettingsScreen.kt
  • mobile/docs/architecture.md
  • mobile/docs/compose-conventions.md
  • web/app/(dashboard)/__tests__/dashboard.test.tsx
  • web/app/(dashboard)/dashboard/ProfileCard.tsx
  • web/app/(dashboard)/dashboard/page.tsx
  • web/docs/data-fetching.md
  • web/docs/routing.md
  • web/lib/user-profile.test.ts
  • web/lib/user-profile.ts

Comment thread backend/internal/transport/handlers/routes.go
Comment thread web/app/(dashboard)/dashboard/page.tsx
Comment thread web/docs/routing.md Outdated
Comment thread web/lib/user-profile.ts
- security: LocalNetworkOnly() now uses RemoteAddr instead of ClientIP()
  to prevent X-Forwarded-For spoofing bypass; added spoofing regression test
- mobile test: use onAllNodesWithText to handle two dash nodes in null state
- web: skip backend fetch when uid is empty to avoid ambiguous round-trip
- web: validate body.data presence before returning from fetchUserProfile
- docs: add "text" language tag to fenced code block in web/docs/routing.md
@GRACENOBLE GRACENOBLE merged commit ff5519a into main Jun 25, 2026
5 checks passed
@GRACENOBLE GRACENOBLE deleted the medium-priority branch June 25, 2026 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: backend Go REST API area: mobile Android app (Kotlin + Jetpack Compose) area: web Next.js web app

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant