Skip to content

Feature/studio apps action - #220

Merged
arpandhakal merged 20 commits into
mainfrom
feature/studio-apps-action
Jul 29, 2026
Merged

arpandhakal merged 20 commits into
mainfrom
feature/studio-apps-action

Conversation

@arpandhakal

Copy link
Copy Markdown
Collaborator

Changes

  • All changes regarding "Studio Apps in Client Home" feature go here.

arpandhakal and others added 6 commits July 21, 2026 14:30
…ppId

Extend the platform layer so Client Home can discover Studio apps' action
labels and bucket notifications by app.

- AppInstallsDataSchema: add icon, disabled, isDraft, isInternalApp
- Add ActionLabelSchema + InstallNotificationSettingsSchema for
  GET /v1/installs/{id}/notification-settings, plus isActionLabelRegistered
- NotificationsResponseSchema: add appId (nullish); relax event to nullish
- AssemblyClient.getInstalls() and getInstallNotificationSettings(installId),
  the latter via a _manualFetch helper since the SDK exposes no method for
  the notification-settings sub-resource

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

Check response.ok before parsing the body in _manualFetch. A transient
429/500 with a non-JSON body previously threw a statusless SyntaxError,
which withRetry could not recognize as retryable. Now read the error body
as text and throw APIError with the real status.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Client Home stores only the internal user's per-app hide state as a
deny-list keyed by appId (absence = shown), so default-on needs no setup,
uninstall needs no cleanup, and reinstall restores the prior choice.

- Add hiddenAppIds jsonb column (string[], default []) to actions + migration
- Thread through drizzle-zod schemas (ActionsSchema/Create/Update -> DTOs);
  repository/service pick it up via payload spread
- Account for hiddenAppIds in the useAppControls dirty check (array compare,
  like order)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…var for API URL

- Replace the manual isActionLabelRegistered checks with a strict
  RegisteredActionLabelSchema (trim + non-empty), per PR review
- Use env.ASSEMBLY_API_URL (default prod) as the base URL in _manualFetch
  instead of the SDK's mutable OpenAPI.BASE global; drop the OpenAPI import

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…m-layer-extend-assemblyclient-for-installs

OUT-3977 | Platform layer: extend AssemblyClient for installs notificationConfig + notification appId
…ence-add-hiddenappids-to-actions-table-per-app-hide

OUT-3978 | Persistence: add hiddenAppIds to actions table (per-app hide state)
@arpandhakal arpandhakal self-assigned this Jul 21, 2026
@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 29, 2026 8:52am

Request Review

@greptile-apps

greptile-apps Bot commented Jul 21, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR adds Studio app actions to the client home action list. The main changes are:

  • New installed-apps API route and service for actionable Studio app installs.
  • Dynamic action rendering, ordering, hiding, and notification counts.
  • Database and settings schema support for hidden Studio app IDs.
  • Direct Assembly API calls for install notification settings.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • The hidden app deny-list comparison now matches its unordered behavior.
  • The Assembly API base URL path follows the clarified environment convention.

Important Files Changed

Filename Overview
src/features/editor/hooks/useAppControls.tsx Compares hidden Studio app IDs by membership so reordered deny-list values do not keep the editor dirty.
src/utils/array.ts Adds a helper for unordered array membership equality and bounded async mapping.
src/features/editor/components/Sidebar/Actions/useActions.tsx Adds Studio app rows to the action settings list with hide/show behavior backed by hidden app IDs.
src/features/settings/hooks/useEnabledActions.ts Combines enabled built-in actions with visible Studio app actions using the shared order array.
src/lib/assembly/assembly-client.ts Adds direct Assembly API fetch support for install notification settings.

Reviews (3): Last reviewed commit: "Merge pull request #226 from assemblycom..." | Re-trigger Greptile

Comment thread src/lib/assembly/assembly-client.ts
Comment on lines +38 to +39
const hiddenAppIdsChanged =
JSON.stringify(actions.hiddenAppIds) !== JSON.stringify(initialSettings.actions?.hiddenAppIds)

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 Unordered Deny List Stays Dirty

hiddenAppIds is a deny-list keyed by app ID, so its order has no meaning. If the UI or server returns the same IDs in a different order, this string comparison marks the editor dirty even though the hidden apps did not change, leaving the Save CTA visible on a clean form.

arpandhakal and others added 10 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>
Extend NotificationCountsDto with an `apps` map (appId -> unread count) so
dynamic action rows stay in sync with built-in rows via one fetch/hook/cache
entry. The counts service buckets the client's unread notifications by appId
while preserving the existing recipientCompanyId filter; built-in event-based
counts are unchanged.

Narrow the actions-card count lookup to the numeric NotificationCountKey type
now that the DTO also carries the nested apps map.

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>
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
…ui-dynamic-studio-app-rows-in-your-actions

OUT-3981 | Client UI: dynamic Studio app rows in Your Actions
…-app-unread-notification-counts

OUT-3980 | API: per-app unread notification counts
arpandhakal and others added 2 commits July 28, 2026 17:10
App builders register `actionLabel.verb` with no casing rule enforced, so it
arrives in whatever shape the app author typed ("review", "reView", "REVIEW").
Normalize it to sentence case in `toDynamicActionDefinition`, the single funnel
for Studio app action labels into the render path.

Nouns pass through verbatim since they render mid-phrase. Pre-existing built-in
actions keep their hardcoded verbs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ize-actionlabel-verb-usage

OUT-4014 | Capitalize actionLabel verb usage
@arpandhakal

Copy link
Copy Markdown
Collaborator Author

@greptile review again please.

Comment thread src/features/editor/hooks/useAppControls.tsx Outdated
arpandhakal and others added 2 commits July 29, 2026 14:34
`hiddenAppIds` is an unordered deny-list, but the toggle path rebuilds it by
filter-then-append, so a hide/unhide/hide cycle returns the same members in a
different position. The serialized comparison read that as a change and left
Save/Cancel visible on a clean form.

Adds `areArraysEqualAsSets` and uses it for `hiddenAppIds` only — `order` is a
genuine sequence and keeps its serialized comparison.

Reported by Greptile on #220.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ds-set-comparison

fix: compare hiddenAppIds as a set for editor dirty state
@arpandhakal

Copy link
Copy Markdown
Collaborator Author

please re review @greptile

@arpandhakal
arpandhakal merged commit 646905f into main Jul 29, 2026
8 checks passed

This branch was successfully deployed

1 active deployment
Preview — 7e749004 Deployed Jul 29, 2026 by vercel[bot]
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