Skip to content

feat(auth): collect the workspace logo on the sign-up creation step#21723

Open
FelixMalfait wants to merge 10 commits into
mainfrom
claude/workspace-creation-logo-step-1
Open

feat(auth): collect the workspace logo on the sign-up creation step#21723
FelixMalfait wants to merge 10 commits into
mainfrom
claude/workspace-creation-logo-step-1

Conversation

@FelixMalfait

@FelixMalfait FelixMalfait commented Jun 17, 2026

Copy link
Copy Markdown
Member

What & why

A single, consistent workspace-creation step for both multi-workspace and single-workspace self-host — collecting name + logo (and the subdomain in multi-workspace) — which removes the duplicate name/logo prompt that previously reappeared on the workspace subdomain (reported after #21641).

Changes

One creation form for both modes

  • With 0 workspaces, both multi-workspace and single-workspace route to the shared SignInUpWorkspaceCreationForm; SignInUp renders it for the WorkspaceCreation step regardless of domain/scope.
  • The subdomain field shows only in multi-workspace; single-workspace keeps its fixed address.

Logo on the creation step

  • New scoped uploadNewWorkspaceLogo(workspaceId, file) mutation: the creator sets a logo on their just-created PENDING_CREATION workspace via the workspace-agnostic token (membership enforced — only the creator is a member at that point), reusing uploadWorkspacePicture. Upload size is capped via settings.storage.maxFileSize (also applied to the existing logo / profile-picture uploads).
  • The picked file is held locally (object-URL preview, revoked on unmount) and uploaded right after creation (non-fatal on failure).

Onboarding step → pure activation loader

  • The old "Create your workspace" form (name + logo) is removed. The onboarding step now activates the pending workspace on mount and shows the loader, with a Retry action on failure.

Testing

  • typecheck (front + server) ✅; oxlint + oxfmt clean on changed files ✅
  • Unit tests: auth.resolver.spec, useWorkspaceSubdomainField, SignInUpWorkspaceCreationForm (multi + single-workspace), useAuth
  • Metadata GraphQL + twenty-client-sdk schema regenerated.

Follow-up to #21641.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op

Comment thread packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx Outdated
@FelixMalfait FelixMalfait marked this pull request as draft June 17, 2026 11:55

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

4 issues found across 11 files

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

Re-trigger cubic

Comment thread packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx Outdated
Comment thread packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts Outdated
@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Jun 17, 2026

Copy link
Copy Markdown

Visual Regression Report (twenty-front)

  • 2 changed
  • 667 unchanged
Changed stories
Story Diff %
pages-onboarding-createworkspace--default 6%
modules-settings-datamodel-objects-forms-settingsdatamodelobjectaboutform--default 0%

@FelixMalfait FelixMalfait force-pushed the claude/workspace-creation-logo-step-1 branch 2 times, most recently from 778ae69 to cf45463 Compare June 17, 2026 12:42
@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Jun 17, 2026

Copy link
Copy Markdown

🚀 Preview Environment Ready!

Your preview environment is available at: https://athletes-prospects-northwest-betty.trycloudflare.com

This environment will automatically shut down after 5 hours.

Unify workspace creation through one form on the root domain (name + logo, plus
subdomain in multi-workspace), removing the duplicate subdomain-side prompt and
the on-the-fly nameless creation that could strand users mid-onboarding.

- New scoped uploadNewWorkspaceLogo(workspaceId, file) mutation: the creator sets
  a logo on their PENDING_CREATION workspace via the workspace-agnostic token
  (membership enforced); upload size capped via settings.storage.maxFileSize
  (also applied to the existing logo / profile-picture uploads).
- The creation form is shared by multi- and single-workspace self-host; the
  in-app "Create Workspace" entry now redirects to the root-domain creation form
  (?action=create-new-workspace) instead of creating a nameless workspace.
- The onboarding step is a pure activation loader (Retry on failure); the name is
  always set by then, so the displayName guard is removed.

Follow-up to #21641.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op
@FelixMalfait FelixMalfait force-pushed the claude/workspace-creation-logo-step-1 branch from cf45463 to 1149e99 Compare June 17, 2026 15:54
@FelixMalfait FelixMalfait marked this pull request as ready for review June 17, 2026 15:57
@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Jun 17, 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.

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

3 issues found across 22 files

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

Re-trigger cubic

Comment thread packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx
- Authorize the new-workspace logo upload before buffering the stream, so a
  rejected request never reads the untrusted file into memory (authorization
  split out of the upload service and reused on the resolver)
- Gate workspace activation on the workspace being loaded, since the backend
  requires a non-empty display name to activate
- Skip subdomain availability lookups in single-workspace mode where the
  address field is hidden

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op
'create-new-workspace';

if (availableWorkspacesCount === 0 || wantsToCreateNewWorkspace) {
// Both multi-workspace and single-workspace self-host now go through the

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 this comment

width: 100%;
`;

// The workspace name (and optional logo) are already collected in the shared

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. Should this step/file be renamed then?

FelixMalfait and others added 2 commits June 17, 2026 19:16
…InUp

Per review feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op
Per review feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op
setNextOnboardingStatus,
]);

// Activation sends the workspace name to the backend, so we wait for the

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. Also I don't understand why we still need to send the display name?

Comment thread packages/twenty-front/src/pages/onboarding/CreateWorkspace.tsx
FelixMalfait and others added 2 commits June 17, 2026 20:10
In single-workspace mode isOnAWorkspace is always true, so sign-up went to
signUpInWorkspace and created a workspace with no name, which then failed
activation ("'displayName' not provided"). Route no-existing-workspace sign-up
(including single-workspace) through the workspace-agnostic path so the user
lands on the creation form and the workspace is named before it is created.

Single-workspace has no subdomain to redirect to after creation, so authenticate
in place via the login token and let onboarding take over on the same domain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op
…visioning

Enforce the invariant that a PENDING workspace always has a name:

- signUpOnNewWorkspace now requires a non-empty displayName (only the user can
  supply a name; the subdomain is still auto-generated when omitted), so a
  nameless workspace can no longer be created.
- SSO sign-ins with no target workspace/invitation now route through the same
  workspace-agnostic create-or-select flow as credentials, instead of
  auto-creating a workspace.
- activateWorkspace no longer requires or rewrites displayName (the name is set
  at creation); the onboarding loader stops sending it. Renaming at activation
  remains available as an optional override.

Integration util updated to name the workspace at creation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op

Copy link
Copy Markdown
Member Author

Design notes for reviewers (keeping these here rather than as inline code comments):

Naming — required at creation, not at activation. signUpOnNewWorkspace now requires a non-empty displayName. Only the user can supply a name, whereas the subdomain is auto-generated from the work-email domain when omitted (and is fixed in single-workspace) — that's why the two are asymmetric. activateWorkspace no longer requires or rewrites the name; it just provisions the tenant (rename stays available as an optional override), which is why the onboarding loader now calls it with input: {}.

Invariant: a PENDING workspace always has a name. With naming enforced at creation, no path can create a nameless workspace, so the activation step can't be reached with an empty name (the original "Workspace creation failed" bug).

Single-workspace sign-up routing. isOnAWorkspace is always true in single-workspace mode, so sign-up is routed through the workspace-agnostic path → the shared creation form (name collected before the workspace exists). There's no subdomain to redirect to afterwards, so we authenticate in place via the login token and let onboarding take over on the same domain. SSO sign-ins with no target workspace/invite go through that same agnostic→form flow.

Logo upload. The picked file is held locally (object URL, revoked on unmount to avoid a blob leak) and uploaded only after the workspace exists; a logo failure is non-fatal. uploadNewWorkspaceLogo authorizes the caller against the pending workspace before reading the upload stream, so a rejected request never buffers an untrusted file into memory.


Generated by Claude Code

FelixMalfait and others added 2 commits June 17, 2026 20:54
Move the design rationale to the PR thread rather than inline code comments
(per review preference), including a now-stale activation comment. Also collapse
the duplicated isContinueDisabled ternary into a single expression and drop a
redundant clearStepTimeouts() call (the finally block already clears).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op
Comment on lines +588 to +592
return this.fileCorePictureService.uploadWorkspacePicture({
file: buffer,
filename,
workspace,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The uploadNewWorkspaceLogo resolver attempts to write file metadata to the workspace database before the corresponding schema has been created, leading to a database error.
Severity: HIGH

Suggested Fix

Defer the logo upload until after the workspace has been successfully activated. Alternatively, ensure the workspace database schema is created before any file operations are attempted, possibly by creating it during the initial workspace creation step rather than during activation.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: packages/twenty-server/src/engine/core-modules/auth/auth.resolver.ts#L588-L592

Potential issue: When a user creates a new workspace and immediately uploads a logo, the
`uploadNewWorkspaceLogo` resolver is triggered. This calls
`fileStorageService.writeFile`, which attempts to save file metadata using
`fileRepository.upsert()`. However, the workspace's database schema is only created
during the `activateWorkspace` step, which occurs after the logo upload attempt. This
race condition results in an attempt to write to a non-existent database schema, causing
a database error and preventing the logo from being saved correctly.

Also affects:

  • packages/twenty-server/src/engine/core-modules/file/file-core-picture/services/file-core-picture.service.ts:160~169

FelixMalfait and others added 2 commits June 17, 2026 21:29
…peForm

SignInUp renders the creation form at the top level for the WorkspaceCreation
step before this form is reached, so the WorkspaceCreation branch and its guard
here were unreachable. Removed them and the now-unused import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op
createWorkspace had a single caller that always passed newTab: false, so the
new-tab branch was unreachable. Removed the param and the '_blank' branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op
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