fix: line-clamp property does not truncate long texts properly in app card description#23695
fix: line-clamp property does not truncate long texts properly in app card description#23695Anshumancanrock wants to merge 1 commit intocalcom:mainfrom
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughThis change updates two components to sanitize and truncate app descriptions. In packages/features/apps/components/AppCard.tsx and packages/ui/components/app-list-card/AppListCard.tsx, it imports useMemo and stripMarkdown, computes a memoized cleanDescription by stripping Markdown and collapsing whitespace, and renders that value instead of the raw description. Both components add inline CSS to clamp text to three lines using -webkit-box with WebkitLineClamp: 3, WebkitBoxOrient: vertical, plus overflow: hidden, textOverflow: ellipsis, lineHeight: 1.4, maxHeight: 4.2em, and word wrapping. No public APIs or exported declarations are changed. Possibly related PRs
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Pre-merge checks (3 passed, 2 warnings)❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@Anshumancanrock is attempting to deploy a commit to the cal Team on Vercel. A member of the Team first needs to authorize it. |
|
Hey there and thank you for opening this pull request! 👋🏼 We require pull request titles to follow the Conventional Commits specification and it looks like your proposed title needs to be adjusted. Details: |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
packages/ui/components/app-list-card/AppListCard.tsx (2)
87-98: Replace inline styles; rely on Tailwind utils and drop nonstandard word-break.Removes style attr, keeps 3-line clamp via utilities, and adds a hover title for full text.
- <p - className={classNames( - "text-sm text-subtle whitespace-normal break-words overflow-hidden", - classNameObject?.description - )} - style={{ - lineHeight: "1.5em", - maxHeight: "4.5em", // Shows exactly 3 lines of text - wordBreak: "break-word", - }}> - {description && description.length > 100 ? `${description.substring(0, 100).trim()}...` : description} - </p> + <p + title={description} + className={classNames( + "text-sm text-subtle whitespace-normal break-words overflow-hidden leading-[1.5] max-h-[4.5em] hyphens-auto", + classNameObject?.description + )} + > + {description && description.length > 100 + ? `${description.slice(0, 100).replace(/\s+\S*$/, "")}\u2026` + : description} + </p>
87-98: Optional: make length configurable to avoid “100” magic number divergence with AppCard.Expose
maxDescriptionLength?: numberhere too for consistency withAppCard.Happy to draft the prop plumbing if you want it in this PR.
packages/features/apps/components/AppCard.tsx (2)
121-129: Unify truncation approach: remove inline styles, avoid mid-word cuts, add tooltip.Keeps cross-browser 3-line clamp without WebKit props; improves readability and a11y.
- <p - className="text-default mt-2 flex-grow text-sm overflow-hidden" - style={{ - lineHeight: "1.5em", - maxHeight: "4.5em", // Shows exactly 3 lines of text - wordBreak: "break-word", - }}> - {app.description ? (app.description.length > maxDescriptionLength ? `${app.description.substring(0, maxDescriptionLength).trim()}...` : app.description) : ""} - </p> + <p + title={app.description || undefined} + className="text-default mt-2 flex-grow text-sm overflow-hidden leading-[1.5] max-h-[4.5em] hyphens-auto" + > + {app.description + ? app.description.length > (maxDescriptionLength ?? 140) + ? `${app.description.slice(0, Math.max(1, Math.floor(maxDescriptionLength ?? 140))).replace(/\s+\S*$/, "")}\u2026` + : app.description + : ""} + </p>
28-31: Confirm defaultmaxDescriptionLengthusage Only one override was found in AllApps.tsx (line 187) setting it to 100; all other ~20+<AppCard>usages—especially in the various app-store packages—rely on the 140 default and will truncate descriptions. Add explicitmaxDescriptionLengthprops where needed to prevent unintended UI changes.packages/features/apps/components/AllApps.tsx (1)
187-187: LGTM; explicit limit aligns with the new API.Minor: consider a shared constant to keep
/appsand UI cards in sync.Example (outside this hunk):
// packages/features/apps/components/constants.ts export const APP_DESCRIPTION_LIMIT = 100;Then here:
<AppCard ... maxDescriptionLength={APP_DESCRIPTION_LIMIT} />
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
packages/features/apps/components/AllApps.tsx(1 hunks)packages/features/apps/components/AppCard.tsx(4 hunks)packages/ui/components/app-list-card/AppListCard.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Always use
t()for text localization in frontend code; direct text embedding should trigger a warning
Files:
packages/ui/components/app-list-card/AppListCard.tsxpackages/features/apps/components/AllApps.tsxpackages/features/apps/components/AppCard.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js
.utc()in hot paths like loops
Files:
packages/ui/components/app-list-card/AppListCard.tsxpackages/features/apps/components/AllApps.tsxpackages/features/apps/components/AppCard.tsx
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit configuration file
Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.
Files:
packages/ui/components/app-list-card/AppListCard.tsxpackages/features/apps/components/AllApps.tsxpackages/features/apps/components/AppCard.tsx
🧠 Learnings (2)
📓 Common learnings
Learnt from: supalarry
PR: calcom/cal.com#22976
File: packages/ui/components/app-list-card/AppListCard.tsx:63-67
Timestamp: 2025-08-12T08:53:11.848Z
Learning: In AppListCard.tsx, the className || classNameObject?.container fallback pattern is intentional to encourage users to migrate from the legacy className prop to the new granular classNameObject styling system, rather than merging both approaches.
📚 Learning: 2025-08-12T08:53:11.848Z
Learnt from: supalarry
PR: calcom/cal.com#22976
File: packages/ui/components/app-list-card/AppListCard.tsx:63-67
Timestamp: 2025-08-12T08:53:11.848Z
Learning: In AppListCard.tsx, the className || classNameObject?.container fallback pattern is intentional to encourage users to migrate from the legacy className prop to the new granular classNameObject styling system, rather than merging both approaches.
Applied to files:
packages/ui/components/app-list-card/AppListCard.tsx
| <p | ||
| className="text-default mt-2 flex-grow text-sm overflow-hidden" | ||
| style={{ | ||
| overflow: "hidden", | ||
| display: "-webkit-box", | ||
| WebkitBoxOrient: "vertical", | ||
| WebkitLineClamp: "3", | ||
| lineHeight: "1.5em", | ||
| maxHeight: "4.5em", // Shows exactly 3 lines of text | ||
| wordBreak: "break-word", | ||
| }}> | ||
| {app.description} | ||
| {app.description ? (app.description.length > maxDescriptionLength ? `${app.description.substring(0, maxDescriptionLength).trim()}...` : app.description) : ""} | ||
| </p> |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Repo-wide check: ensure no lingering WebKit-only clamping remains.
Catches any leftover -webkit-line-clamp/display:-webkit-box.
🏁 Script executed:
#!/bin/bash
# Find legacy clamping usage
rg -nP -C2 --glob '!**/dist/**' --glob '!**/build/**' -S '(-webkit-line-clamp|-webkit-box-orient|display:\s*-webkit-box|line-clamp-\d+)'Length of output: 148
🏁 Script executed:
#!/bin/bash
# Re-run search disabling default ignores and restricting to code files
rg -nP -C2 -uuu -g '*.{js,jsx,ts,tsx}' '(-webkit-line-clamp|-webkit-box-orient|display\s*:\s*-webkit-box|line-clamp-\d+)' .Length of output: 7665
Remove all Tailwind line-clamp utilities across the repo
The search still surfaces dozens of className="… line-clamp-1/2/3/4" usages (e.g. in packages/ui/components, packages/features/**, apps/web/components, etc.), which rely on WebKit-only CSS under the hood. Replace each with the same style-based truncation you added in AppCard.tsx (using line-height, max-height, overflow-hidden, and text substring logic).
🤖 Prompt for AI Agents
In packages/features/apps/components/AppCard.tsx around lines 121 to 129, the
current approach uses inline style-based truncation for the description; the
reviewer asks to remove all Tailwind line-clamp utilities across the repo and
replace them with the same style+substring approach. Search the codebase for any
className containing "line-clamp-" and replace each occurrence by removing that
utility and adding equivalent inline styles (set lineHeight, maxHeight =
lineHeight * desiredLines, overflow: hidden, wordBreak: "break-word") on the
text container and, where applicable, implement the substring fallback (trim to
a maxDescriptionLength and append "..." when truncated) in the component render
logic so behavior matches the AppCard implementation.
|
@anikdhabal Hi, Coderabbit has suggested removing line-clamp across the whole repo and replacing it with my approach. Would you recommend that I go ahead with this change? |
There was a problem hiding this comment.
This does not work properly. There are cases where the text content is simply cutt off without showing "..."
See the video attached: https://cap.link/js0bvmqd57qqm4j
| wordBreak: "break-word", | ||
| }}> | ||
| {app.description} | ||
| {app.description ? (app.description.length > maxDescriptionLength ? `${app.description.substring(0, maxDescriptionLength).trim()}...` : app.description) : ""} |
There was a problem hiding this comment.
can we simplify this code here?
| searchText={searchText} | ||
| credentials={app.credentials} | ||
| userAdminTeams={userAdminTeams} | ||
| maxDescriptionLength={100} |
There was a problem hiding this comment.
there is no need to pass this as a prop. you can simply use the value inside the component
be32f7a to
dce462f
Compare
|
@kart1ka I’ve fixed the overflow issues now all apps truncate their descriptions properly, except for the Microsoft Teams app. That one is tricky because the title is so long, and its description only takes up two lines. Would it be okay if we also truncate the Microsoft Teams app title, so the description has enough space (like with the other apps)? |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
packages/ui/components/app-list-card/AppListCard.tsx (1)
97-100: Minor: avoid redundant breaking stylesYou already apply Tailwind’s break-words; pairing it with inline wordBreak later is redundant. Consider relying on the class only.
- className={classNames( - "whitespace-normal break-words", - classNameObject?.description - )} + className={classNames("break-words", classNameObject?.description)}
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
packages/features/apps/components/AppCard.tsx(4 hunks)packages/ui/components/app-list-card/AppListCard.tsx(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/features/apps/components/AppCard.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Always use
t()for text localization in frontend code; direct text embedding should trigger a warning
Files:
packages/ui/components/app-list-card/AppListCard.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/review.mdc)
Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js
.utc()in hot paths like loops
Files:
packages/ui/components/app-list-card/AppListCard.tsx
**/*.{ts,tsx,js,jsx}
⚙️ CodeRabbit configuration file
Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.
Files:
packages/ui/components/app-list-card/AppListCard.tsx
🧠 Learnings (1)
📚 Learning: 2025-08-12T08:53:11.848Z
Learnt from: supalarry
PR: calcom/cal.com#22976
File: packages/ui/components/app-list-card/AppListCard.tsx:63-67
Timestamp: 2025-08-12T08:53:11.848Z
Learning: In AppListCard.tsx, the className || classNameObject?.container fallback pattern is intentional to encourage users to migrate from the legacy className prop to the new granular classNameObject styling system, rather than merging both approaches.
Applied to files:
packages/ui/components/app-list-card/AppListCard.tsx
🔇 Additional comments (4)
packages/ui/components/app-list-card/AppListCard.tsx (4)
4-4: LGTM: memoization + sanitization imports are appropriatePulling in useMemo and stripMarkdown is the right call for perf and safety before truncation.
Also applies to: 8-8
64-69: Clean description pipeline looks goodMemoized strip + whitespace collapse is solid. No issues spotted.
111-111: Rendering sanitized text: good choiceUsing cleanDescription prevents Markdown artifacts from breaking truncation. Nice.
101-110: Use standard line-clamp with WebKit fallback; move truncation to a CSS utility & expose full text on hoverUnprefixed line-clamp is supported in modern browsers — keep the -webkit- props for older engines and provide a non‑WebKit fallback (gradient/“…” pseudo‑element or JS). Move truncation out of inline styles so you can use @supports(line-clamp: 3) and reuse across the repo.
- packages/ui/components/app-list-card/AppListCard.tsx (lines 101–110): remove textOverflow and wordBreak from the inline style; keep WebKit props + overflow/lineHeight/maxHeight; extract to a reusable CSS class that uses @supports(line-clamp: 3) { line-clamp: 3; } with a gradient or JS fallback.
- Expose full text on hover: add title={cleanDescription} to ListItemText.
Suggested minimal diff (still relevant):
style={{ - display: "-webkit-box", - WebkitLineClamp: 3, - WebkitBoxOrient: "vertical", - overflow: "hidden", - textOverflow: "ellipsis", - lineHeight: "1.4", - maxHeight: "4.2em", - wordBreak: "break-word", + display: "-webkit-box", + WebkitBoxOrient: "vertical", + WebkitLineClamp: 3, + overflow: "hidden", + lineHeight: "1.4", + maxHeight: "4.2em" }}>- <ListItemText + <ListItemText component="p" + title={cleanDescription}Repo-wide grep in the sandbox returned no results (rg reported "No files were searched"), so I could not verify other occurrences — re-run locally and paste output or run this:
rg -n --hidden --no-ignore -S '(WebkitLineClamp|webkit-line-clamp|line-clamp:\s*\d|display:\s*-webkit-box)' .
3833440 to
2306f90
Compare
|
@dhairyashiil could you please review this one :) |
|
@dhairyashiil could you please review it ser? The video after fix is already there. |
dhairyashiil
left a comment
There was a problem hiding this comment.
Hello @Anshumancanrock,
Could you please refer to #23660 and attach videos showing the different types of behaviors the maintainer and contributor discussed there? This would help me a lot in reviewing and approving the PR.
Also, please make sure to check and record the behavior across all sizes like mobile, iPad, laptop, etc.
Thanks a ton!
|
@dhairyashiil yeah ! Attached the video there and here too, showing fix in both web and mobile view. Recording.2025-12-11.195333.1.1.1.mp4 |
|
I fixed two issues:
I also added one extra improvement: truncating the title when it exceeds one line. Without this, some app discription could never truncate properly since we are using a line clamp-3 for the description. Hope this helps @dhairyashiil |
|
hii @dhairyashiil did you test this :) ? |
Devin AI is resolving merge conflictsThis PR has merge conflicts with the Devin will:
If you prefer to resolve conflicts manually, you can close the Devin session and handle it yourself. |
Devin AI is resolving merge conflictsThis PR has merge conflicts with the Devin will:
If you prefer to resolve conflicts manually, you can close the Devin session and handle it yourself. |
de00c1e to
f8d6003
Compare
There was a problem hiding this comment.
1 issue found across 1 file
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="apps/web/modules/apps/components/AppCard.tsx">
<violation number="1" location="apps/web/modules/apps/components/AppCard.tsx:114">
P2: App name truncate won’t take effect in flex row (missing min-w-0/width), so long titles will still overflow.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| </div> | ||
| <div className="flex items-center"> | ||
| <h3 className="text-emphasis font-medium"> | ||
| <h3 className="text-emphasis truncate font-medium" title={app.name}> |
There was a problem hiding this comment.
P2: App name truncate won’t take effect in flex row (missing min-w-0/width), so long titles will still overflow.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/modules/apps/components/AppCard.tsx, line 114:
<comment>App name truncate won’t take effect in flex row (missing min-w-0/width), so long titles will still overflow.</comment>
<file context>
@@ -109,7 +111,7 @@ export function AppCard({ app, credentials, searchText, userAdminTeams }: AppCar
</div>
<div className="flex items-center">
- <h3 className="text-emphasis font-medium">
+ <h3 className="text-emphasis truncate font-medium" title={app.name}>
{searchTextIndex != undefined && searchText ? (
<>
</file context>
Fix confidence (alpha): 8.5/10
| <h3 className="text-emphasis truncate font-medium" title={app.name}> | |
| <h3 className="text-emphasis truncate font-medium min-w-0" title={app.name}> |
|
Thanks for your work, fixed by this:- #26722 |
|
@anikdhabal the microsoft teams app card's description is still not truncating Since the description is set to truncate after 3 lines and this particular card’s description only has 2 lines so truncation doesn’t take effect. If we truncate the long title to a single line, the description would extend to 3 lines and truncate properly. Should I raise a PR to fix this specific case? |

What does this PR do?
This PR fixes a critical cross-browser compatibility issue where app card descriptions were not properly truncated due to reliance on webkit-only CSS properties. Long descriptions were overflowing and breaking the layout in many non-webkit browsers.
line-clampproperty does not truncate long texts properly in app card description #23659Key improvements:
Replaces webkit-only -webkit-line-clamp with standard CSS properties that work across all browsers
Implements consistent 3-line truncation with JavaScript fallback for precise character limits
Visual Demo (For contributors especially)
Image Demo (if applicable):
Before Fix:

After Fix:
Recording.2025-12-04.014106.1.mp4
Mandatory Tasks (DO NOT REMOVE)
How should this be tested?