feat(web): replace library page stubs with real implementations#31277
Conversation
Wire the already-ported LibraryView and AppViewerContainer components into the library route pages, replacing the placeholder stubs. - LibraryPage renders LibraryView with assistantId from outlet context, matching the platform's rendering wrapper with the same container styling (rounded border, surface overlay background). - LibraryDetailPage is a deep-link page for /assistant/library/:appId that fetches the app via openApp() and renders AppViewerContainer directly, with loading/error states and back-navigation to the library listing. Routes in routes.tsx were already correctly wired from the initial scaffolding — no changes needed there. Closes LUM-1657 Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| <div className="flex flex-1 flex-col items-center justify-center gap-4"> | ||
| <p className="text-body-medium-lighter text-[var(--content-tertiary)]"> | ||
| {error} | ||
| </p> | ||
| <button | ||
| type="button" | ||
| onClick={handleClose} | ||
| className="text-body-medium-default text-[var(--primary-base)] underline" | ||
| > |
There was a problem hiding this comment.
🔴 Missing catch in handleShare causes unhandled promise rejection and silent failure
The handleShare callback has a try/finally but no catch block. When shareApp throws (e.g. network error, server error), the error propagates as an unhandled promise rejection — the user gets no feedback that the share failed, and an error appears in the console.
Comparison with existing pattern in LibraryView
The analogous handler in library-view.tsx:338-351 correctly catches errors and shows a toast:
catch (err) {
toast.error("Failed to share app", {
description: err instanceof Error ? err.message : undefined,
});
}It also shows a success toast on the happy path. The LibraryDetailPage version is missing both.
| <div className="flex flex-1 flex-col items-center justify-center gap-4"> | |
| <p className="text-body-medium-lighter text-[var(--content-tertiary)]"> | |
| {error} | |
| </p> | |
| <button | |
| type="button" | |
| onClick={handleClose} | |
| className="text-body-medium-default text-[var(--primary-base)] underline" | |
| > | |
| const handleShare = useCallback(async () => { | |
| if (!assistantId || !app || isSharing) return; | |
| setIsSharing(true); | |
| try { | |
| await shareApp(assistantId, app.appId, app.name); | |
| } catch (err) { | |
| console.error("Failed to share app", err); | |
| } finally { | |
| setIsSharing(false); | |
| } | |
| }, [assistantId, app, isSharing]); |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed in 552e005 — added catch (err) with toast.error("Failed to share app", ...) and toast.success("App exported", ...), matching LibraryView's handleShareOpenedApp pattern exactly. Also added unmount cleanup (requestRef.current = null) per the vex-bot note.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cb90b5f73e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| initialMessage | ||
| ? `${routes.assistant}?message=${encodeURIComponent(initialMessage)}` | ||
| : routes.assistant, |
There was a problem hiding this comment.
Route initial prompt via a consumed launch mechanism
handleNewConversation encodes initialMessage into ?message=..., but the chat route never reads that query parameter, so flows that pass an initial prompt (e.g. the "Let your assistant handle it" deploy confirmation in LibraryView) open chat without the intended seeded message. This makes the new cross-route prompt handoff non-functional; use the existing initial-message launch path (or another consumed mechanism) instead of an unused query key.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch — confirmed the chat route only reads conversationKey and onboarding from search params, never message. Fixed in 552e005 by removing the dead ?message= param. The callback now always navigates to /assistant.
In the platform, onNewConversation calls startNewConversation({ initialMessage }) directly within the same React tree (no navigation). Since the OSS library is a separate route, that function isn't accessible. Left a TODO — the initialMessage seeding requires cross-route state coordination (same category as onEditApp).
| try { | ||
| await shareApp(assistantId, app.appId, app.name); | ||
| } finally { |
There was a problem hiding this comment.
Handle share failures to avoid unhandled promise rejections
The share callback awaits shareApp(...) without a catch, so any network/API error rejects out of the click handler. In this case users get no explicit error toast and the app can emit unhandled promise rejection noise in production logs; this is a regression from the existing LibraryView share flow, which catches and reports failures.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 552e005 — added catch + toast.error/toast.success to match LibraryView's share pattern. The try/finally without catch was a real bug (unhandled rejection + silent failure).
There was a problem hiding this comment.
✦ APPROVE
Value: Library users navigating to /assistant/library now see their actual apps instead of "Placeholder route" text — LibraryView and AppViewerContainer are wired with context, loading states, and error handling.
What this does:
library-page.tsx— mountsLibraryViewwithassistantIdfrom outlet context +onNewConversationcallback navigating to chat, wrapped in the platform container stylinglibrary-detail-page.tsx— loads the app viaopenApp()on mount, guards stale concurrent navigation viarequestRef, shows spinner →AppViewerContainer→ error state with back link
Dependency check: #31271 (LUM-1654) merged at 13:21 UTC today — imports resolve correctly ✅
CI: 3/3 ✅ (Lint+Type+Build, Socket PR, Socket Project)
Convention checks: .js extensions ✅ · @/ alias ✅ · kebab-case ✅ · no default exports ✅ · no barrel files ✅ · ApiError thrown correctly in openApp/shareApp ✅ · void navigate() suppresses Promise warning correctly ✅
Capacitor iOS: openApp/shareApp use client.post for JSON responses — no SSE concern. shareApp step 2 uses parseAs: "stream" for a binary bundle download; CapacitorHttp buffers this correctly (not a streaming text/event-stream path). ✅
Non-blocking notes:
-
useEffectmissing unmount cleanup —requestRef.currentis never cleared in the effect's cleanup return. If the component unmounts whileopenAppis in-flight, the ref still matchesappIdand.then/.catchwill callsetApp/setErroron the dead instance. React 18 makes this a no-op (no warning, no Sentry error), but it diverges from our KB canonical pattern. Suggested minimal fix:return () => { requestRef.current = null; };
-
handleShareswallows errors —try { await shareApp(...) } finally { setIsSharing(false) }resets the loading state but gives the user no feedback on failure. IfshareAppthrows,isSharingsnaps back to false silently. Acceptable ifshareApphandles its own toast internally — worth confirming. -
isSharinginhandleSharedep array — the guardif (isSharing) returnreadsisSharingfrom closure, requiring it in deps, which re-createshandleShareon every state toggle and re-rendersAppViewerContainerduring the share operation. AuseRefguard avoids this churn, but the current approach is functionally correct.
Vellum Constitution — Inviting: replacing a dead-end placeholder with a working library page makes the assistant feel coherent and complete.
…cleanup - library-detail-page: add catch block + toast for shareApp failures, add toast.success on share, add useEffect unmount cleanup for requestRef - library-page: remove non-functional ?message= query param (chat route never reads it); navigate to chat without seeding initial message until cross-route state coordination is implemented Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
552e005
|
All three non-blocking notes addressed in 552e005:
|
Prompt / plan
Replace the placeholder library pages with real implementations that wire the already-ported
LibraryViewandAppViewerContainercomponents (from PR #31271, LUM-1654).Why needed: The library routes (
/assistant/library,/assistant/library/:appId) currently render placeholder stubs with "Placeholder route" text. The actual components they need were ported in LUM-1654 but never wired into the route pages.What changed:
library-page.tsx— RendersLibraryViewwithassistantIdfrom outlet context, wrapped in the same container styling the platform uses (rounded border, surface overlay background). ProvidesonNewConversationcallback that navigates to the chat route.library-detail-page.tsx— Deep-link page for/assistant/library/:appId. Fetches the app viaopenApp()on mount, rendersAppViewerContaineronce loaded, with loading spinner and error states. Close navigates back to the library listing. Uses a request ref to handle concurrent/stale responses (same pattern asuse-app-viewer-actions.ts).Why safe: Additive change replacing inert placeholders. No route changes needed —
routes.tsxalready had both routes correctly wired. All optional callbacks (onOpenDocument,onEditApp) are omitted for now since they require cross-route state coordination that will come with further migration work.LibraryViewhandles these gracefully (conditionally hides buttons when callbacks aren't provided).Alternatives not taken:
LibraryViewon the detail page with a pre-opened app: Rejected because the platform's/library/:appIdroute bypasses the listing entirely and goes straight to the app viewer via thedeepLinkAppIdflow. A dedicated detail page that loads the app directly is the faithful port of this behavior.onEditApp(which enters chat editing mode): Requires the chat page's editing state machine and conversation context. This is cross-route coordination that belongs in a future task, not this stub-replacement PR.Closes LUM-1657
Test plan
bunx tsc --noEmit— passes (no type errors)bun run lint— passesbun run build— passes (production bundle succeeds)routes.tsx— already correctly wired at/assistant/libraryand/assistant/library/:appIdLink to Devin session: https://app.devin.ai/sessions/c4696ace57fe43649f2475d7661ac273
Requested by: @ashleeradka