## Why In the side panel and on mobile, a tab holding a single widget renders with no gutter at all: the field list sits flush against the panel border. Regression from #23109. `getWidgetCardVariant` checked the derived presentation before the surface: ```ts if (presentation === 'solo') return 'solo'; const isSideColumnContext = isInPinnedTab || isMobile || isInSidePanel; ``` So a single-widget tab resolved to `'solo'` even in the side panel or on mobile, and `'solo'` has no branch in `WidgetCard`'s padding switch, so it falls through to `0`. The same widget used to match `variant === 'side-column' && !isEditable` and get `spacing[3]` (12px). The pinned left panel escaped this only because `PageLayoutLeftPanel` hardcodes `presentation: 'stack'` — the rule was already there ("the pinned left panel is always a column, a surface rule not a widget rule"), just applied at one call site instead of being the rule. ## Why only the Home tab looks broken Every widget that used to live on a `CANVAS` tab carries its own gutter, so losing the card padding costs them nothing: | Widget | Own horizontal padding | |---|---| | Timeline | `spacing[6]` | | Notes | `spacing[6]` | | Files | `spacing[6]` | | Tasks | `spacing[6]` | | **Fields** | **none** | `Fields` was the only widget on a `VERTICAL_LIST` tab, so it was the only one relying on the card for its gutter, and the only one that ends up flush. ## What Resolve the surface first: a column surface (pinned panel, side panel, mobile) is always a column of cards, whatever the tab presentation is. Solo stays a main-tab-area concept. Header visibility is untouched: `showHeader` keys off `presentation`, not the variant, so a solo tab still shows no bare title row. The `Fields` widget does not regain the header it lost in #23109. ## Measured Side panel, custom object whose Home tab holds a single Fields widget (1600x1000, panel at x=1200): | | Card padding | First label x | |---|---|---| | main | `0px` | 1221 | | this PR | `12px` | 1233 | 12px restored, matching what the pinned left panel gives the same widget. ## Trade-off worth a second opinion In the side panel and on mobile, the activity widgets now resolve to `'side-column'` instead of `'solo'`, so they pick up the card's 12px on top of their own 24px, i.e. 36px instead of 24px. Nothing overlaps or clips, but it is a visible change on those tabs. If you would rather keep them at 24px, the follow-up is to drop the intrinsic `spacing[6]` from the activity cards and let the surface own the gutter everywhere. ## Test plan - `getWidgetCardVariant` tests extended: `'side-column'` now wins over `'solo'` for each of `isInPinnedTab` / `isMobile` / `isInSidePanel`. 13 tests pass. - 88 suites / 620 tests across `page-layout/widgets` pass. - `lint:diff-with-main twenty-front` clean. - Verified against a local stack: side panel on a single-widget Home tab, before and after.
The #1 Open-Source CRM
Website ·
Documentation ·
Roadmap ·
Discord ·
Figma
Why Twenty
Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.
Learn more about why we built Twenty
Installation
Cloud
The fastest way to get started. Sign up at twenty.com and spin up a workspace in under a minute, with no infrastructure to manage and always up to date.
Build an app
Scaffold a new app with the Twenty CLI:
npx create-twenty-app my-app
Define objects, fields, and views as code:
import { defineObject, FieldType } from 'twenty-sdk/define';
export default defineObject({
nameSingular: 'deal',
namePlural: 'deals',
labelSingular: 'Deal',
labelPlural: 'Deals',
fields: [
{ name: 'name', label: 'Name', type: FieldType.TEXT },
{ name: 'amount', label: 'Amount', type: FieldType.CURRENCY },
{ name: 'closeDate', label: 'Close Date', type: FieldType.DATE_TIME },
],
});
Then ship it to your workspace:
npx twenty app:publish --private
See the app development guide for objects, views, agents, and logic functions.
Self-hosting
Run Twenty on your own infrastructure with Docker Compose, or contribute locally via the local setup guide.
Everything you need
Twenty gives you the building blocks of a modern CRM (objects, views, workflows, and agents) and lets you extend them as code. Here's a tour of what's in the box.
Want to go deeper? Read the User Guide for product walkthroughs, or the
Documentation for developer reference.
|
|
|
|
|
|
Stack
TypeScript
Nx
NestJS, with BullMQ,
PostgreSQL,
Redis
React, with Jotai, Linaria and Lingui
Thanks
Thanks to these amazing services that we use and recommend for code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
Join the Community
Star the repo ·
Discord ·
Feature requests ·
Releases ·
X ·
LinkedIn ·
Crowdin ·
Contribute





