Skip to content

OUT-3979 | API: actionable installs discovery endpoint - #221

Merged
arpandhakal merged 1 commit into
arpandhakal/out-3977-platform-layer-extend-assemblyclient-for-installsfrom
arpandhakal/out-3979-api-actionable-installs-discovery-endpoint
Jul 22, 2026
Merged

OUT-3979 | API: actionable installs discovery endpoint#221
arpandhakal merged 1 commit into
arpandhakal/out-3977-platform-layer-extend-assemblyclient-for-installsfrom
arpandhakal/out-3979-api-actionable-installs-discovery-endpoint

Conversation

@arpandhakal

Copy link
Copy Markdown
Collaborator

Stacked on OUT-3977 (#218). Base is the OUT-3977 branch so the diff shows only OUT-3979 changes. Retarget to main once #218 merges.

Changes

New installed-apps feature module that discovers Studio apps eligible for "Your Actions". Client Home owns no app list — this reads from the platform.

  • Feature module (src/features/installed-apps/) following the CLEAN pattern: InstalledAppsService (static new(user)), controller, and route GET /api/installed-apps wrapped in withErrorHandler.
  • Discovery is a two-step fetch (the list endpoint doesn't inline the action label): assembly.getInstalls(), filter out disabled / isDraft / isInternalApp (and any install missing id/appId), then fan out assembly.getInstallNotificationSettings(installId) in parallel via Promise.all, keeping only installs whose actionLabel passes isActionLabelRegistered (all three parts present and non-empty).
  • Resilient fan-out: a failing notification-settings fetch for one install is logged and that install is skipped — it never 500s the endpoint.
  • Response shape per app: { installId, appId, displayName, icon, actionLabel: { verb, singularNoun, pluralNoun } }, validated by ActionableInstallsDtoSchema.
  • Hook useInstalledApps — React Query, key [INSTALLED_APPS_QUERY_KEY, workspaceId], Zod-validates the response, gated on workspaceId.
  • Registered the route in authorizedRoutes for both internal users and clients; added the @installed-apps/* path alias.

Testing Criteria

  • pnpm typecheck passes
  • pnpm lint passes
  • Existing unit tests pass (23/23)
  • Staging (pending): endpoint returns only registered + active installs; a registration added to an already-installed app appears without reinstall; uninstalled apps disappear; a deliberately failing sub-resource fetch doesn't 500 the endpoint. (Loom to follow.)

Notes

  • Depends on OUT-3977 (getInstalls, getInstallNotificationSettings, isActionLabelRegistered, RegisteredActionLabelSchema).
  • No consumer yet — the client rows (OUT-3981) and editor toggles (OUT-3982) will call useInstalledApps. Freshness (later registration / uninstall) is governed purely by React Query staleTime; no extra caching.
  • Uses 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

  • Purely additive: a new endpoint, module, hook, route registration, and tsconfig alias. No existing code paths change.

🤖 Generated with Claude Code

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>
@linear-code

linear-code Bot commented Jul 21, 2026

Copy link
Copy Markdown

OUT-3979

@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
client-home-v3 Ready Ready Preview, Comment Jul 21, 2026 9:32am

Request Review

@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds an installed-apps discovery path for actionable Studio installs. The main changes are:

  • New /api/installed-apps route, controller, and service.
  • Platform install discovery with notification-settings fan-out.
  • DTO validation for actionable install responses.
  • React Query hook for client-side consumers.
  • Authorized route entries and an @installed-apps/* alias.

Confidence Score: 5/5

This looks safe to merge after a small diagnostics cleanup.

  • The new route, imports, alias, auth flow, and DTO shapes match existing patterns.
  • The only issue found is that broad per-install error swallowing can make a platform outage look like an empty result.

src/features/installed-apps/lib/installed-apps.service.ts

Important Files Changed

Filename Overview
src/app/api/installed-apps/route.ts Adds the GET route wrapper for installed-app discovery.
src/app/routes.ts Registers the installed-apps API route for internal and client users.
src/features/installed-apps/hooks/useInstalledApps.ts Adds a workspace-gated React Query hook that validates the response DTO.
src/features/installed-apps/installed-apps.dto.ts Defines Zod schemas for actionable install response objects.
src/features/installed-apps/lib/installed-apps.controller.ts Adds the authenticated controller for the new endpoint.
src/features/installed-apps/lib/installed-apps.service.ts Implements install filtering and notification-settings fan-out, with one diagnostics issue around broad error swallowing.
tsconfig.json Adds the installed-apps path alias.

Reviews (1): Last reviewed commit: "feat(OUT-3979): add actionable installs ..." | Re-trigger Greptile

Comment on lines +47 to +54
} 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
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 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.

@priosshrsth priosshrsth left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm

export const getActionableInstalls = async (req: NextRequest): Promise<NextResponse<APIResponse>> => {
const user = authenticateHeaders(req.headers)

const installedAppsService = InstalledAppsService.new(user)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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.

@arpandhakal
arpandhakal merged commit bb90585 into arpandhakal/out-3977-platform-layer-extend-assemblyclient-for-installs Jul 22, 2026
8 checks 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.

2 participants