Skip to content

fix(web): lift assistant lifecycle to ChatLayout, fix blank pages#31212

Merged
ashleeradka merged 2 commits into
mainfrom
devin/1779229424-fix-blank-pages-lifecycle-architecture
May 19, 2026
Merged

fix(web): lift assistant lifecycle to ChatLayout, fix blank pages#31212
ashleeradka merged 2 commits into
mainfrom
devin/1779229424-fix-blank-pages-lifecycle-architecture

Conversation

@ashleeradka
Copy link
Copy Markdown
Contributor

@ashleeradka ashleeradka commented May 19, 2026

Prompt / plan

All pages in the Vite web app (/assistant/home, /assistant, /assistant/library, /assistant/settings) render blank or with placeholder content when running vel up --exclude macos --vite.

Root cause

The assistant lifecycle (useAssistantLifecycle) was scoped to ChatPage — one of several child routes — instead of ChatLayout, the shared layout wrapping all /assistant/* routes.

In the platform repo, AssistantPageClient acts as both layout and lifecycle owner — it resolves the real assistant UUID once and passes it to all child views. The Vite app's port placed the lifecycle inside ChatPage only, leaving other routes (home, library, settings) without access to the resolved assistant ID.

HomePageRoute hardcoded assistantId="default" as a placeholder. Django's URL pattern assistants/<uuid:assistant_id>/<path:rest>/ can't match the string "default", so all API calls returned 404 — the home page rendered an empty greeting with no feed items, no avatar, and no suggestions.

How the code got into this state

  1. #31114 decomposed AssistantShell into RootLayout + ChatLayout — but didn't move the lifecycle to the layout level.
  2. #31109 ported the chat page and placed useAssistantLifecycle inside ChatPage rather than ChatLayout.
  3. #31067 ported the home page and used assistantId="default" as a placeholder since the lifecycle wasn't available at that level.
  4. #31168 correctly removed the requiresAuth() / VITE_AUTH_REQUIRED gating (auth is always required) but left stale documentation in AGENTS.md and CONVENTIONS.md.

What changed

  1. Lift useAssistantLifecycle to ChatLayout — the layout now owns the lifecycle and passes the resolved assistantId + assistantState to child routes via React Router v7's outlet context.

  2. New useAssistantContext() hook — typed wrapper around useOutletContext() for child routes to consume the layout-provided lifecycle. Lives at src/domains/chat/assistant-context.ts.

  3. Fix HomePageRoute — consumes useAssistantContext() to get the real assistant UUID instead of hardcoding "default".

  4. Simplify ChatPage — removes the duplicate useAssistantLifecycle call. Reads assistantId and assistantState from the layout context.

  5. Wire checkAssistant into ChatRouteContent — replaces the pre-existing noopVoid placeholder with the real checkAssistant from the lifecycle context, so onMaintenanceExited actually rechecks assistant status.

  6. Fix stale docs — removes misleading VITE_AUTH_REQUIRED documentation from AGENTS.md and CONVENTIONS.md. Auth is always required; the env var gating was intentionally removed.

Prevention

Added guidance to AGENTS.md documenting the convention: assistant lifecycle is owned by ChatLayout and consumed via useAssistantContext() — never hardcode or independently resolve the assistant ID in child routes.

Alternatives considered and rejected

  • Just fix HomePageRoute to call useAssistantLifecycle directly — rejected because it duplicates the lifecycle (each route runs independent hatching/polling). Wasteful and inconsistent with the platform architecture.

  • Zustand store for assistant state — considered but rejected for this fix. The lifecycle hook has imperative side effects (hatching, polling, retry) that don't map cleanly to a store. Outlet context keeps the lifecycle as a hook in the layout, matching both the platform pattern and React Router conventions.

Test plan

  • bun run lint — passes
  • bunx tsc --noEmit — passes
  • CI checks

Link to Devin session: https://app.devin.ai/sessions/1b39bae960a146a498035acc9bb663c6
Requested by: @ashleeradka


Open in Devin Review

ChatLayout now owns useAssistantLifecycle and passes the resolved
assistantId + assistantState to child routes via outlet context.
This matches the platform's AssistantPageClient architecture where
a single lifecycle owner wraps all views.

- HomePageRoute no longer hardcodes assistantId='default' (which
  caused Django 404s since <uuid:assistant_id> can't match a string)
- ChatPage consumes the layout-provided lifecycle instead of running
  its own duplicate instance
- New useAssistantContext() hook wraps useOutletContext() with types
- Fix stale VITE_AUTH_REQUIRED docs in AGENTS.md and CONVENTIONS.md
  (auth is always required; the env var gating was intentionally
  removed)

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment thread apps/web/src/domains/chat/chat-page.tsx Outdated
});

const { assistantState, assistantId } = lifecycle;
const { assistantId, assistantState } = useAssistantContext();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 checkAssistant from context not wired to ChatRouteContent

The AssistantContextValue exposes checkAssistant (from the lifecycle hook), and ChatPage destructures the context at apps/web/src/domains/chat/chat-page.tsx:39. However, the chatRouteProps at line 216 still passes checkAssistant: noopVoid instead of the real function from context. This means onMaintenanceExited at apps/web/src/domains/chat/components/chat-route-content.tsx:1182 remains a no-op — when maintenance mode ends, the assistant status won't be automatically rechecked. This was already the case before this PR (the old code also passed noopVoid), but now that the lifecycle is properly lifted and exposed via context, it would be straightforward to wire checkAssistant from useAssistantContext() into the props.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good catch — wired checkAssistant from the context into chatRouteProps in d5028c0. onMaintenanceExited will now actually recheck assistant status.

vex-assistant-bot[bot]
vex-assistant-bot Bot previously approved these changes May 19, 2026
Copy link
Copy Markdown
Contributor

@vex-assistant-bot vex-assistant-bot Bot left a comment

Choose a reason for hiding this comment

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

APPROVE

Value: Fixes blank pages across all /assistant/* routes by correctly lifting useAssistantLifecycle from ChatPage (one leaf route) to ChatLayout (the shared parent), mirroring how AssistantPageClient owns the lifecycle in the platform repo.

What this does: Creates a typed outlet context (AssistantContextValue + useAssistantContext()) and passes resolved assistant state from ChatLayout to all child routes via <Outlet context={assistantContext} />. ChatPage and HomePageRoute now consume the shared context instead of each independently resolving (or hardcoding "default" for Home).


Per-file notes:

assistant-context.ts — Clean pattern. useOutletContext<AssistantContextValue>() is the correct RR7 data-mode idiom for typed layout-to-child state passing.

chat-layout.tsx — Lifecycle ownership is correct here. Both mobile and desktop <Outlet /> instances get the context. Two non-blocking observations:

  • lifecycle.autoGreetRef in the useMemo dep array is redundant — ref object identity is stable across renders (per useRef contract). Removing it won't change behavior, but it's a minor signal confusion.
  • isRetired: false and isNonProduction: false are hardcoded. That's fine for now, but when those conditions become real, the onRedirect: navigate path will now actually fire (previously the ChatPage-scoped lifecycle used a no-op navigate). Worth a TODO comment when that wires up.

chat-page.tsxauthLoading is kept from useAuthStore — presumably still used for its own render conditionals below the diff. Correct.

routes.tsxHomePageRoute — Real assistantId replaces "default". This is the visible fix: Home page was blank because it was rendering against a fake ID with no assistant state behind it.

CONVENTIONS.md — Removing the "Auth is optional / VITE_AUTH_REQUIRED" section is a policy change worth flagging for awareness: local dev and self-hosting no longer have an opt-out path in the docs. Intentional hardening or docs-only cleanup for now? Either way, harmless to ship.


Anti-pattern checks: ✅ Zustand selectors use createSelectors pattern (useAuthStore.use.isLoggedIn(), .use.isLoading()). ✅ No useShallow on owned stores. ✅ Imports from react-router. ✅ No barrel files. ✅ No clientLoader/clientAction. Clean across the board.

Vellum Constitution — Trust-seeking: lifecycle state resolves once at the layout boundary and flows transparently to all child routes, eliminating the silent blank-page failure mode users were hitting.

Replace noopVoid with the real checkAssistant from useAssistantContext()
so onMaintenanceExited actually rechecks assistant status.

Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
@ashleeradka ashleeradka merged commit f90d4ad into main May 19, 2026
3 checks passed
@ashleeradka ashleeradka deleted the devin/1779229424-fix-blank-pages-lifecycle-architecture branch May 19, 2026 22:44
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