Skip to content

fix(ui): stop RoundedLink labels clipping in table cells#21413

Open
bcharleson wants to merge 13 commits into
twentyhq:mainfrom
bcharleson:fix/rounded-link-label-clipping
Open

fix(ui): stop RoundedLink labels clipping in table cells#21413
bcharleson wants to merge 13 commits into
twentyhq:mainfrom
bcharleson:fix/rounded-link-label-clipping

Conversation

@bcharleson

Copy link
Copy Markdown

Summary

Domain / URL link chips in table views clipped label text vertically (letters cut off at top and bottom), making links look broken or struck through.

Root cause: RoundedLink set height: 10px on the content box with overflow: hidden, while labels use ~12px font — text overflow was hidden.

Fix: Align with Chip small sizing: box-sizing: border-box, height: spacing[3], font-size: sm, flex centering, and correct font-weight (was incorrectly using the font.size.md token).

Test plan

  • Open Companies table with Domain Name column — pills show full hostname, vertically centered
  • Links / URL / email phone fields using RoundedLink still look correct
  • Storybook: UI/Navigation/Link/RoundedLink

Fixes visual regression in dense record tables (Companies domain column).

Made with Cursor

bcharleson and others added 13 commits May 29, 2026 04:54
Remove Twenty branding fallbacks on auth and navigation, document fork
management and upstream sync, and add weekly upstream PR plus GHCR publish
workflows for the TOFU CRM image.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use stable v3/v6 action tags so the tofu-twenty image build can run on tag push.

Co-authored-by: Cursor <cursoragent@cursor.com>
Inject workspace logo into HTML for crawlers, redirect /favicon.ico to the
signed logo URL, and extend PageFavicon for PWA manifest overrides so Slack
and browser unfurls no longer show Twenty's default icon.

Co-authored-by: Cursor <cursoragent@cursor.com>
twenty-shared/utils does not re-export this guard; use the same import
pattern as other twenty-front modules so the Docker build succeeds.

Co-authored-by: Cursor <cursoragent@cursor.com>
Restores server startup after workspace branding middleware was added.

Co-authored-by: Cursor <cursoragent@cursor.com>
Serve SPA index through branding middleware, use stable /favicon.ico
for tab and install icons, slim static manifest fallback, and document
upstream merge checklist in tofu/WHITE-LABEL.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
Use a shared front dist path in Docker, resolve branding from SERVER_URL when proxy headers are incomplete, handle /favicon.ico in middleware, and point PageFavicon at the signed workspace logo URL.

Co-authored-by: Cursor <cursoragent@cursor.com>
Import isNonEmptyString from @sniptt/guards instead of twenty-shared/utils
so favicon and branded index HTML resolve correctly in production.

Co-authored-by: Cursor <cursoragent@cursor.com>
Serve /manifest.json from workspace branding middleware so install prompts
and fresh loads stay white-labeled, with a generic SVG fallback when no
workspace logo is configured.

Co-authored-by: Cursor <cursoragent@cursor.com>
Redirect /favicon.ico to the neutral default icon when no workspace logo
is set, detect logo format in generated manifests, and skip upstream CD
workflows that require TWENTY_INFRA_TOKEN on the TOFU fork.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Merge upstream/main (2.11.0) into TOFU fork. Resolves conflicts in
cd-deploy-tag.yaml, ImageInput.tsx, and SignInUp.tsx while preserving
white-label patches and the upstream-only deploy guard.
The link chip used a 10px content height with overflow hidden, which cut off
domain labels in dense table views. Match Chip small sizing: border-box,
spacing-3 height, sm font size, and centered flex alignment.

Co-authored-by: Cursor <cursoragent@cursor.com>
@twenty-ci-bot-public

Copy link
Copy Markdown

👋 Thanks for contributing to Twenty! We're excited to have you on board.

Your PR has been set to draft while you work on it. Once you're done, mark it as Ready for review and our automated checks will run.

By submitting your Pull Request, you acknowledge that you agree with the terms of our Contributor License Agreement.

@github-actions

Copy link
Copy Markdown
Contributor

Welcome!

Hello there, congrats on your first PR! We're excited to have you contributing to this project.
By submitting your Pull Request, you acknowledge that you agree with the terms of our Contributor License Agreement.

Generated by 🚫 dangerJS against 743c047

Copilot AI 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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Introduces a TOFU-specific white-labeling and ops playbook for a Twenty CRM fork, including server-side first-paint branding (favicon/manifest/HTML head injection) and GitHub automation for upstream syncing + image publishing.

Changes:

  • Added extensive TOFU documentation for deployment, pricing, white-label requirements, and upstream sync procedures.
  • Implemented server-side workspace branding (dynamic /manifest.json, /favicon.ico, and branded SPA HTML on first load) plus related frontend white-label tweaks.
  • Added GitHub workflows for weekly upstream sync PRs and GHCR Docker image publishing for TOFU tags.

Reviewed changes

Copilot reviewed 45 out of 46 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tofu/partnerships/twenty-crm/company-profile.md Partnership research brief for Twenty
tofu/WHITE-LABEL.md White-label patch inventory + verification checklist
tofu/UPSTREAM-SYNC.md Describes weekly upstream sync PR workflow
tofu/SETUP.md Local/dev/Compose setup paths and troubleshooting
tofu/README.md Fork rationale, licensing notes, and doc index
tofu/PRICING.md Internal cost model + client pricing tiers
tofu/FORK-MANAGEMENT.md Fork strategy, patch tracking, and merge playbook
tofu/DEPLOYMENT-DIGITALOCEAN.md Canonical DO App Platform + Supabase + Upstash deployment guide
tofu/DATABASE.md Postgres/Redis/storage architecture and Supabase wiring guidance
tofu/CLIENT-CUSTOMIZATION.md Step-by-step client onboarding/customization checklist
tofu/BRANDING.md Branding/asset map and white-labeling guidance
packages/twenty-ui-deprecated/src/navigation/link/components/RoundedLink.tsx Adjusts link styling to align with new sizing/typography
packages/twenty-server/src/utils/generate-front-config.ts Uses shared dist-path resolver; skips config injection when front dist isn’t present
packages/twenty-server/src/engine/core-modules/workspace-branding/workspace-branding.service.ts Adds service to resolve workspace branding + cache index.html template
packages/twenty-server/src/engine/core-modules/workspace-branding/workspace-branding.module.ts New module wiring for branding service/middleware/controller
packages/twenty-server/src/engine/core-modules/workspace-branding/workspace-branding.middleware.ts Middleware serving branded SPA HTML + dynamic manifest + favicon redirects
packages/twenty-server/src/engine/core-modules/workspace-branding/workspace-branding.controller.ts Controller for /favicon.ico (currently overlaps with middleware)
packages/twenty-server/src/engine/core-modules/workspace-branding/utils/is-static-asset-path.util.ts Utility to prevent branding HTML injection on API/static paths
packages/twenty-server/src/engine/core-modules/workspace-branding/utils/inject-workspace-branding-into-index-html.util.ts Injects workspace-specific meta/title/icon tags into an HTML template
packages/twenty-server/src/engine/core-modules/workspace-branding/utils/get-request-origin.util.ts Extracts origin for branding resolution (uses forwarded headers)
packages/twenty-server/src/engine/core-modules/workspace-branding/utils/get-icon-mime-type-from-url.util.ts MIME/sizes helpers for manifest icon selection
packages/twenty-server/src/engine/core-modules/workspace-branding/utils/get-front-index-html-path.util.ts Locates built front index.html
packages/twenty-server/src/engine/core-modules/workspace-branding/utils/get-front-dist-path.util.ts Locates built front dist directory across environments
packages/twenty-server/src/engine/core-modules/workspace-branding/utils/generate-workspace-manifest.util.ts Generates branded or neutral fallback PWA manifest
packages/twenty-server/src/engine/core-modules/workspace-branding/utils/tests/inject-workspace-branding-into-index-html.util.spec.ts Adds unit tests for HTML injection
packages/twenty-server/src/engine/core-modules/workspace-branding/utils/tests/generate-workspace-manifest.util.spec.ts Adds unit tests for manifest generation
packages/twenty-server/src/engine/core-modules/core-engine.module.ts Registers WorkspaceBrandingModule in core engine
packages/twenty-server/src/app.module.ts Serves static front with index: false and applies branding middleware on GET *
packages/twenty-front/src/utils/title-utils.ts Default title becomes “CRM” (i18n string)
packages/twenty-front/src/utils/tests/title-utils.test.ts Updates tests for new default title
packages/twenty-front/src/pages/auth/SignInUp.tsx Uses workspace logo as primary; changes “Welcome to Twenty” copy
packages/twenty-front/src/modules/ui/utilities/page-favicon/components/PageFavicon.tsx Updates favicon/manifest behavior to align with server-side branding
packages/twenty-front/src/modules/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName.ts Default workspace name no longer “Twenty”
packages/twenty-front/src/modules/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo.ts Removes Twenty CDN logo fallback
packages/twenty-front/src/modules/ui/input/components/ImageInput.tsx Forces <img> refresh when pictureURI changes
packages/twenty-front/src/modules/auth/sign-in-up/components/FooterNote.tsx Removes Twenty legal links from sign-in footer; simplifies bypass
packages/twenty-front/src/modules/auth/components/Logo.tsx Removes default Twenty icon fallback and adjusts rendering when no logo is set
packages/twenty-front/src/modules/activities/timeline-activities/utils/getTimelineActivityAuthorFullName.ts System events read “System” instead of “Twenty”
packages/twenty-front/public/manifest.json Replaces large icon set with neutral single icon
packages/twenty-front/public/branding/default-app-icon.svg Adds neutral default app icon asset
packages/twenty-front/public/branding/README.md Documents neutral branding assets and replacement steps
packages/twenty-front/index.html Adds branding injection anchor block and neutral defaults
.github/workflows/tofu-upstream-sync.yaml Weekly workflow to open upstream sync PRs
.github/workflows/tofu-docker-publish.yaml Publishes GHCR image on tag push / manual dispatch
.github/workflows/cd-deploy-tag.yaml Prevents upstream deploy workflow from running in fork
.github/workflows/cd-deploy-main.yaml Prevents upstream deploy workflow from running in fork
Comments suppressed due to low confidence (2)

packages/twenty-server/src/engine/core-modules/workspace-branding/workspace-branding.middleware.ts:1

  • This middleware fully handles /favicon.ico, but there is also a WorkspaceBrandingController that defines GET favicon.ico and returns 404 when no branding is found. With the middleware applied to all GET routes, the controller path is effectively shadowed, and the two implementations disagree on the fallback behavior (302 to default asset vs 404). Pick a single owner for /favicon.ico (recommended: middleware only, remove the controller route) to avoid confusion and future regressions.
    packages/twenty-server/src/engine/core-modules/workspace-branding/workspace-branding.service.ts:1
  • readFileSync can throw (permissions, transient filesystem issues, partial image builds), which would fail module initialization and can prevent the server from starting. Wrap the readFileSync in a try/catch, log a warning, and leave indexHtmlTemplate as null so the middleware safely falls back to next() behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +30 to +40
- name: Create sync branch and merge upstream
id: merge
run: |
SYNC_BRANCH="upstream/sync-$(date -u +%Y-%m-%d)"
echo "branch=${SYNC_BRANCH}" >> "$GITHUB_OUTPUT"
git checkout -B "$SYNC_BRANCH"
if git merge upstream/main -m "chore: sync upstream Twenty ($(date -u +%Y-%m-%d))"; then
echo "status=clean" >> "$GITHUB_OUTPUT"
else
echo "status=conflicts" >> "$GITHUB_OUTPUT"
fi
Comment on lines +4 to +18
const forwardedProto = request.headers['x-forwarded-proto'];
const protocol =
typeof forwardedProto === 'string'
? forwardedProto.split(',')[0]?.trim()
: request.protocol;

const forwardedHost = request.headers['x-forwarded-host'];
const hostHeader =
typeof forwardedHost === 'string'
? forwardedHost.split(',')[0]?.trim()
: request.headers.host;

const hostname = hostHeader ?? request.hostname;

return `${protocol}://${hostname}`;
Comment on lines +90 to 95
// No logo uploaded yet — link back to default domain so admins can
// still navigate, but show nothing instead of the Twenty icon.
<UndecoratedLink
to={AppPath.SignInUp}
onClick={redirectToDefaultDomain}
>
<StyledPrimaryLogo
style={{ backgroundImage: `url(${primaryLogoUrl})` }}
/>
</UndecoratedLink>
) : (
<StyledPrimaryLogo
style={{ backgroundImage: `url(${primaryLogoUrl})` }}
/>
Comment on lines +75 to +84
const workspaceLogo = workspacePublicData?.logo ?? currentWorkspace?.logo;
const workspaceDisplayName =
workspacePublicData?.displayName ??
currentWorkspace?.displayName ??
DEFAULT_PWA_NAME;

const hasWorkspaceLogo = isNonEmptyString(workspaceLogo);
const faviconUrl = hasWorkspaceLogo
? workspaceLogo
: getServerFaviconUrl();

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 743c047165

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const STATIC_ASSET_PATH_PATTERN =
/\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|map|json|txt|webp|wasm)$/i;

const API_PATH_PREFIXES = [

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Let API GET routes bypass the branding middleware

Because WorkspaceBrandingMiddleware is registered for every GET request in AppModule, any controller path missing from this allow-list is answered with the SPA index before the controller runs. I checked existing GET controllers such as @Controller('apps/oauth') for /apps/oauth/authorize and @Controller('.well-known') for /.well-known/oauth-authorization-server; neither prefix is listed here, so those endpoints now return branded HTML instead of the OAuth redirect/metadata JSON, breaking connection OAuth and OAuth discovery.

Useful? React with 👍 / 👎.

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

23 issues found across 46 files

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="tofu/BRANDING.md">

<violation number="1" location="tofu/BRANDING.md:74">
P2: The grep audit command is case-sensitive and will miss differently-cased branding strings.</violation>
</file>

<file name="packages/twenty-front/src/modules/ui/input/components/ImageInput.tsx">

<violation number="1" location="packages/twenty-front/src/modules/ui/input/components/ImageInput.tsx:138">
P1: `isPictureURLError` is never reset when `picture` changes, so a single `onError` permanently suppresses the `<img>` element even after a valid URL is provided. The new `key={pictureURI}` is ineffective because the image branch is gated by `!isPictureURLError`.</violation>
</file>

<file name="tofu/PRICING.md">

<violation number="1" location="tofu/PRICING.md:80">
P1: Launch tier margin calculation contradicts its own data-safety requirement: Tier 1's Supabase Free caveat says Pro is required before real client data lands, but Launch promises data migration while using Tier 1 ($17) instead of Tier 2 ($65) for its margin math.</violation>

<violation number="2" location="tofu/PRICING.md:117">
P2: Enterprise tier low-end gross margin percentage is incorrect: using the document's own numbers ($15,000 pricing minus $4,050 cost), the margin is ~73%, not 80%.</violation>

<violation number="3" location="tofu/PRICING.md:200">
P2: Arithmetic error in MRR example: 3 Custom Ops ($2,500) + 5 Growth Partner ($7,500) equals $45,000/mo, not $57,500/mo</violation>
</file>

<file name="packages/twenty-front/public/manifest.json">

<violation number="1" location="packages/twenty-front/public/manifest.json:10">
P1: Manifest replaced all platform-specific PNG icons with a single SVG icon, removing required PWA sizes (192x192, 512x512) and raster fallbacks that browsers rely on for installability.</violation>
</file>

<file name="tofu/FORK-MANAGEMENT.md">

<violation number="1" location="tofu/FORK-MANAGEMENT.md:215">
P2: Stale hard-coded AGPL cutoff in emergency freeze documentation creates legal risk</violation>
</file>

<file name="tofu/SETUP.md">

<violation number="1" location="tofu/SETUP.md:99">
P2: `sed -i ''` uses BSD/macOS syntax that fails on GNU/Linux (typical VPS OS). For cross-platform compatibility, replace the BSD-specific in-place sed commands with a portable alternative.</violation>
</file>

<file name="packages/twenty-server/src/engine/core-modules/workspace-branding/utils/get-request-origin.util.ts">

<violation number="1" location="packages/twenty-server/src/engine/core-modules/workspace-branding/utils/get-request-origin.util.ts:3">
P2: `getRequestOrigin` reads raw `X-Forwarded-*` headers directly instead of using Express's `request.protocol` and `request.hostname`, bypassing the configured `trust proxy` boundary.</violation>

<violation number="2" location="packages/twenty-server/src/engine/core-modules/workspace-branding/utils/get-request-origin.util.ts:16">
P2: Empty forwarded header tokens are kept as valid values instead of falling back to safe defaults. `split(',')[0]?.trim()` can produce an empty string for malformed headers like `x-forwarded-host: ,`, and `??` does not treat empty strings as absent, so the fallback to `request.hostname` / `request.protocol` never triggers.</violation>
</file>

<file name="tofu/DEPLOYMENT-DIGITALOCEAN.md">

<violation number="1" location="tofu/DEPLOYMENT-DIGITALOCEAN.md:45">
P2: Invalid `doctl` command documented for one-off exec: `doctl apps tier instance exec ...` does not exist. `doctl apps tier` manages App Platform pricing tiers, not instance execution. App Platform does not provide a non-interactive `exec` command; operators should use `doctl apps console` (already documented above) for shell access.</violation>
</file>

<file name="packages/twenty-server/src/engine/core-modules/workspace-branding/workspace-branding.service.ts">

<violation number="1" location="packages/twenty-server/src/engine/core-modules/workspace-branding/workspace-branding.service.ts:47">
P2: Uncaught `readFileSync` error in `onModuleInit` can crash startup instead of gracefully disabling branding</violation>
</file>

<file name="tofu/CLIENT-CUSTOMIZATION.md">

<violation number="1" location="tofu/CLIENT-CUSTOMIZATION.md:26">
P2: Conflicting documentation about no-logo fallback behavior on sign-in page</violation>
</file>

<file name="tofu/DATABASE.md">

<violation number="1" location="tofu/DATABASE.md:87">
P1: Supabase setup instructions are inconsistent: the SQL creates a `twenty_production` database, but the connection strings later connect to the built-in `postgres` database. Supabase projects have a single managed database per project; schema-level isolation should be used instead.</violation>
</file>

<file name="tofu/README.md">

<violation number="1" location="tofu/README.md:55">
P2: README Quick Start includes non-portable, machine-specific absolute filesystem paths that only exist on one developer's machine.</violation>
</file>

<file name=".github/workflows/tofu-upstream-sync.yaml">

<violation number="1" location=".github/workflows/tofu-upstream-sync.yaml:36">
P2: Workflow may fail clean merges because no git committer identity is configured before `git merge` creates a commit.</violation>

<violation number="2" location=".github/workflows/tofu-upstream-sync.yaml:36">
P1: Merge failures are not fail-closed: when `git merge upstream/main` fails with conflicts, no merge commit is created and HEAD remains at the original commit, yet the workflow continues to force-push and attempt to open a PR. This results in `gh pr create` failing or creating an empty PR with zero commits to merge.</violation>

<violation number="3" location=".github/workflows/tofu-upstream-sync.yaml:47">
P2: Unconditional `--force` push before PR-existence check can clobber manual updates on same-day sync branches</violation>
</file>

<file name=".github/workflows/tofu-docker-publish.yaml">

<violation number="1" location=".github/workflows/tofu-docker-publish.yaml:38">
P2: Workflow dispatch input `tag` is emitted directly to `$GITHUB_OUTPUT` without validation. Malformed input (newlines, invalid Docker tag characters) can break output parsing and produce invalid Docker tags downstream.</violation>
</file>

<file name="packages/twenty-server/src/engine/core-modules/workspace-branding/utils/get-icon-mime-type-from-url.util.ts">

<violation number="1" location="packages/twenty-server/src/engine/core-modules/workspace-branding/utils/get-icon-mime-type-from-url.util.ts:1">
P1: `getIconMimeTypeFromUrl` checks the full URL string with `endsWith('.svg')` / `endsWith('.ico')`, which breaks for signed CDN URLs that include query parameters or hashes. The `logoUrl` comes from `signWorkspaceLogoUrl`, a service that typically appends auth tokens as query params. An SVG logo with params will be mis-detected as `image/png`, the manifest will emit raster sizes instead of `any`, and browsers may ignore or mis-handle the icon entry.</violation>

<violation number="2" location="packages/twenty-server/src/engine/core-modules/workspace-branding/utils/get-icon-mime-type-from-url.util.ts:12">
P2: Hard-coded PNG fallback misclassifies non-PNG icon URLs in manifest metadata.</violation>
</file>

<file name="packages/twenty-server/src/engine/core-modules/workspace-branding/utils/is-static-asset-path.util.ts">

<violation number="1" location="packages/twenty-server/src/engine/core-modules/workspace-branding/utils/is-static-asset-path.util.ts:4">
P0: The `API_PATH_PREFIXES` allow-list is incomplete — it omits GET controller routes like `/apps/oauth/authorize` and `/.well-known/oauth-authorization-server`. Since `WorkspaceBrandingMiddleware` is registered for `path: '*', method: GET` and NestJS middleware runs before controllers, any route not in this list will receive branded HTML instead of the controller's response. This breaks OAuth connection flows and OIDC discovery. Add missing prefixes (at minimum `/apps/`, `/.well-known/`) or invert the approach by explicitly listing SPA-eligible paths.</violation>
</file>

<file name="packages/twenty-front/src/modules/ui/utilities/page-favicon/components/PageFavicon.tsx">

<violation number="1" location="packages/twenty-front/src/modules/ui/utilities/page-favicon/components/PageFavicon.tsx:83">
P2: `faviconUrl` uses the raw `workspaceLogo` value without normalizing to an absolute URL. If the logo is stored as a relative path (e.g., `/files/workspace-logo.png`), the `<link rel="icon">` and manifest icon entries may not resolve correctly in all contexts (e.g., PWA manifest requires absolute URLs for icons). Apply `getImageAbsoluteURI` with `REACT_APP_SERVER_BASE_URL` before using the value, consistent with how other components handle workspace logos.</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

const STATIC_ASSET_PATH_PATTERN =
/\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|map|json|txt|webp|wasm)$/i;

const API_PATH_PREFIXES = [

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.

P0: The API_PATH_PREFIXES allow-list is incomplete — it omits GET controller routes like /apps/oauth/authorize and /.well-known/oauth-authorization-server. Since WorkspaceBrandingMiddleware is registered for path: '*', method: GET and NestJS middleware runs before controllers, any route not in this list will receive branded HTML instead of the controller's response. This breaks OAuth connection flows and OIDC discovery. Add missing prefixes (at minimum /apps/, /.well-known/) or invert the approach by explicitly listing SPA-eligible paths.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-server/src/engine/core-modules/workspace-branding/utils/is-static-asset-path.util.ts, line 4:

<comment>The `API_PATH_PREFIXES` allow-list is incomplete — it omits GET controller routes like `/apps/oauth/authorize` and `/.well-known/oauth-authorization-server`. Since `WorkspaceBrandingMiddleware` is registered for `path: '*', method: GET` and NestJS middleware runs before controllers, any route not in this list will receive branded HTML instead of the controller's response. This breaks OAuth connection flows and OIDC discovery. Add missing prefixes (at minimum `/apps/`, `/.well-known/`) or invert the approach by explicitly listing SPA-eligible paths.</comment>

<file context>
@@ -0,0 +1,28 @@
+const STATIC_ASSET_PATH_PATTERN =
+  /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|map|json|txt|webp|wasm)$/i;
+
+const API_PATH_PREFIXES = [
+  '/graphql',
+  '/metadata',
</file context>

>
{pictureURI && !isPictureURLError ? (
<img
key={pictureURI}

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.

P1: isPictureURLError is never reset when picture changes, so a single onError permanently suppresses the <img> element even after a valid URL is provided. The new key={pictureURI} is ineffective because the image branch is gated by !isPictureURLError.

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/ui/input/components/ImageInput.tsx, line 138:

<comment>`isPictureURLError` is never reset when `picture` changes, so a single `onError` permanently suppresses the `<img>` element even after a valid URL is provided. The new `key={pictureURI}` is ineffective because the image branch is gated by `!isPictureURLError`.</comment>

<file context>
@@ -135,6 +135,7 @@ export const ImageInput = ({
       >
         {pictureURI && !isPictureURLError ? (
           <img
+            key={pictureURI}
             src={pictureURI}
             alt="profile"
</file context>

Comment thread tofu/PRICING.md
- 30 days of post-launch support included
- Then $500/mo for hosting + uptime monitoring + Twenty upstream updates

**Our margin:** $500 − $17 (Tier 1 infra) = **$483/mo margin (97%)**

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.

P1: Launch tier margin calculation contradicts its own data-safety requirement: Tier 1's Supabase Free caveat says Pro is required before real client data lands, but Launch promises data migration while using Tier 1 ($17) instead of Tier 2 ($65) for its margin math.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tofu/PRICING.md, line 80:

<comment>Launch tier margin calculation contradicts its own data-safety requirement: Tier 1's Supabase Free caveat says Pro is required before real client data lands, but Launch promises data migration while using Tier 1 ($17) instead of Tier 2 ($65) for its margin math.</comment>

<file context>
@@ -0,0 +1,224 @@
+- 30 days of post-launch support included
+- Then $500/mo for hosting + uptime monitoring + Twenty upstream updates
+
+**Our margin:** $500 − $17 (Tier 1 infra) = **$483/mo margin (97%)**
+
+#### **Custom Ops** — one-time $25,000–$50,000 + $2,500/mo managed
</file context>

@@ -1,458 +1,16 @@
{

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.

P1: Manifest replaced all platform-specific PNG icons with a single SVG icon, removing required PWA sizes (192x192, 512x512) and raster fallbacks that browsers rely on for installability.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-front/public/manifest.json, line 10:

<comment>Manifest replaced all platform-specific PNG icons with a single SVG icon, removing required PWA sizes (192x192, 512x512) and raster fallbacks that browsers rely on for installability.</comment>

<file context>
@@ -1,458 +1,16 @@
-    {
-      "src": "images/icons/ios/1024.png",
-      "sizes": "1024x1024"
+      "src": "/branding/default-app-icon.svg",
+      "sizes": "any",
+      "type": "image/svg+xml",
</file context>

Comment thread tofu/DATABASE.md
-- Create a dedicated user + database for Twenty
-- Safer than using the default postgres role
CREATE USER twenty_app WITH PASSWORD 'CHANGE_ME_HEX_PASSWORD';
CREATE DATABASE twenty_production OWNER twenty_app;

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.

P1: Supabase setup instructions are inconsistent: the SQL creates a twenty_production database, but the connection strings later connect to the built-in postgres database. Supabase projects have a single managed database per project; schema-level isolation should be used instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tofu/DATABASE.md, line 87:

<comment>Supabase setup instructions are inconsistent: the SQL creates a `twenty_production` database, but the connection strings later connect to the built-in `postgres` database. Supabase projects have a single managed database per project; schema-level isolation should be used instead.</comment>

<file context>
@@ -0,0 +1,218 @@
+-- Create a dedicated user + database for Twenty
+-- Safer than using the default postgres role
+CREATE USER twenty_app WITH PASSWORD 'CHANGE_ME_HEX_PASSWORD';
+CREATE DATABASE twenty_production OWNER twenty_app;
+GRANT ALL PRIVILEGES ON DATABASE twenty_production TO twenty_app;
+```
</file context>

GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SYNC_BRANCH: ${{ steps.merge.outputs.branch }}
run: |
git push -u origin "$SYNC_BRANCH" --force

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: Unconditional --force push before PR-existence check can clobber manual updates on same-day sync branches

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/tofu-upstream-sync.yaml, line 47:

<comment>Unconditional `--force` push before PR-existence check can clobber manual updates on same-day sync branches</comment>

<file context>
@@ -0,0 +1,71 @@
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          SYNC_BRANCH: ${{ steps.merge.outputs.branch }}
+        run: |
+          git push -u origin "$SYNC_BRANCH" --force
+
+      - name: Open pull request
</file context>

SYNC_BRANCH="upstream/sync-$(date -u +%Y-%m-%d)"
echo "branch=${SYNC_BRANCH}" >> "$GITHUB_OUTPUT"
git checkout -B "$SYNC_BRANCH"
if git merge upstream/main -m "chore: sync upstream Twenty ($(date -u +%Y-%m-%d))"; then

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: Workflow may fail clean merges because no git committer identity is configured before git merge creates a commit.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/tofu-upstream-sync.yaml, line 36:

<comment>Workflow may fail clean merges because no git committer identity is configured before `git merge` creates a commit.</comment>

<file context>
@@ -0,0 +1,71 @@
+          SYNC_BRANCH="upstream/sync-$(date -u +%Y-%m-%d)"
+          echo "branch=${SYNC_BRANCH}" >> "$GITHUB_OUTPUT"
+          git checkout -B "$SYNC_BRANCH"
+          if git merge upstream/main -m "chore: sync upstream Twenty ($(date -u +%Y-%m-%d))"; then
+            echo "status=clean" >> "$GITHUB_OUTPUT"
+          else
</file context>

else
TAG="${GITHUB_REF_NAME}"
fi
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"

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: Workflow dispatch input tag is emitted directly to $GITHUB_OUTPUT without validation. Malformed input (newlines, invalid Docker tag characters) can break output parsing and produce invalid Docker tags downstream.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/tofu-docker-publish.yaml, line 38:

<comment>Workflow dispatch input `tag` is emitted directly to `$GITHUB_OUTPUT` without validation. Malformed input (newlines, invalid Docker tag characters) can break output parsing and produce invalid Docker tags downstream.</comment>

<file context>
@@ -0,0 +1,63 @@
+          else
+            TAG="${GITHUB_REF_NAME}"
+          fi
+          echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
+
+      - name: Set up Docker Buildx
</file context>

return 'image/x-icon';
}

return 'image/png';

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: Hard-coded PNG fallback misclassifies non-PNG icon URLs in manifest metadata.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/twenty-server/src/engine/core-modules/workspace-branding/utils/get-icon-mime-type-from-url.util.ts, line 12:

<comment>Hard-coded PNG fallback misclassifies non-PNG icon URLs in manifest metadata.</comment>

<file context>
@@ -0,0 +1,20 @@
+    return 'image/x-icon';
+  }
+
+  return 'image/png';
+};
+
</file context>


const hasWorkspaceLogo = isNonEmptyString(workspaceLogo);
const faviconUrl = hasWorkspaceLogo
? workspaceLogo

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: faviconUrl uses the raw workspaceLogo value without normalizing to an absolute URL. If the logo is stored as a relative path (e.g., /files/workspace-logo.png), the <link rel="icon"> and manifest icon entries may not resolve correctly in all contexts (e.g., PWA manifest requires absolute URLs for icons). Apply getImageAbsoluteURI with REACT_APP_SERVER_BASE_URL before using the value, consistent with how other components handle workspace logos.

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/ui/utilities/page-favicon/components/PageFavicon.tsx, line 83:

<comment>`faviconUrl` uses the raw `workspaceLogo` value without normalizing to an absolute URL. If the logo is stored as a relative path (e.g., `/files/workspace-logo.png`), the `<link rel="icon">` and manifest icon entries may not resolve correctly in all contexts (e.g., PWA manifest requires absolute URLs for icons). Apply `getImageAbsoluteURI` with `REACT_APP_SERVER_BASE_URL` before using the value, consistent with how other components handle workspace logos.</comment>

<file context>
@@ -1,26 +1,131 @@
+
+  const hasWorkspaceLogo = isNonEmptyString(workspaceLogo);
+  const faviconUrl = hasWorkspaceLogo
+    ? workspaceLogo
+    : getServerFaviconUrl();
+
</file context>

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.

2 participants