Skip to content

feat(server): workspace opt-out of root-domain directory listing#22423

Open
FelixMalfait wants to merge 8 commits into
mainfrom
feat/workspace-directory-listing-optout
Open

feat(server): workspace opt-out of root-domain directory listing#22423
FelixMalfait wants to merge 8 commits into
mainfrom
feat/workspace-directory-listing-optout

Conversation

@FelixMalfait

@FelixMalfait FelixMalfait commented Jul 1, 2026

Copy link
Copy Markdown
Member

What

Lets a workspace opt out of being surfaced in the multi-workspace root-domain (app.twenty.com) picker via email-domain discovery.

Adds isDirectoryListingEnabled (default true) on the workspace. When false, the workspace is filtered out of the approved-access-domain branch of findAvailableWorkspacesByEmail, so a user whose email domain matches an approved access domain no longer sees the workspace in the sign-up picker.

Scope of the opt-out (deliberately narrow)

The filter is applied only to the approved-access-domain discovery source:

  • Members (availableWorkspacesForSignIn) — never filtered; they keep access.
  • Explicit invitations — never filtered; the intent is one-to-one.
  • Approved-access-domain discovery — the only "listing" source, gated by the flag.

A hidden workspace stays fully reachable by members and invited users via the direct workspace subdomain; it just isn't advertised in the global picker.

Open question for review: do we also want a stronger mode that hides the workspace from the root-domain picker even for existing members (forcing them to use the subdomain directly)? That would additionally filter the member/invitation sources and is a larger behavior change — not included here.

Changes

Backend

  • workspace.entity.ts — new isDirectoryListingEnabled column (@Field, default true).
  • user-workspace.service.ts — filter the approved-access-domain branch on the flag.
  • update-workspace-input.ts — expose the field on updateWorkspace.
  • workspace.service.tsPermissionFlagType.SECURITY (same as the other discovery/security toggles).
  • Fast instance command adding the column (default true, so no existing workspace is hidden).

Frontend

  • Settings > Security: a "List in workspace directory" toggle (shown only in multi-workspace mode) that flips the flag via updateWorkspace, mirroring the existing isInternalMessagesImportEnabled toggle.
  • Threaded the field through the current-user fragment, CurrentWorkspace type, and mock data.
  • Regenerated the metadata + client-sdk GraphQL types (generated-metadata, twenty-client-sdk/.../generated) — generated against a server booted from this branch.

Verification

  • tsgo typecheck: 0 errors. oxlint: 0/0. oxfmt: clean.
  • Codegen diff verified to contain only the new field (no unrelated drift).
  • Tests not run locally; CI covers unit/integration + the codegen/migration freshness checks.

Adds an isDirectoryListingEnabled flag (default true) on the workspace.
When disabled, the workspace is excluded from approved-access-domain
discovery in findAvailableWorkspacesByEmail, so it no longer appears in
the multi-workspace root-domain picker for users who match its email
domain. Existing members and explicitly invited users are unaffected.

The flag is a SECURITY-permissioned field on updateWorkspace and is
added via a fast instance command.
@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Jul 1, 2026

Copy link
Copy Markdown

🚀 Preview Environment Ready!

Your preview environment is available at: https://identify-supervisors-disturbed-scope.trycloudflare.com

This environment will automatically shut down after 5 hours.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No issues found across 6 files

Re-trigger cubic

@Column({ default: true })
isPublicInviteLinkEnabled: boolean;

// When false, the workspace is hidden from approved-access-domain discovery

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Remove comment

@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Jul 1, 2026

Copy link
Copy Markdown

🔍 Automated Pre-Review

No issues detected - This PR is ready for human review.


View details

Automated pre-review — human approval still required.

…ypes

Adds a 'List in workspace directory' toggle under Settings > Security
(shown only in multi-workspace mode) that flips isDirectoryListingEnabled
via updateWorkspace, and threads the field through the current-user
fragment, CurrentWorkspace type and mocks. Regenerates the metadata and
client-sdk GraphQL types for the new field.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 10 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/twenty-front/src/modules/settings/security/components/SettingsSecuritySettings.tsx">

<violation number="1" location="packages/twenty-front/src/modules/settings/security/components/SettingsSecuritySettings.tsx:157">
P2: If the mutation fails (network error, validation, etc.), the toggle will stay in the changed position despite the server rejecting it — the local state is optimistically updated but never rolled back on error. This is the same pattern as handleSyncInternalEmailsChange in this file, so it's consistent, but adding a rollback in the .catch() would make the UI more reliable and avoid confusing the user.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

The new isDirectoryListingEnabled column on WorkspaceEntity is a cached
field, so it is required on FlatWorkspace. Add the missing mapping to fix
the tsgo typecheck error.

Claude-Session: https://claude.ai/code/session_01GwCx8XsGh7g4yx6C9ctbYx
@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Jul 2, 2026

Copy link
Copy Markdown

🔍 Visual Regression Review — twenty-front

✅ 3 visual change(s) reviewed — all explained by this PR.

Changed: 4 · Added: 0 · Removed: 0 · Unchanged: 698

3 item(s) to double-check (uncertain / low confidence)
Story Verdict Confidence Explained by
🟡 modules-settings-playground-graphqlplayground--default uncertain 62%
🟡 modules-settings-accounts-blocklist-settingsaccountsblocklistinput--default uncertain 65%
🟡 ui-feedback-snackbarmanager-snackbar--default uncertain 70%
Changed stories
Story Diff %
modules-settings-playground-graphqlplayground--default 3%
modules-settings-accounts-blocklist-settingsaccountsblocklistinput--default 1%
ui-feedback-snackbarmanager-snackbar--default 0%
modules-objectrecord-recordcalendar-month--default 0%

View run details · advisory mode

…tation

The Settings > Security directory-listing toggle updates the field via
updateWorkspace but the mutation did not select it back, so the Apollo
cache kept a stale value (unlike its mirror isInternalMessagesImportEnabled).
Select the field in the mutation and regenerate the metadata GraphQL types.

Claude-Session: https://claude.ai/code/session_01GwCx8XsGh7g4yx6C9ctbYx
<SettingsOptionCardContentToggle
Icon={IconList}
title={t`List in workspace directory`}
description={t`Let people whose email domain matches an approved access domain find this workspace on the sign-in screen. Members and invited users are unaffected.`}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Is this linked to approved domain?
I feel like this explanation is wrong let's be more explicit, says that's it's about app.Twenty.com (or replace with whatever url it is). Move to the authentication section. Make the test very clear by also maybe mentioning current workspace url (not sure if a good idea because it might be too long)

Address review feedback: relocate the 'List in workspace directory'
toggle from a standalone Discovery section into the Authentication
section (grouped with Invite by Link), and rewrite the description to be
explicit about the root multi-workspace sign-in domain and its link to
approved access domains. The domain is read from client config so it is
correct for self-hosted instances. Optimistically updates with rollback
on error, matching the sibling auth toggles.

Claude-Session: https://claude.ai/code/session_01GwCx8XsGh7g4yx6C9ctbYx

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Address review feedback: read the latest currentWorkspace value in both
the optimistic update and the error rollback via Jotai's functional
updater, so a concurrent workspace-state change during an in-flight
request isn't overwritten by a stale closure snapshot.

Claude-Session: https://claude.ai/code/session_01GwCx8XsGh7g4yx6C9ctbYx

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/twenty-front/src/modules/settings/security/components/SettingsSecurityAuthProvidersOptionsList.tsx">

<violation number="1" location="packages/twenty-front/src/modules/settings/security/components/SettingsSecurityAuthProvidersOptionsList.tsx:143">
P2: The optimistic rollback in `handleDirectoryListingChange` unconditionally applies `!value` for any failed request. Because `value` is captured in the closure, if a user toggles rapidly and two requests overlap, the later rollback can overwrite the correct state restored by the earlier one. The `Toggle` component has no debounce and the handler never disables it during a mutation, so this overlap is possible.

A simple guard is to only rollback when the current atom value still matches the optimistic value that was applied (`isDirectoryListingEnabled === value`). This prevents a stale-failure handler from corrupting state that has since been updated by another toggle.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

return;
}

setCurrentWorkspace((currentWorkspaceValue) =>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: The optimistic rollback in handleDirectoryListingChange unconditionally applies !value for any failed request. Because value is captured in the closure, if a user toggles rapidly and two requests overlap, the later rollback can overwrite the correct state restored by the earlier one. The Toggle component has no debounce and the handler never disables it during a mutation, so this overlap is possible.

A simple guard is to only rollback when the current atom value still matches the optimistic value that was applied (isDirectoryListingEnabled === value). This prevents a stale-failure handler from corrupting state that has since been updated by another toggle.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-front/src/modules/settings/security/components/SettingsSecurityAuthProvidersOptionsList.tsx, line 143:

<comment>The optimistic rollback in `handleDirectoryListingChange` unconditionally applies `!value` for any failed request. Because `value` is captured in the closure, if a user toggles rapidly and two requests overlap, the later rollback can overwrite the correct state restored by the earlier one. The `Toggle` component has no debounce and the handler never disables it during a mutation, so this overlap is possible.

A simple guard is to only rollback when the current atom value still matches the optimistic value that was applied (`isDirectoryListingEnabled === value`). This prevents a stale-failure handler from corrupting state that has since been updated by another toggle.</comment>

<file context>
@@ -140,10 +140,11 @@ export const SettingsSecurityAuthProvidersOptionsList = () => {
-      ...currentWorkspace,
-      isDirectoryListingEnabled: value,
-    });
+    setCurrentWorkspace((currentWorkspaceValue) =>
+      currentWorkspaceValue
+        ? { ...currentWorkspaceValue, isDirectoryListingEnabled: value }
</file context>

@FelixMalfait

Copy link
Copy Markdown
Member Author

Open question for review: do we also want a stronger mode that hides the workspace from the root-domain picker even for existing members (forcing them to use the subdomain directly)? That would additionally filter the member/invitation sources and is a larger behavior change — not included here.

--> I think we should revisit this and cover both. Not sure if it should be a dropdown, two toggles, etc? UX need to be clear for user. @claude please look at this in depth and describe all options (with text) + make recommandations

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant