Skip to content

Migrate Auth to Result-based error handling; add shared Result/error infra - #71

Merged
evertonschuster merged 1 commit into
mainfrom
pr/02-auth-result-and-shared-error-infra
Aug 2, 2026
Merged

Migrate Auth to Result-based error handling; add shared Result/error infra#71
evertonschuster merged 1 commit into
mainfrom
pr/02-auth-result-and-shared-error-infra

Conversation

@evertonschuster

@evertonschuster evertonschuster commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

Split out of #69 (part 2 — see that PR for the full picture; independently buildable and mergeable on its own, verified with a real build/test run rather than just a partial checkout).

  • features/auth/: Session/User/Tenant.create() and every AuthRepository method (initiateLogin/handleCallback/logout) return Result<T, E> instead of throwing. OidcAuthRepository's own try/catch around oidc-client-ts calls stays as a contained infrastructure-adapter boundary.
  • shared/: adds Result.ts (Result/success/failure/flatMapResult/combineResults), an ErrorReporter port + ConsoleErrorReporter adapter, and rewrites useAsync.ts to take () => Promise<Result<T, E>> instead of a throwing () => Promise<T>.
  • test/fixtures/: adds authEntityFixtures.ts (shadow Tenant/User/Session fixtures that unwrap the Result so call sites read like before) and unwrapResult.ts.

Temporary compatibility shims, flagged for removal in the next PR in this stack (which migrates Catalog to Result end-to-end and removes Tags): useCategories.ts/useServices.ts/useTags.ts wrap their existing list-fetch call in success()/failure() instead of letting it reject, and ~20 old Categories/Services/Tags test files that constructed Tenant/User directly now import the shadow fixtures instead. Neither changes any other behavior — they only satisfy useAsync's new contract so this PR builds and tests green on its own, without pulling in the unrelated Catalog reorg (confirmed: useAsync.ts/Result.ts/AuthenticatedHttpClient-adjacent changes are used by every catalog feature, not just Auth — this PR deliberately excludes AuthenticatedHttpClient/HttpClient/DeleteConfirmationDialog/useDeleteConfirmation since Auth doesn't actually depend on them; those land in the Catalog PR instead).

Test plan

  • npm install + npm run build --workspace=apps/admin-frontend — green (verified with a real build, not just type-checking a partial file set)
  • npm run lint --workspace=apps/admin-frontend — clean, 0 warnings
  • npm run format:check --workspace=apps/admin-frontend — clean
  • npm run test --workspace=apps/admin-frontend — 559/559 passing
  • scripts/sync_agent_skills.py --check, scripts/check_agent_governance.py, scripts/architecture_guard.py — all pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved login, callback, and logout error handling with clearer feedback instead of unexpected failures.
    • Logout failures now display a dedicated error message while local session data is cleared safely.
    • Invalid or incomplete authentication sessions are rejected and removed automatically.
    • Authentication callbacks now prevent navigation when sign-in completion fails.
    • Catalog category, service, and tag loading now handles missing tenant context and request failures gracefully, including empty results where appropriate.
  • Reliability

    • Improved handling of concurrent authentication callbacks and asynchronous loading states.

…infra

Split out of #69 (part 2 of N — see that PR for the full picture).

- features/auth/: Session/User/Tenant.create() and every AuthRepository
  method (initiateLogin/handleCallback/logout) return Result<T, E>
  instead of throwing; OidcAuthRepository's own try/catch around
  oidc-client-ts stays as a contained infrastructure-adapter boundary.
  See docs/adr/015-auth-result-errors.md (landing in a later PR in this
  stack, since it documents Catalog too).
- shared/: adds Result.ts (Result/success/failure/flatMapResult/
  combineResults), ErrorReporter port + ConsoleErrorReporter adapter,
  and rewrites useAsync.ts to take () => Promise<Result<T, E>> instead
  of a throwing () => Promise<T>.
- test/fixtures/: adds authEntityFixtures.ts (shadow Tenant/User/Session
  fixtures that unwrap the Result so call sites read like before) and
  unwrapResult.ts.

Temporary compatibility shims (flagged for removal in the next PR in
this stack, which migrates Catalog to Result end-to-end and removes
Tags): useCategories.ts/useServices.ts/useTags.ts wrap their existing
list-fetch call in success()/failure() instead of letting it reject, and
~20 old Categories/Services/Tags test files that constructed Tenant/User
directly now import the shadow fixtures instead — neither changes any
other behavior, they only satisfy useAsync's new contract so this PR
builds and tests green on its own without pulling in the unrelated
Catalog reorg.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a shared Result model and applies it to domain validation, OIDC authentication, async UI state, and catalog listing. Authentication failures now return typed results. Invalid sessions are cleared, and logout failures receive dedicated error mapping.

Changes

Result and async foundation

Layer / File(s) Summary
Shared Result and async contracts
apps/admin-frontend/src/shared/application/*, apps/admin-frontend/src/shared/presentation/hooks/useAsync.*, apps/admin-frontend/src/shared/infrastructure/observability/*
Adds Result constructors, transformations, aggregation, error reporting contracts, and Result-aware async state handling.
Domain and OIDC session validation
apps/admin-frontend/src/features/auth/domain/*, apps/admin-frontend/src/features/auth/infrastructure/oidcUserToSessionMapper.*, apps/admin-frontend/src/test/fixtures/*
Domain factories and OIDC session mapping return typed failures instead of throwing validation errors.
OIDC repository Result flow
apps/admin-frontend/src/features/auth/application/repositories/*, apps/admin-frontend/src/features/auth/infrastructure/OidcAuthRepository.*, apps/admin-frontend/src/features/auth/infrastructure/mapOidcErrorToAuthFlowError.ts
Authentication operations return Results, invalid cached sessions are removed, renewals are validated, and logout failures are mapped to AUTH_LOGOUT_FAILED.
Authentication application and UI flow
apps/admin-frontend/src/features/auth/application/use-cases/*, apps/admin-frontend/src/features/auth/presentation/*, apps/admin-frontend/src/app/*, apps/admin-frontend/src/test/fixtures/createFakeAppContainer.ts
Authentication actions and pages branch on Result success or failure. Callback single-flight behavior remains covered.
Catalog listing Result adapters
apps/admin-frontend/src/features/catalog/**
Category, service, and tag listing hooks return successful empty Results without tenant context and failure Results for listing errors. Tests use shared authentication fixtures.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LoginPage
  participant AuthProvider
  participant OidcAuthRepository
  participant OIDCProvider
  LoginPage->>AuthProvider: login()
  AuthProvider->>OidcAuthRepository: initiateLogin()
  OidcAuthRepository->>OIDCProvider: start OIDC redirect
  OIDCProvider-->>OidcAuthRepository: Result or mapped AuthFlowError
  OidcAuthRepository-->>AuthProvider: Result<void, AuthFlowError>
  AuthProvider-->>LoginPage: success or feedback error
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% 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 summarizes the main changes: migrating Auth to Result-based error handling and adding shared Result and error infrastructure.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pr/02-auth-result-and-shared-error-infra

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: 10

🧹 Nitpick comments (1)
apps/admin-frontend/src/features/auth/application/repositories/AuthRepository.ts (1)

19-21: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Trim inline ADR rationale to a short pointer.

Both comments restate the full docs/adr/014 rationale inline instead of referencing it briefly. The coding guideline requires keeping comments minimal and putting architectural rationale in ADRs, not in code comments.

  • apps/admin-frontend/src/features/auth/application/repositories/AuthRepository.ts#L19-L21: replace the full rationale with a short pointer, for example // null covers "no usable session" for all causes - see docs/adr/014.
  • apps/admin-frontend/src/features/auth/presentation/AuthProvider.tsx#L19-L21: apply the same trim.
🤖 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
`@apps/admin-frontend/src/features/auth/application/repositories/AuthRepository.ts`
around lines 19 - 21, Trim the inline ADR rationale in AuthRepository and
AuthProvider to a brief comment stating that null covers all “no usable session”
causes and pointing to docs/adr/014; update both specified files and preserve
the existing behavior.

Source: Path instructions

🤖 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
`@apps/admin-frontend/src/features/auth/application/use-cases/HandleAuthCallback.ts`:
- Around line 31-50: Restore the established throw-and-catch frontend error
contract across the auth flow: in
apps/admin-frontend/src/features/auth/application/use-cases/HandleAuthCallback.ts:31-50,
throw typed AuthFlowError failures instead of returning Result unions; in
apps/admin-frontend/src/features/auth/application/use-cases/Logout.ts:12-13,
restore Promise<void> and propagate typed exceptions; in
apps/admin-frontend/src/features/auth/presentation/AuthContext.ts:15-17, expose
the existing action contracts; in
apps/admin-frontend/src/features/auth/presentation/CallbackPage/CallbackPage.tsx:17-21,
catch failures and map them with toAuthFlowFeedback; and in
apps/admin-frontend/src/features/auth/application/test-helpers/createFakeAuthRepository.ts:14-17,
align fake defaults with the restored repository contract.

In `@apps/admin-frontend/src/features/auth/domain/entities/Session.ts`:
- Around line 23-28: Update Session.create to validate input.expiresAt with
Number.isFinite(input.expiresAt.getTime()) before constructing the Session,
returning InvalidSessionError for invalid dates so malformed expiry data fails
closed. Add coverage for an invalid expiry value in the session/OIDC mapping
tests.

In `@apps/admin-frontend/src/features/auth/domain/value-objects/Tenant.ts`:
- Line 2: Remove the Application-layer Result dependency from the domain
factories in Tenant, User.create, and Session.create. Restore the established
behavior of throwing the named InvalidTenantError or corresponding DomainError
subclasses on invalid input, while returning the domain value directly on
success. Remove the test-only unwrapResult compatibility wrappers and update
affected tests and callers to use the throw-and-catch contract.

In
`@apps/admin-frontend/src/features/auth/infrastructure/mapOidcErrorToAuthFlowError.ts`:
- Around line 110-127: Update the timeout and network messages returned by
mapLogoutError to refer to the authentication or logout service instead of the
login service, while preserving their existing meaning, codes, flowCode, and
retryable values.

In
`@apps/admin-frontend/src/features/auth/infrastructure/oidcUserToSessionMapper.ts`:
- Around line 27-29: Update the expires_at validation in oidcUserToSessionMapper
to reject undefined, non-finite, non-integer, out-of-range, and Date-invalid
claim values before constructing the session; return a typed mapping failure
consistent with MissingExpiryClaimError handling, and add tests covering each
invalid-value category and valid boundaries.
- Around line 20-50: Restore the established throw-and-catch error contract: in
apps/admin-frontend/src/features/auth/infrastructure/oidcUserToSessionMapper.ts:20-50,
update mapOidcUserToSession to throw the named mapping and domain-validation
errors instead of returning Result values. In
apps/admin-frontend/src/features/auth/presentation/LoginPage/LoginPage.tsx:21-26,
catch login failures and pass the caught error to toAuthFlowFeedback. Update the
related auth repository interfaces, use cases, fixtures, and tests to use the
established DomainError/ApiError exception flow consistently.

In `@apps/admin-frontend/src/shared/application/ErrorReporter.ts`:
- Around line 2-9: Remove the ADR-level and behavior-description comments
without changing behavior: in
apps/admin-frontend/src/shared/application/ErrorReporter.ts lines 2-9 remove the
interface and error-flow-policy comments; in
apps/admin-frontend/src/shared/application/Result.ts lines 25-37 remove helper
and pipeline-policy comments; in
apps/admin-frontend/src/shared/infrastructure/observability/ConsoleErrorReporter.ts
lines 3-5 move the adapter-selection rationale to an ADR; in
apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts lines 58-62 and
161-162 remove the policy and behavior comments, and at lines 146-150 retain
only one short comment explaining the necessary lint suppression.

In `@apps/admin-frontend/src/shared/application/Result.ts`:
- Around line 1-46: Remove the frontend Result contract and helpers from
apps/admin-frontend/src/shared/application/Result.ts. In
apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts lines 63-121,
restore the callback and async handling to Promise<T>, catching thrown
DomainError and ApiError instances and mapping them through the existing error
flow; do not apply the backend-only Result pattern in the frontend.

In `@apps/admin-frontend/src/shared/presentation/hooks/useAsync.test.tsx`:
- Around line 34-36: Update the error assertion in the useAsync test to first
require result.current.error to be an Error with toBeInstanceOf(Error), then
narrow it and assert its message is "boom" so non-Error failures cannot pass
silently.

In `@apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts`:
- Around line 63-64: Restore useAsync’s asyncFn contract to return Promise<T>
instead of Promise<Result<T, E>>, and reinstate its existing exception-catching
and typed error-mapping behavior. Update the hook’s state handling and callers
such as catalog list hooks to pass plain async operations without converting
failures into Result values, while mapping DomainError and ApiError through
useAsync.

---

Nitpick comments:
In
`@apps/admin-frontend/src/features/auth/application/repositories/AuthRepository.ts`:
- Around line 19-21: Trim the inline ADR rationale in AuthRepository and
AuthProvider to a brief comment stating that null covers all “no usable session”
causes and pointing to docs/adr/014; update both specified files and preserve
the existing behavior.
🪄 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 Plus

Run ID: 8365899e-c1dd-4ebb-ba6e-d5b6b76d7a7c

📥 Commits

Reviewing files that changed from the base of the PR and between 033baf9 and 99d62e3.

📒 Files selected for processing (69)
  • apps/admin-frontend/src/app/composition/container.test.ts
  • apps/admin-frontend/src/app/layouts/AdminLayout.test.tsx
  • apps/admin-frontend/src/features/auth/application/errors/AuthFlowError.ts
  • apps/admin-frontend/src/features/auth/application/repositories/AuthRepository.ts
  • apps/admin-frontend/src/features/auth/application/test-helpers/createFakeAuthRepository.ts
  • apps/admin-frontend/src/features/auth/application/use-cases/GetCurrentSession.test.ts
  • apps/admin-frontend/src/features/auth/application/use-cases/HandleAuthCallback.test.ts
  • apps/admin-frontend/src/features/auth/application/use-cases/HandleAuthCallback.ts
  • apps/admin-frontend/src/features/auth/application/use-cases/InitiateLogin.test.ts
  • apps/admin-frontend/src/features/auth/application/use-cases/InitiateLogin.ts
  • apps/admin-frontend/src/features/auth/application/use-cases/Logout.test.ts
  • apps/admin-frontend/src/features/auth/application/use-cases/Logout.ts
  • apps/admin-frontend/src/features/auth/domain/entities/Session.test.ts
  • apps/admin-frontend/src/features/auth/domain/entities/Session.ts
  • apps/admin-frontend/src/features/auth/domain/entities/User.test.ts
  • apps/admin-frontend/src/features/auth/domain/entities/User.ts
  • apps/admin-frontend/src/features/auth/domain/value-objects/Tenant.test.ts
  • apps/admin-frontend/src/features/auth/domain/value-objects/Tenant.ts
  • apps/admin-frontend/src/features/auth/infrastructure/MissingExpiryClaimError.ts
  • apps/admin-frontend/src/features/auth/infrastructure/OidcAuthRepository.test.ts
  • apps/admin-frontend/src/features/auth/infrastructure/OidcAuthRepository.ts
  • apps/admin-frontend/src/features/auth/infrastructure/mapOidcErrorToAuthFlowError.ts
  • apps/admin-frontend/src/features/auth/infrastructure/oidcUserToSessionMapper.test.ts
  • apps/admin-frontend/src/features/auth/infrastructure/oidcUserToSessionMapper.ts
  • apps/admin-frontend/src/features/auth/presentation/AuthContext.test.ts
  • apps/admin-frontend/src/features/auth/presentation/AuthContext.ts
  • apps/admin-frontend/src/features/auth/presentation/AuthProvider.test.tsx
  • apps/admin-frontend/src/features/auth/presentation/AuthProvider.tsx
  • apps/admin-frontend/src/features/auth/presentation/CallbackPage/CallbackPage.test.tsx
  • apps/admin-frontend/src/features/auth/presentation/CallbackPage/CallbackPage.tsx
  • apps/admin-frontend/src/features/auth/presentation/LoginPage/LoginPage.test.tsx
  • apps/admin-frontend/src/features/auth/presentation/LoginPage/LoginPage.tsx
  • apps/admin-frontend/src/features/auth/presentation/ProtectedRoute.test.tsx
  • apps/admin-frontend/src/features/auth/presentation/TenantBoundary.test.tsx
  • apps/admin-frontend/src/features/auth/presentation/authFlowFeedback.ts
  • apps/admin-frontend/src/features/auth/presentation/useAuthenticatedTenant.test.tsx
  • apps/admin-frontend/src/features/catalog/application/use-cases/categories/CreateCategory.test.ts
  • apps/admin-frontend/src/features/catalog/application/use-cases/categories/DeleteCategory.test.ts
  • apps/admin-frontend/src/features/catalog/application/use-cases/categories/ListCategories.test.ts
  • apps/admin-frontend/src/features/catalog/application/use-cases/categories/UpdateCategory.test.ts
  • apps/admin-frontend/src/features/catalog/application/use-cases/services/CreateService.test.ts
  • apps/admin-frontend/src/features/catalog/application/use-cases/services/DeleteService.test.ts
  • apps/admin-frontend/src/features/catalog/application/use-cases/services/ListServices.test.ts
  • apps/admin-frontend/src/features/catalog/application/use-cases/services/UpdateService.test.ts
  • apps/admin-frontend/src/features/catalog/application/use-cases/tags/CreateTag.test.ts
  • apps/admin-frontend/src/features/catalog/application/use-cases/tags/DeleteTag.test.ts
  • apps/admin-frontend/src/features/catalog/application/use-cases/tags/ListTags.test.ts
  • apps/admin-frontend/src/features/catalog/application/use-cases/tags/UpdateTag.test.ts
  • apps/admin-frontend/src/features/catalog/infrastructure/repositories/ApiCategoryRepository.test.ts
  • apps/admin-frontend/src/features/catalog/infrastructure/repositories/ApiServiceRepository.test.ts
  • apps/admin-frontend/src/features/catalog/infrastructure/repositories/ApiTagRepository.test.ts
  • apps/admin-frontend/src/features/catalog/presentation/categories/CategoriesPage.test.tsx
  • apps/admin-frontend/src/features/catalog/presentation/categories/hooks/useCategories.test.tsx
  • apps/admin-frontend/src/features/catalog/presentation/categories/hooks/useCategories.ts
  • apps/admin-frontend/src/features/catalog/presentation/services/ServicesPage.testSupport.tsx
  • apps/admin-frontend/src/features/catalog/presentation/services/hooks/useServices.test.tsx
  • apps/admin-frontend/src/features/catalog/presentation/services/hooks/useServices.ts
  • apps/admin-frontend/src/features/catalog/presentation/tags/TagsPage.test.tsx
  • apps/admin-frontend/src/features/catalog/presentation/tags/hooks/useTags.test.tsx
  • apps/admin-frontend/src/features/catalog/presentation/tags/hooks/useTags.ts
  • apps/admin-frontend/src/shared/application/ErrorReporter.ts
  • apps/admin-frontend/src/shared/application/Result.test.ts
  • apps/admin-frontend/src/shared/application/Result.ts
  • apps/admin-frontend/src/shared/infrastructure/observability/ConsoleErrorReporter.ts
  • apps/admin-frontend/src/shared/presentation/hooks/useAsync.test.tsx
  • apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts
  • apps/admin-frontend/src/test/fixtures/authEntityFixtures.ts
  • apps/admin-frontend/src/test/fixtures/createFakeAppContainer.ts
  • apps/admin-frontend/src/test/fixtures/unwrapResult.ts

Comment on lines +31 to +50
async execute(callbackUrl: string): Promise<Result<CompletedAuthCallback, AuthFlowError>> {
if (this.cached?.url !== callbackUrl) {
this.cached = { url: callbackUrl, promise: this.performCallback(callbackUrl) }
}

return this.cached.promise
}

private async performCallback(callbackUrl: string): Promise<CompletedAuthCallback> {
const { session, returnTo } = await this.authRepository.handleCallback(callbackUrl)

return {
tenantContext: toTenantContext(session.user),
returnTo: resolvePostLoginPath(returnTo),
private async performCallback(
callbackUrl: string,
): Promise<Result<CompletedAuthCallback, AuthFlowError>> {
const result = await this.authRepository.handleCallback(callbackUrl)
if (!result.success) {
return result
}

return success({
tenantContext: toTenantContext(result.value.session.user),
returnTo: resolvePostLoginPath(result.value.returnTo),
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Keep the established frontend error contract.

This migration introduces a second error-handling model in apps/admin-frontend. Existing frontend flows use typed thrown errors and presentation-layer error mapping. The Result contract also requires temporary Catalog compatibility adapters, which confirms the contract split.

  • apps/admin-frontend/src/features/auth/application/use-cases/HandleAuthCallback.ts#L31-L50: restore the throw-and-catch callback contract with typed AuthFlowError failures.
  • apps/admin-frontend/src/features/auth/application/use-cases/Logout.ts#L12-L13: restore the Promise<void> use-case contract and propagate typed failures through exceptions.
  • apps/admin-frontend/src/features/auth/presentation/AuthContext.ts#L15-L17: expose the existing action contracts instead of Result unions.
  • apps/admin-frontend/src/features/auth/presentation/CallbackPage/CallbackPage.tsx#L17-L21: restore error catching and toAuthFlowFeedback mapping.
  • apps/admin-frontend/src/features/auth/application/test-helpers/createFakeAuthRepository.ts#L14-L17: update fake defaults to match the restored repository contract.

Based on learnings, apps/admin-frontend must use the established throw-and-catch convention for domain and HTTP failures and must not apply the backend-only Result policy to frontend code.

📍 Affects 5 files
  • apps/admin-frontend/src/features/auth/application/use-cases/HandleAuthCallback.ts#L31-L50 (this comment)
  • apps/admin-frontend/src/features/auth/application/use-cases/Logout.ts#L12-L13
  • apps/admin-frontend/src/features/auth/presentation/AuthContext.ts#L15-L17
  • apps/admin-frontend/src/features/auth/presentation/CallbackPage/CallbackPage.tsx#L17-L21
  • apps/admin-frontend/src/features/auth/application/test-helpers/createFakeAuthRepository.ts#L14-L17
🤖 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
`@apps/admin-frontend/src/features/auth/application/use-cases/HandleAuthCallback.ts`
around lines 31 - 50, Restore the established throw-and-catch frontend error
contract across the auth flow: in
apps/admin-frontend/src/features/auth/application/use-cases/HandleAuthCallback.ts:31-50,
throw typed AuthFlowError failures instead of returning Result unions; in
apps/admin-frontend/src/features/auth/application/use-cases/Logout.ts:12-13,
restore Promise<void> and propagate typed exceptions; in
apps/admin-frontend/src/features/auth/presentation/AuthContext.ts:15-17, expose
the existing action contracts; in
apps/admin-frontend/src/features/auth/presentation/CallbackPage/CallbackPage.tsx:17-21,
catch failures and map them with toAuthFlowFeedback; and in
apps/admin-frontend/src/features/auth/application/test-helpers/createFakeAuthRepository.ts:14-17,
align fake defaults with the restored repository contract.

Source: Learnings

Comment on lines +23 to +28
static create(input: CreateSessionInput): Result<Session, InvalidSessionError> {
if (input.accessToken.trim().length === 0) {
throw new InvalidSessionError('Session access token must not be empty')
return failure(new InvalidSessionError('Session access token must not be empty'))
}

return new Session(input.user, input.accessToken, input.expiresAt)
return success(new Session(input.user, input.accessToken, input.expiresAt))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject invalid session expiry values.

mapOidcUserToSession only rejects expires_at === undefined before it creates a Date. A nonnumeric or out-of-range runtime value creates an invalid Date. isExpiredAt then compares against NaN and returns false, which can treat the session as unexpired.

Validate Number.isFinite(input.expiresAt.getTime()) before constructing the session. Add coverage for an invalid expiry value so malformed OIDC data fails closed.

🤖 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 `@apps/admin-frontend/src/features/auth/domain/entities/Session.ts` around
lines 23 - 28, Update Session.create to validate input.expiresAt with
Number.isFinite(input.expiresAt.getTime()) before constructing the Session,
returning InvalidSessionError for invalid dates so malformed expiry data fails
closed. Add coverage for an invalid expiry value in the session/OIDC mapping
tests.

@@ -1,4 +1,5 @@
import { InvalidTenantError } from '@/features/auth/domain/errors/InvalidTenantError'
import { failure, success, type Result } from '@/shared/application/Result'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Remove the Application-layer Result dependency from the domain factory.

Tenant is a Domain type, but it imports @/shared/application/Result. User.ts and Session.ts repeat the same outward dependency. This violates the required Domain → Application inward dependency direction.

This also replaces the established frontend DomainError throw-and-catch contract. It forces test-only unwrapResult compatibility wrappers. Restore InvalidTenantError throwing here, apply the same change to User.create and Session.create, and remove the compatibility fixture layer.

As per coding guidelines, each app must use “Domain → Application → Infrastructure/Presentation layering and inward-only dependencies.” Based on learnings, domain factories should throw named DomainError subclasses and should not introduce the backend Result pattern into apps/admin-frontend.

Also applies to: 11-18

🤖 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 `@apps/admin-frontend/src/features/auth/domain/value-objects/Tenant.ts` at line
2, Remove the Application-layer Result dependency from the domain factories in
Tenant, User.create, and Session.create. Restore the established behavior of
throwing the named InvalidTenantError or corresponding DomainError subclasses on
invalid input, while returning the domain value directly on success. Remove the
test-only unwrapResult compatibility wrappers and update affected tests and
callers to use the throw-and-catch contract.

Sources: Coding guidelines, Learnings

Comment on lines +110 to +127
export function mapLogoutError(error: unknown): AuthFlowError {
if (error instanceof ErrorTimeout) {
return new AuthFlowError({
code: 'timeout',
flowCode: 'AUTH_LOGOUT_FAILED',
message: 'O serviço de login demorou para confirmar a saída. Tente novamente.',
retryable: true,
})
}

if (error instanceof TypeError) {
return new AuthFlowError({
code: 'network',
flowCode: 'AUTH_LOGOUT_FAILED',
message: 'Não foi possível conectar ao serviço de login para concluir a saída.',
retryable: true,
})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix "login" wording in logout error messages.

O serviço de login demorou para confirmar a saída. and Não foi possível conectar ao serviço de login para concluir a saída. both name the login service during a logout failure. Update the wording to refer to the authentication or logout service, not the login service.

✏️ Proposed wording fix
   if (error instanceof ErrorTimeout) {
     return new AuthFlowError({
       code: 'timeout',
       flowCode: 'AUTH_LOGOUT_FAILED',
-      message: 'O serviço de login demorou para confirmar a saída. Tente novamente.',
+      message: 'O serviço de autenticação demorou para confirmar a saída. Tente novamente.',
       retryable: true,
     })
   }

   if (error instanceof TypeError) {
     return new AuthFlowError({
       code: 'network',
       flowCode: 'AUTH_LOGOUT_FAILED',
-      message: 'Não foi possível conectar ao serviço de login para concluir a saída.',
+      message: 'Não foi possível conectar ao serviço de autenticação para concluir a saída.',
       retryable: true,
     })
   }
📝 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.

Suggested change
export function mapLogoutError(error: unknown): AuthFlowError {
if (error instanceof ErrorTimeout) {
return new AuthFlowError({
code: 'timeout',
flowCode: 'AUTH_LOGOUT_FAILED',
message: 'O serviço de login demorou para confirmar a saída. Tente novamente.',
retryable: true,
})
}
if (error instanceof TypeError) {
return new AuthFlowError({
code: 'network',
flowCode: 'AUTH_LOGOUT_FAILED',
message: 'Não foi possível conectar ao serviço de login para concluir a saída.',
retryable: true,
})
}
export function mapLogoutError(error: unknown): AuthFlowError {
if (error instanceof ErrorTimeout) {
return new AuthFlowError({
code: 'timeout',
flowCode: 'AUTH_LOGOUT_FAILED',
message: 'O serviço de autenticação demorou para confirmar a saída. Tente novamente.',
retryable: true,
})
}
if (error instanceof TypeError) {
return new AuthFlowError({
code: 'network',
flowCode: 'AUTH_LOGOUT_FAILED',
message: 'Não foi possível conectar ao serviço de autenticação para concluir a saída.',
retryable: true,
})
}
🤖 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
`@apps/admin-frontend/src/features/auth/infrastructure/mapOidcErrorToAuthFlowError.ts`
around lines 110 - 127, Update the timeout and network messages returned by
mapLogoutError to refer to the authentication or logout service instead of the
login service, while preserving their existing meaning, codes, flowCode, and
retryable values.

Comment on lines +20 to 50
export function mapOidcUserToSession(oidcUser: OidcUser): Result<Session, SessionMappingError> {
const tenantId = oidcUser.profile.tenant_id

if (typeof tenantId !== 'string' || tenantId.trim().length === 0) {
throw new MissingTenantClaimError()
return failure(new MissingTenantClaimError())
}

if (oidcUser.expires_at === undefined) {
throw new Error('oidc-client-ts User is missing expires_at; cannot determine session validity')
return failure(new MissingExpiryClaimError())
}

const tenantResult = Tenant.create(tenantId)
if (!tenantResult.success) {
return tenantResult
}

const tenant = Tenant.create(tenantId)
const user = User.create({
const userResult = User.create({
id: oidcUser.profile.sub,
tenant,
tenant: tenantResult.value,
...(typeof oidcUser.profile.email === 'string' ? { email: oidcUser.profile.email } : {}),
...(typeof oidcUser.profile.name === 'string' ? { name: oidcUser.profile.name } : {}),
})
if (!userResult.success) {
return userResult
}

return Session.create({
user,
user: userResult.value,
accessToken: oidcUser.access_token,
expiresAt: new Date(oidcUser.expires_at * 1000),
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Restore the established frontend exception flow.

These changes introduce a second frontend error contract based on Result. The established frontend contract uses named DomainError and ApiError exceptions, then maps caught errors in presentation flows.

  • apps/admin-frontend/src/features/auth/infrastructure/oidcUserToSessionMapper.ts#L20-L50: restore named thrown mapping and domain-validation errors instead of returning Result.
  • apps/admin-frontend/src/features/auth/presentation/LoginPage/LoginPage.tsx#L21-L26: catch the login failure and pass it to toAuthFlowFeedback.

Update the related repository interfaces, use cases, fixtures, and tests in the same change.

Based on learnings, frontend domain and HTTP failures must use the established throw-and-catch convention rather than the backend Result pattern.

📍 Affects 2 files
  • apps/admin-frontend/src/features/auth/infrastructure/oidcUserToSessionMapper.ts#L20-L50 (this comment)
  • apps/admin-frontend/src/features/auth/presentation/LoginPage/LoginPage.tsx#L21-L26
🤖 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
`@apps/admin-frontend/src/features/auth/infrastructure/oidcUserToSessionMapper.ts`
around lines 20 - 50, Restore the established throw-and-catch error contract: in
apps/admin-frontend/src/features/auth/infrastructure/oidcUserToSessionMapper.ts:20-50,
update mapOidcUserToSession to throw the named mapping and domain-validation
errors instead of returning Result values. In
apps/admin-frontend/src/features/auth/presentation/LoginPage/LoginPage.tsx:21-26,
catch login failures and pass the caught error to toAuthFlowFeedback. Update the
related auth repository interfaces, use cases, fixtures, and tests to use the
established DomainError/ApiError exception flow consistently.

Source: Learnings

Comment on lines 27 to +29
if (oidcUser.expires_at === undefined) {
throw new Error('oidc-client-ts User is missing expires_at; cannot determine session validity')
return failure(new MissingExpiryClaimError())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject malformed expires_at values.

This check only rejects undefined. A defined NaN, infinity, non-integer, or out-of-range value produces Invalid Date. Session.isExpiredAt() then compares against NaN and returns false, so cached-session recovery can accept the malformed session as non-expired.

Validate that the claim is a finite integer and produces a valid Date. Return a typed mapping failure and add invalid-value tests.

As per coding guidelines, domain factories must runtime-validate externally sourced generated API values, including finite numeric and integer constraints.

🤖 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
`@apps/admin-frontend/src/features/auth/infrastructure/oidcUserToSessionMapper.ts`
around lines 27 - 29, Update the expires_at validation in
oidcUserToSessionMapper to reject undefined, non-finite, non-integer,
out-of-range, and Date-invalid claim values before constructing the session;
return a typed mapping failure consistent with MissingExpiryClaimError handling,
and add tests covering each invalid-value category and valid boundaries.

Source: Coding guidelines

Comment on lines +2 to +9
/** Which capture surface this came through - e.g. "react.onCaughtError", "window.unhandledrejection". */
readonly source: string
readonly extra?: Record<string, unknown>
}

// The one sink every genuinely-unexpected failure (a bug, not an expected
// Result.Failure) flows through - business/presentation code never reports
// directly, only the capture surfaces wired in main.tsx do (docs/adr/014).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Move architectural rationale out of source comments.

The shared root cause is ADR-level and behavior-description comments in implementation files. Keep only short comments that explain non-obvious constraints.

  • apps/admin-frontend/src/shared/application/ErrorReporter.ts#L2-L9: remove interface-description and error-flow-policy comments.
  • apps/admin-frontend/src/shared/application/Result.ts#L25-L37: remove helper behavior and pipeline-policy comments.
  • apps/admin-frontend/src/shared/infrastructure/observability/ConsoleErrorReporter.ts#L3-L5: move adapter-selection rationale to an ADR.
  • apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts#L58-L62: remove Result-flow policy rationale.
  • apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts#L146-L150: reduce the permitted lint-suppression reason to one short comment.
  • apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts#L161-L162: remove behavior-description comments.

As per coding guidelines: comments must explain only non-obvious reasons and must not contain ADR-level rationale.

📍 Affects 4 files
  • apps/admin-frontend/src/shared/application/ErrorReporter.ts#L2-L9 (this comment)
  • apps/admin-frontend/src/shared/application/Result.ts#L25-L37
  • apps/admin-frontend/src/shared/infrastructure/observability/ConsoleErrorReporter.ts#L3-L5
  • apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts#L58-L62
  • apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts#L146-L150
  • apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts#L161-L162
🤖 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 `@apps/admin-frontend/src/shared/application/ErrorReporter.ts` around lines 2 -
9, Remove the ADR-level and behavior-description comments without changing
behavior: in apps/admin-frontend/src/shared/application/ErrorReporter.ts lines
2-9 remove the interface and error-flow-policy comments; in
apps/admin-frontend/src/shared/application/Result.ts lines 25-37 remove helper
and pipeline-policy comments; in
apps/admin-frontend/src/shared/infrastructure/observability/ConsoleErrorReporter.ts
lines 3-5 move the adapter-selection rationale to an ADR; in
apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts lines 58-62 and
161-162 remove the policy and behavior comments, and at lines 146-150 retain
only one short comment explaining the necessary lint suppression.

Source: Coding guidelines

Comment on lines +1 to +46
export interface Success<T> {
readonly success: true
readonly value: T
}

export interface Failure<E> {
readonly success: false
readonly error: E
}

export type Result<T, E> = Success<T> | Failure<E>

export function success<T>(value: T): Success<T> {
return { success: true, value }
}

export function failure<E>(error: E): Failure<E> {
return { success: false, error }
}

export function mapResult<T, U, E>(result: Result<T, E>, transform: (value: T) => U): Result<U, E> {
return result.success ? success(transform(result.value)) : result
}

// Chains a Result-returning step onto another Result without nesting -
// the composition primitive that keeps a pipeline of possibly-failing
// steps (decode -> domain validation) throw-free end to end.
export function flatMapResult<T, U, E>(
result: Result<T, E>,
transform: (value: T) => Result<U, E>,
): Result<U, E> {
return result.success ? transform(result.value) : result
}

// Fail-fast: the first Failure in the list short-circuits the rest: the
// same "any one item can spoil the batch" semantics as Promise.all, for
// synchronous Results instead of promises.
export function combineResults<T, E>(results: readonly Result<T, E>[]): Result<T[], E> {
const values: T[] = []
for (const result of results) {
if (!result.success) {
return result
}
values.push(result.value)
}
return success(values)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Restore the existing exception-based frontend error flow.

The shared Result API and the useAsync callback contract introduce a Result-based frontend error model. This conflicts with the established DomainError and ApiError exception flow.

  • apps/admin-frontend/src/shared/application/Result.ts#L1-L46: remove the shared Result contract from the frontend error path.
  • apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts#L63-L121: restore Promise<T> handling and catch/map typed thrown errors in the hook.

Based on learnings: apps/admin-frontend must not apply the backend-only Result-pattern policy.

📍 Affects 2 files
  • apps/admin-frontend/src/shared/application/Result.ts#L1-L46 (this comment)
  • apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts#L63-L121
🤖 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 `@apps/admin-frontend/src/shared/application/Result.ts` around lines 1 - 46,
Remove the frontend Result contract and helpers from
apps/admin-frontend/src/shared/application/Result.ts. In
apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts lines 63-121,
restore the callback and async handling to Promise<T>, catching thrown
DomainError and ApiError instances and mapping them through the existing error
flow; do not apply the backend-only Result pattern in the frontend.

Source: Learnings

Comment on lines +34 to +36
if (result.current.error instanceof Error) {
expect(result.current.error.message).toBe('boom')
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the error type before reading its message.

If result.current.error is not an Error, this test performs no message assertion and still passes. Assert toBeInstanceOf(Error) before narrowing and checking "boom".

🤖 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 `@apps/admin-frontend/src/shared/presentation/hooks/useAsync.test.tsx` around
lines 34 - 36, Update the error assertion in the useAsync test to first require
result.current.error to be an Error with toBeInstanceOf(Error), then narrow it
and assert its message is "boom" so non-Error failures cannot pass silently.

Comment on lines +63 to +64
export function useAsync<T, E = unknown>(
asyncFn: () => Promise<Result<T, E>>,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Keep useAsync on the existing exception-and-catch contract.

This signature requires callers to convert thrown domain and API errors into Result.Failure. That duplicates the existing presentation error boundary and forces adapters such as catalog list hooks to catch errors only to rewrap them. Restore the Promise<T> callback contract and its typed error mapping.

Based on learnings: presentation flows must catch and map DomainError and ApiError through useAsync, rather than adopting a frontend Result pattern.

Also applies to: 111-121

🤖 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 `@apps/admin-frontend/src/shared/presentation/hooks/useAsync.ts` around lines
63 - 64, Restore useAsync’s asyncFn contract to return Promise<T> instead of
Promise<Result<T, E>>, and reinstate its existing exception-catching and typed
error-mapping behavior. Update the hook’s state handling and callers such as
catalog list hooks to pass plain async operations without converting failures
into Result values, while mapping DomainError and ApiError through useAsync.

Source: Learnings

@evertonschuster
evertonschuster merged commit 110cb7f into main Aug 2, 2026
18 checks passed
@evertonschuster
evertonschuster deleted the pr/02-auth-result-and-shared-error-infra branch August 2, 2026 14:58
evertonschuster added a commit that referenced this pull request Aug 2, 2026
…ervices; physical reorg (#75)

* Migrate Catalog (Categories/Services/Tags) to Result errors; remove Services vertical; physical reorg

Split out of #69 (part 3 of 4 — see that PR for the full picture).
Recreated from origin/main after #70 and #71 merged, since this repo's
convention (and the split-large-coderabbit-pr skill) is a sequential
series, not stacking on an unmerged branch — CodeRabbit also doesn't
review PRs whose base isn't the default branch, so stacking silently
skipped review for this and the next PR in the series. Same content as
the original #72, just re-based; no functional change.

This PR is larger than the <100-file target used for the other PRs in
this series, deliberately — see "Why this couldn't be split further"
below.

- Categories, Services, and Tags all move to the Result-based error
  convention docs/adr/014 establishes: domain entities' create() methods,
  mappers, and API repositories return Result<T, AppError> instead of
  throwing; useAsync.ts (already Result-based, landed in #71) is the one
  hook every feature's data layer builds on now.
- app/composition/container.ts's CatalogFacade drops the use-case-class
  indirection (ListCategories/CreateTag/etc. as separate classes) for
  direct repository delegation (`{ execute: repo.method }`) - there's no
  orchestration between the facade and the repository, so the extra
  class per operation wasn't earning its keep. The 24 now-orphaned
  use-case-class files (application/use-cases/{categories,services,tags}/)
  are deleted.
- Services' frontend implementation (ServicesPage, ServiceForm, six
  ServicesPage.*.test.tsx files, all its components/hooks/models) is
  fully removed, reverting `/services` to a placeholder page
  (app/pages/ServicesPage/ServicesPage.tsx) - this vertical is going
  back to `stub` status, see docs/STATUS.md.
- Categories moves to a routed create/edit dialog
  (features/catalog/presentation/categories/pages/CategoriesListPage/,
  .../CategoryEditorDialog/) per docs/adr/012, replacing the old flat
  CategoriesPage.tsx/useCategories.ts/CategoryEditorDialog.tsx shape.
- Tags gets the equivalent internal move (hooks/useTagEditor.ts,
  pages/TagEditorDialog.tsx) and its own Result migration
  (Tag.ts/tagMapper.ts/ApiTagRepository.ts) - Tags itself is not
  being removed here, just migrated; its removal is a later PR in
  this stack (docs/adr/016).
- shared/: AuthenticatedHttpClient's get/post/put/delete now return
  Result<T, AppError> instead of throwing; DeleteConfirmationDialog
  takes entityName/entityType instead of a raw title/description pair;
  useCreateInline is removed (no longer used once Services - its only
  consumer - is gone).
- Also removes .husky/pre-commit and fixes architecture_guard.py's
  precommit check accordingly (already merged independently via #70;
  included here too since this branch's own ancestry needed it before
  #70 existed).

## Why this couldn't be split further

I initially tried a narrower "Category-only foundation" PR (~99 files)
deferring Services/Tags. That failed a real build: app/composition/
container.ts wires TagRepository with its *new* method signature
directly (tagRepository.listAll(options) instead of the old
(tenantContext, options) two-arg form) - not just a return-type
change useAsync-style, but the interface itself. Making that build
without also migrating TagRepository/ApiTagRepository/tagMapper/Tag.ts
for real isn't a smaller wrapper shim - it's the same size of work as
just finishing the migration, since there's no reduced version of an
interface signature. Categories, Services, and Tags share
container.ts's catalog wiring, router.tsx, and AuthenticatedHttpClient
tightly enough that they're one atomic, verified-buildable unit at this
layer - mirroring why Auth couldn't be split from Catalog either,
just one layer down.

## Test plan

- [x] `npm install` + `npm run build --workspace=apps/admin-frontend` — green
- [x] `npm run lint --workspace=apps/admin-frontend` — clean, 0 warnings
- [x] `npm run format:check --workspace=apps/admin-frontend` — clean
- [x] `npm run test --workspace=apps/admin-frontend` — 368/368 passing
- [x] `scripts/sync_agent_skills.py --check`, `scripts/check_agent_governance.py`, `scripts/architecture_guard.py` — all pass

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* Fix categories-mobile.spec.ts's mock for the GET-by-id endpoint

useCategoryEditor fetches its own category via GET /api/v1/categories/{id}
(docs/adr/013), but this spec's route mock matched any /api/v1/categories*
path and always returned the full list array regardless of whether the
request was for the collection or a single id - so the by-id fetch
received an array instead of a CategoryDto, and the edit dialog's Nome
field never populated. Mock now inspects the last path segment and
returns the matching single category (404 if not found) for a by-id GET,
the full list otherwise.

Verified against the real Playwright suite (production build + preview,
matching CI): all 10 e2e specs pass, including this one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
evertonschuster added a commit that referenced this pull request Aug 2, 2026
Split out of #69 (final part of this series — see that PR for the full
picture). Recreated from origin/main after #70/#71/#75 merged, since
this repo's convention (and the split-large-coderabbit-pr skill) is a
sequential series, not stacking on an unmerged branch. Same content as
the original #73, just re-based; no functional change.

Removes the entire Tags vertical from apps/admin-frontend (domain,
application, infrastructure, presentation, MSW handlers, E2E specs, nav
entry, route, and catalog facade wiring) while intentionally retaining
the backend Tag domain entity and /api/v1/tags endpoints, including
Service's many-to-many relationship to Tag - a project-owner decision,
see docs/adr/016-remove-tags-frontend.md. Categories replaces Tags as
the reference CRUD implementation throughout the docs and the
agenza-frontend-feature skill.

## Test plan

- [x] `npm install` + `npm run build --workspace=apps/admin-frontend` — green
- [x] `npm run lint --workspace=apps/admin-frontend` — clean, 0 warnings
- [x] `npm run format:check --workspace=apps/admin-frontend` — clean
- [x] `npm run test --workspace=apps/admin-frontend` — 305/305 passing
- [x] `npx playwright test` (full e2e suite, production build + preview) — 8/8 passing
- [x] `scripts/sync_agent_skills.py --check`, `scripts/check_agent_governance.py`, `scripts/architecture_guard.py` — all pass

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
evertonschuster added a commit that referenced this pull request Aug 2, 2026
…#77)

These 3 files (agent-skills/agenza-frontend-feature/SKILL.md and its two
synced copies under .claude/skills/ and .agents/skills/) live at the repo
root, outside apps/admin-frontend/ — every diff I computed while splitting
#69 into #70/#71/#75/#76 was scoped to apps/admin-frontend (and backend/
for #70), so these files' accumulated updates from this session (Catalog
Result migration, Auth Result migration, and finally the Tags-removal
doc pass replacing TagsPage/TagForm with Categories as the reference
implementation) never made it into any of the split PRs, even though the
actual code changes they describe are all correctly merged.

Content taken directly from the original branch's final commit
(4911abb), already reviewed and governance-checked at the time. Verified
again here against the current merged main: sync_agent_skills.py --check,
check_agent_governance.py, and architecture_guard.py all pass, and the
file paths the skill references (CategoriesListPage.tsx, CategoryForm.tsx,
categoryMapper.ts, AdminLayout.tsx) all exist in the current tree.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant