Skip to content

OUT-3981 | Client UI: dynamic Studio app rows in Your Actions - #223

Merged
arpandhakal merged 7 commits into
arpandhakal/out-3980-api-per-app-unread-notification-countsfrom
arpandhakal/out-3981-client-ui-dynamic-studio-app-rows-in-your-actions
Jul 27, 2026
Merged

OUT-3981 | Client UI: dynamic Studio app rows in Your Actions#223
arpandhakal merged 7 commits into
arpandhakal/out-3980-api-per-app-unread-notification-countsfrom
arpandhakal/out-3981-client-ui-dynamic-studio-app-rows-in-your-actions

Conversation

@arpandhakal

Copy link
Copy Markdown
Collaborator

Changes

Renders a "Your Actions" row for each registered Studio app the signed-in client has unread notifications from — as [Verb] [count] [noun] (e.g. "Review 3 documents"), visually indistinguishable from built-in rows.

  • installed-apps/lib/dynamic-action.tsDynamicActionDefinition + toDynamicActionDefinition() synthesizes a row from an ActionableInstallDto (OUT-3979): verb ← actionLabel.verb, label ← pluralNoun, singularLabel ← singularNoun, icon mapped onto the design-system Icon (fallback CustomApps), plus installId/appId. Also exports the RenderableAction union and isDynamicAction() guard.
  • installed-apps/lib/icon-names.ts — runtime allow-list of valid design-system IconType names + isIconType(). The package does not expose its icon registry at runtime (its exports map blocks the subpath), so we snapshot the names to validate arbitrary install icon strings before rendering.
  • useEnabledActions — merges dynamic definitions after the built-ins (no drag-order participation in v1), filtered by actions.hiddenAppIds (OUT-3978, deny-list by appId).
  • ActionItem / ActionsCard — render the RenderableAction union: dynamic count from the per-app apps map (OUT-3980); navigation via postMessage({ type: 'history.push', id: installId, route: 'apps' }); dynamic rows never render a zero/empty state in any mode.
  • Widened ActionDefinition.icon to IconType so built-in and dynamic rows share the field. Built-in rows unchanged in behavior and appearance.

Testing Criteria

  • As a client with unread notifications from an installed Studio app that has a registered action label, confirm a row appears reading [verb] [count] [noun], styled identically to built-in rows.
  • Count of 1 uses the singular noun; 2+ uses the plural.
  • Row is hidden entirely when the app's unread count is 0 (never a "0 items" row).
  • Clicking the row opens the app in the parent portal (history.pushapps with the install id).
  • An app whose appId is in actions.hiddenAppIds does not render.
  • An install with an unrecognized/absent icon falls back to the CustomApps glyph.
  • Built-in rows (invoices/contracts/tasks/forms) unchanged in appearance and behavior.
  • pnpm typecheck and pnpm lint pass.
  • Loom video: TODO

Notes

  • Stacked on OUT-3980 (per-app counts, base branch) with OUT-3979 (actionable-installs discovery endpoint) merged in. Also depends on OUT-3978 (hiddenAppIds, already in feature/studio-apps-action). Merge order: 3979 → 3980 → 3981 into feature/studio-apps-action.
  • icon-names.ts is a snapshot of the design-system icon set; regenerate if the design system adds icons. Staleness is safe — a newly-added icon simply falls back to CustomApps.

Impact & Surface Area of Change

  • useEnabledActions now depends on useInstalledApps (a workspace-scoped React Query) and returns a RenderableAction[] union instead of ActionDefinition[]. Only consumer is ActionsCard; the editor sidebar uses its own useActions and is unaffected.
  • ActionDefinition.icon widened from the 4-value ActionItemIcon to IconType — verify built-in action icons still render.
  • Worth a regression check on the "Your Actions" card in all three surfaces (editor, preview, client/readonly).

🤖 Generated with Claude Code

arpandhakal and others added 3 commits July 21, 2026 15:17
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>
…endpoint' into arpandhakal/out-3981-client-ui-dynamic-studio-app-rows-in-your-actions
Synthesize ActionDefinition-shaped rows at runtime from the actionable-installs
discovery endpoint (OUT-3979) and merge them into useEnabledActions after the
built-ins (no drag-order participation in v1), filtered by actions.hiddenAppIds
(OUT-3978, deny-list by appId).

- New DynamicActionDefinition + toDynamicActionDefinition: verb <- actionLabel.verb,
  label <- pluralNoun, singularLabel <- singularNoun, icon mapped onto the design
  system Icon (fallback CustomApps when the install icon isn't a valid IconType)
- icon-names: runtime allow-list of design-system IconType names, since the package
  does not expose its icon registry at runtime
- ActionItem/ActionsCard render the RenderableAction union: dynamic count from the
  per-app map (OUT-3980), navigation via postMessage history.push { id: installId,
  route: 'apps' }; dynamic rows never render a zero/empty state in any mode
- Widen ActionDefinition.icon to IconType so built-in and dynamic rows share the field

Built-in rows unchanged in behavior and appearance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@linear-code

linear-code Bot commented Jul 22, 2026

Copy link
Copy Markdown

OUT-3981

@vercel

vercel Bot commented Jul 22, 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 27, 2026 11:10am

Request Review

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

Adds workspace-scoped discovery and rendering of actionable Studio app installs.

  • Introduces an authenticated installed-apps API and service that retrieves active installs with registered action labels.
  • Converts eligible installs into dynamic action definitions with validated design-system icons.
  • Merges dynamic rows with built-in actions, applies the hidden-app deny-list, and resolves unread counts by app ID.
  • Navigates dynamic rows to their corresponding install in the parent portal.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete changed-code-triggered failures established.

The new endpoint is authenticated, dynamic install metadata is validated before rendering, empty app actions are suppressed, and navigation retains the install-specific identifier.

Important Files Changed

Filename Overview
src/features/installed-apps/lib/installed-apps.service.ts Retrieves active installs, fetches each notification action label, and isolates individual settings-fetch failures.
src/features/settings/hooks/useEnabledActions.ts Appends dynamic install actions after enabled built-ins and filters them using hidden app IDs.
src/features/action-items/components/actions-card.tsx Resolves per-app counts, suppresses empty dynamic rows, and renders built-in and dynamic actions together.
src/features/action-items/components/action-item.tsx Renders dynamic labels and routes dynamic action clicks to the associated app install.
src/features/installed-apps/hooks/useInstalledApps.ts Adds a workspace-keyed query for validated actionable-install data.
src/features/installed-apps/lib/dynamic-action.ts Defines the renderable action union and maps install metadata into presentation fields.
src/app/routes.ts Registers the installed-apps endpoint for authenticated internal and client users.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[ActionsCard] --> B[useEnabledActions]
  B --> C[useInstalledApps]
  C --> D[GET /api/installed-apps]
  D --> E[InstalledAppsService]
  E --> F[Assembly installs and notification settings]
  B --> G[Dynamic action definitions]
  H[Notification counts by appId] --> A
  G --> A
  A --> I[Visible action rows]
  I --> J[Parent portal history.push by installId]
Loading

Reviews (1): Last reviewed commit: "feat(OUT-3981): render dynamic Studio ap..." | Re-trigger Greptile

@arpandhakal
arpandhakal requested a review from priosshrsth July 22, 2026 08:41
@arpandhakal arpandhakal self-assigned this Jul 22, 2026
arpandhakal and others added 4 commits July 24, 2026 14:01
Internal users control each Studio app's presence in Your Actions from
the existing Actions sidebar panel. Apps appear automatically once
installed and registered, defaulted to on.

- useActions merges dynamic apps (keyed by appId) after built-ins;
  toggle checked = !hiddenAppIds.includes(appId); flipping mutates
  actions.hiddenAppIds, saved via the existing Save Changes flow.
- AppToggleItem renders a non-draggable toggle row (Studio-app rows are
  excluded from dnd reorder in v1).
- Editor preview now shows every enabled dynamic row regardless of
  count, matching built-in behavior, with the {{N}} placeholder chip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Built-ins and dynamic Studio-app rows now share one drag-orderable list
  in the Actions sidebar; `order` holds a mix of ActionKeys and appIds, and
  useEnabledActions renders in that unified order (drops the v1 "apps last,
  non-draggable" restriction). Removes AppToggleItem; ActionItem icon widened
  to IconType.
- Exclude the Tasks app (appId === TASKS_APP_ID) from actionable-installs
  discovery so it doesn't duplicate the built-in Tasks row.
- Fan out notification-settings via mapWithConcurrency (limit 5) instead of
  Promise.all, avoiding a TLS-connection burst that tripped undici connect
  timeouts in dev.
- Log both the raw platform installs and the final actionable list to aid
  debugging why an app does/doesn't surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the temporary logger.info calls for the raw platform installs and the
final actionable list; keep the error log for failed notification-settings
fetches.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…studio-app-toggles-in-actions-sidebar-preview-rows

OUT-3982 | Editor: Studio app toggles in Actions sidebar + preview rows
@arpandhakal
arpandhakal merged commit 461a46c into arpandhakal/out-3980-api-per-app-unread-notification-counts Jul 27, 2026
7 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.

1 participant