Skip to content

Display active identity in application shell - #49

Merged
rd-code-23 merged 1 commit into
mainfrom
feature/dashboard
Aug 23, 2026
Merged

rd-code-23 merged 1 commit into
mainfrom
feature/dashboard

Conversation

@rd-code-23

Copy link
Copy Markdown
Owner

Summary

  • display the active registered-user or guest identity in the shared application shell
  • show the identity in both the desktop sidebar and mobile header
  • make authentication state a discriminated union so authenticated state guarantees a user
  • keep logout and guest-exit actions outside this focused change

Problem

After entering the protected application area, there was no visible indication of whether the visitor was using a registered account or temporary guest mode. The application shell also had no typed identity available for future account-related controls.

Identity data flow

The existing access boundary already determines whether a visitor may enter. This PR extends that responsibility so it produces one of two valid identities:

type ApplicationIdentity =
  | { kind: 'guest' }
  | { kind: 'user'; user: PublicUser };

The data flows through the shared pathless route layout:

guest marker or /auth/me response
              ↓
DashboardAccessBoundary
              ↓
ApplicationIdentity
              ↓
_app layout
              ↓
ApplicationShell
              ↓
IdentitySummary

Render-prop pattern

DashboardAccessBoundary receives a function as its children prop:

<DashboardAccessBoundary>
  {(identity) => (
    <ApplicationShell identity={identity}>
      <Outlet />
    </ApplicationShell>
  )}
</DashboardAccessBoundary>

This is a render prop. Once the boundary resolves access, it calls that function with either { kind: 'guest' } or { kind: 'user', user }. The _app route can then pass the resolved identity into the shell while <Outlet /> continues to render the selected child page.

The access boundary owns authentication decisions. The shell only owns presentation.

Registered-user presentation

Registered users display:

  • display name, falling back to email when no name exists
  • email address
  • Google avatar when available
  • neutral user icon when no avatar exists

Only the browser-safe PublicUser contract is passed to the shell.

Guest presentation

Guests display:

  • Guest
  • Progress is temporary
  • neutral user icon

This makes the temporary nature of guest mode visible without adding exit behavior to this PR.

Responsive behavior

The desktop sidebar displays the complete identity summary below navigation.

The mobile header uses the same component with its custom compact presentation. compact is a React prop, not device detection: it tells the component to conserve space, while Tailwind responsive classes determine when the secondary detail is visible.

Discriminated authentication state

useCurrentUser() previously returned authenticationStatus and user as values whose relationship TypeScript could not prove. That required a redundant defensive condition before using the user.

It now returns explicit branches:

  • loading with user: null
  • error with user: null
  • anonymous with user: null
  • authenticated with user: PublicUser

After the boundary handles loading, error, and anonymous states, TypeScript knows the remaining state is authenticated and the user cannot be null. The old standalone status alias was removed because the return branches now infer the union directly.

Scope

This PR intentionally does not add:

  • registered-user logout
  • guest exit
  • account menus
  • profile editing

Those behaviors will be implemented as separate reviewable tasks.

Project convention

AGENTS.md now records the TypeScript readability convention that required properties should be listed before optional properties in object types and interfaces.

Verification

  • registered identity, email, and avatar are covered
  • guest identity and temporary-progress message are covered
  • desktop and mobile shell instances are covered
  • 19 web tests passed
  • TypeScript passed
  • ESLint passed
  • changed files passed formatting and diff checks

@rd-code-23
rd-code-23 merged commit e2daaa7 into main Aug 23, 2026
1 check passed
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