OUT-3979 | API: actionable installs discovery endpoint - #221
Conversation
New installed-apps feature module (CLEAN: service + controller + route) that returns Studio app installs eligible for "Your Actions". - Service filters out disabled/draft/internal installs, then fans out getInstallNotificationSettings per install in parallel, keeping only those with a complete registered actionLabel - A single notification-settings failure skips that install, not the request - GET /api/installed-apps (withErrorHandler), authorized for IU and client - useInstalledApps React Query hook ([KEY, workspaceId], Zod-validated) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds an installed-apps discovery path for actionable Studio installs. The main changes are:
Confidence Score: 5/5This looks safe to merge after a small diagnostics cleanup.
src/features/installed-apps/lib/installed-apps.service.ts Important Files Changed
Reviews (1): Last reviewed commit: "feat(OUT-3979): add actionable installs ..." | Re-trigger Greptile |
| } catch (error) { | ||
| // One failing sub-resource fetch must not fail the whole endpoint — skip this install. | ||
| logger.error( | ||
| `InstalledAppsService#getActionableInstalls | notification-settings fetch failed for install ${install.id}`, | ||
| error, | ||
| ) | ||
| return null | ||
| } |
There was a problem hiding this comment.
Systemic Settings Failures Become Empty Lists
When the notification-settings endpoint fails for every active install, this catch path drops all installs and the API returns an empty array. A workspace with eligible apps can then look identical to a workspace with no actionable apps, which hides auth, permission, or platform outages from callers.
| export const getActionableInstalls = async (req: NextRequest): Promise<NextResponse<APIResponse>> => { | ||
| const user = authenticateHeaders(req.headers) | ||
|
|
||
| const installedAppsService = InstalledAppsService.new(user) |
There was a problem hiding this comment.
I wonder why we think this is better than calling new InstalledAppService. I find myself doing similiar thing. But most of the time makes no sense unless it has to be async.
bb90585
into
arpandhakal/out-3977-platform-layer-extend-assemblyclient-for-installs
Changes
New
installed-appsfeature module that discovers Studio apps eligible for "Your Actions". Client Home owns no app list — this reads from the platform.src/features/installed-apps/) following the CLEAN pattern:InstalledAppsService(static new(user)), controller, and routeGET /api/installed-appswrapped inwithErrorHandler.assembly.getInstalls(), filter outdisabled/isDraft/isInternalApp(and any install missingid/appId), then fan outassembly.getInstallNotificationSettings(installId)in parallel viaPromise.all, keeping only installs whoseactionLabelpassesisActionLabelRegistered(all three parts present and non-empty).{ installId, appId, displayName, icon, actionLabel: { verb, singularNoun, pluralNoun } }, validated byActionableInstallsDtoSchema.useInstalledApps— React Query, key[INSTALLED_APPS_QUERY_KEY, workspaceId], Zod-validates the response, gated onworkspaceId.authorizedRoutesfor both internal users and clients; added the@installed-apps/*path alias.Testing Criteria
pnpm typecheckpassespnpm lintpassesNotes
getInstalls,getInstallNotificationSettings,isActionLabelRegistered,RegisteredActionLabelSchema).useInstalledApps. Freshness (later registration / uninstall) is governed purely by React Query staleTime; no extra caching.Promise.all(not bounded concurrency); fine for the expected install counts. If a workspace ever has a very large install list, this is the place to add a concurrency cap.Impact & Surface Area of Change
🤖 Generated with Claude Code