@@ -481,7 +479,7 @@ const SidebarDraftRow = memo(function SidebarDraftRow(props: {
tabIndex={0}
data-testid="sidebar-draft-row"
className={cn(
- "group/sidebar-row relative w-full cursor-pointer overflow-hidden rounded-md text-left text-sidebar-foreground outline-none select-none",
+ "group/sidebar-row relative w-full cursor-pointer overflow-hidden rounded-[var(--control-radius)] text-left text-sidebar-foreground outline-none select-none",
props.isActive
? "bg-sidebar-row-active"
: "bg-amber-400/[0.04] hover:bg-amber-400/[0.08]",
@@ -1025,7 +1023,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
// a useful hierarchy nor a reliable hover cue. Status now lives in the row
// content; surface is reserved for interaction (hover, multi-select, route).
const rowSurfaceClassName = cn(
- "group/sidebar-row relative w-full cursor-pointer overflow-hidden rounded-md text-left outline-none select-none",
+ "group/sidebar-row relative w-full cursor-pointer overflow-hidden rounded-[var(--control-radius)] text-left outline-none select-none",
props.isActive
? "bg-sidebar-row-active text-sidebar-foreground"
: isSelected
@@ -1091,7 +1089,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
className={cn(
// Sidebar chrome follows the interface font; tabular digits keep the
// number from reflowing as PR states stream in.
- "shrink-0 text-xs tabular-nums hover:underline",
+ "shrink-0 cursor-pointer rounded-sm text-xs tabular-nums outline-none hover:underline focus-visible:ring-2 focus-visible:ring-ring",
variant === "slim" && variantAction === "unsettle"
? props.isActive
? "text-secondary-label"
@@ -1128,7 +1126,10 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
tabIndex={0}
data-testid="sidebar-row-slim"
aria-busy={isRegeneratingTitle || undefined}
- className={cn(rowSurfaceClassName, "flex h-9 items-center gap-2.5 px-2.5")}
+ className={cn(
+ rowSurfaceClassName,
+ "flex h-9 items-center gap-2.5 px-[var(--sidebar-row-content-inset)]",
+ )}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
onKeyDown={handleKeyDown}
@@ -1547,7 +1548,7 @@ const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props: {
onMouseMove={props.onHighlight}
onClick={props.onSelect}
className={cn(
- "flex h-9 w-full cursor-pointer items-center gap-2.5 rounded-md px-2.5 text-left text-sm outline-none",
+ "flex h-9 w-full cursor-pointer items-center gap-2.5 rounded-[var(--control-radius)] px-[var(--sidebar-row-content-inset)] text-left text-sm outline-none",
props.isHighlighted || props.isRouteActive
? "bg-sidebar-row-active text-sidebar-foreground"
: "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground",
@@ -2073,9 +2074,23 @@ export default function Sidebar() {
true,
Schema.Boolean,
);
+ // Shelf toggles insert or remove a whole block of rows at once. Letting the
+ // list's auto-animate FLIP every row below the shelf reads as jank for the
+ // snoozed shelf (mid-list) while the settled shelf (list tail) stays smooth,
+ // so both shelves toggle with the list animation suspended and behave
+ // identically; single-row transitions (settle, wake, reorder) keep animating.
+ const threadListAnimationRef = useRef
| null>(null);
+ const toggleShelfWithoutListAnimation = useCallback((toggle: () => void) => {
+ const controller = threadListAnimationRef.current;
+ controller?.disable();
+ toggle();
+ window.requestAnimationFrame(() => {
+ window.requestAnimationFrame(() => controller?.enable());
+ });
+ }, []);
const toggleSettledShelf = useCallback(
- () => setSettledShelfExpanded((value) => !value),
- [setSettledShelfExpanded],
+ () => toggleShelfWithoutListAnimation(() => setSettledShelfExpanded((value) => !value)),
+ [setSettledShelfExpanded, toggleShelfWithoutListAnimation],
);
const renderedSettledThreads = useMemo(() => {
if (settledShelfExpanded) return visibleSettledThreads;
@@ -2096,8 +2111,8 @@ export default function Sidebar() {
Schema.Boolean,
);
const toggleSnoozedShelf = useCallback(
- () => setSnoozedShelfExpanded((value) => !value),
- [setSnoozedShelfExpanded],
+ () => toggleShelfWithoutListAnimation(() => setSnoozedShelfExpanded((value) => !value)),
+ [setSnoozedShelfExpanded, toggleShelfWithoutListAnimation],
);
const visibleSnoozedThreads = useMemo(() => {
if (snoozedShelfExpanded) return snoozedThreads;
@@ -3180,7 +3195,7 @@ export default function Sidebar() {
const attachListAutoAnimateRef = useCallback((node: HTMLUListElement | null) => {
if (!node) return;
- autoAnimate(node, { duration: 150, easing: "ease-out" });
+ threadListAnimationRef.current = autoAnimate(node, { duration: 150, easing: "ease-out" });
}, []);
// New thread defaults to the project you're in (active thread's project,
@@ -3664,7 +3679,7 @@ export default function Sidebar() {
onClick={toggleSnoozedShelf}
aria-expanded={snoozedShelfExpanded}
data-testid="sidebar-snoozed-shelf-toggle"
- className="mb-1 mt-3 flex w-full cursor-pointer items-center gap-2 px-2.5 text-left"
+ className="mb-1 mt-3 flex w-full cursor-pointer items-center gap-2 rounded-md px-[var(--sidebar-row-content-inset)] text-left outline-none hover:bg-sidebar-row-hover focus-visible:ring-2 focus-visible:ring-ring"
>
{snoozedShelfExpanded
@@ -3698,7 +3713,7 @@ export default function Sidebar() {
onClick={toggleSettledShelf}
aria-expanded={settledShelfExpanded}
data-testid="sidebar-settled-shelf-toggle"
- className="mb-1 mt-3 flex w-full cursor-pointer items-center gap-2 px-2.5 text-left"
+ className="mb-1 mt-3 flex w-full cursor-pointer items-center gap-2 rounded-md px-[var(--sidebar-row-content-inset)] text-left outline-none hover:bg-sidebar-row-hover focus-visible:ring-2 focus-visible:ring-ring"
>
{settledShelfExpanded
@@ -3727,7 +3742,7 @@ export default function Sidebar() {
Show {Math.min(hiddenSettledCount, SETTLED_TAIL_PAGE_COUNT)} more
@@ -3751,7 +3766,7 @@ export default function Sidebar() {
Add project
diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx
index c59f682c415e..7c4e0a118629 100644
--- a/apps/web/src/components/ThreadTerminalDrawer.tsx
+++ b/apps/web/src/components/ThreadTerminalDrawer.tsx
@@ -908,7 +908,14 @@ function TerminalActionButton({ label, className, onClick, children }: TerminalA
}
+ render={
+
+ }
>
{children}
@@ -1281,7 +1288,7 @@ export default function ThreadTerminalDrawer({
No terminal sessions for this thread yet.
{newTerminalActionLabel}
@@ -1318,7 +1325,7 @@ export default function ThreadTerminalDrawer({
onActiveTerminalChange(terminalId)}
>
@@ -1553,7 +1560,7 @@ export default function ThreadTerminalDrawer({
render={
onCloseTerminal(terminalId)}
aria-label={closeTerminalLabel}
/>
diff --git a/apps/web/src/components/chat/ChangedFilesTree.tsx b/apps/web/src/components/chat/ChangedFilesTree.tsx
index d29d8b7f2f44..e0aec99d813f 100644
--- a/apps/web/src/components/chat/ChangedFilesTree.tsx
+++ b/apps/web/src/components/chat/ChangedFilesTree.tsx
@@ -73,7 +73,7 @@ export const ChangedFilesCard = memo(function ChangedFilesCard(props: {
type="button"
aria-expanded={expanded}
data-scroll-anchor-ignore
- className="group flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden rounded-lg px-1 py-1.5 text-left transition-colors hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
+ className="group flex min-w-0 flex-1 cursor-pointer items-center gap-1.5 overflow-hidden rounded-lg px-1 py-1.5 text-left transition-colors hover:bg-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"
onClick={() => onExpandedChange(!expanded)}
>
onOpenTurnDiff(turnId, file.path)}
>
onExpandedChange(true)}
>
Show all {files.length} files
@@ -254,7 +254,7 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {
toggleDirectory(node.path)}
>
@@ -292,7 +292,7 @@ export const ChangedFilesTree = memo(function ChangedFilesTree(props: {
onOpenTurnDiff(turnId, node.path)}
>
diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx
index e918e7758688..e6f442de403f 100644
--- a/apps/web/src/components/chat/ChatComposer.tsx
+++ b/apps/web/src/components/chat/ChatComposer.tsx
@@ -2766,7 +2766,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps)
event.preventDefault()}
diff --git a/apps/web/src/components/chat/ChatHeader.tsx b/apps/web/src/components/chat/ChatHeader.tsx
index db13419962f0..26c7fc14d105 100644
--- a/apps/web/src/components/chat/ChatHeader.tsx
+++ b/apps/web/src/components/chat/ChatHeader.tsx
@@ -232,7 +232,7 @@ export const ChatHeader = memo(function ChatHeader({
type="button"
aria-label={`New thread in ${activeProjectName}`}
onClick={onNewThreadInProject}
- className="inline-flex min-w-0 cursor-pointer items-center gap-1.5 rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
+ className="inline-flex min-w-0 cursor-pointer items-center gap-1.5 rounded-sm text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"
/>
}
>
@@ -274,7 +274,7 @@ export const ChatHeader = memo(function ChatHeader({
aria-label={`Thread actions for ${activeThreadTitle}`}
aria-haspopup="menu"
onClick={openMenuFromTitle}
- className="group/thread-title inline-flex min-w-0 max-w-full cursor-pointer items-center gap-1 rounded-sm text-left focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
+ className="group/thread-title inline-flex min-w-0 max-w-full cursor-pointer items-center gap-1 rounded-sm text-left focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background"
/>
}
>
diff --git a/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx b/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx
index dd966bf57952..d947ad2618e5 100644
--- a/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx
+++ b/apps/web/src/components/chat/ComposerPendingApprovalPanel.tsx
@@ -26,7 +26,9 @@ export const ComposerPendingApprovalPanel = memo(function ComposerPendingApprova
return (
-
PENDING APPROVAL
+
+ PENDING APPROVAL
+
{approvalSummary}
{pendingCount > 1 ? (
1/{pendingCount}
diff --git a/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx b/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx
index ceac45c9411d..380ff11ae735 100644
--- a/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx
+++ b/apps/web/src/components/chat/ComposerPendingUserInputPanel.tsx
@@ -153,7 +153,7 @@ const ComposerPendingUserInputCard = memo(function ComposerPendingUserInputCard(
const customAnswerActive = progress.customAnswer.trim().length > 0;
return (
-
+
{activeQuestion.header}
@@ -182,7 +182,7 @@ const ComposerPendingUserInputCard = memo(function ComposerPendingUserInputCard(
isSelected
? "border-primary/30 bg-primary/8 text-foreground"
: "border-transparent bg-muted/22 text-foreground/85 hover:border-border/45 hover:bg-muted/34",
- isResponding && "opacity-50 cursor-not-allowed",
+ isResponding && "opacity-64 cursor-not-allowed",
!isResponding && "cursor-pointer",
);
const content = (
diff --git a/apps/web/src/components/chat/ComposerPreviewAnnotationCards.tsx b/apps/web/src/components/chat/ComposerPreviewAnnotationCards.tsx
index 5e9e43dcf21e..59db45e4bf7f 100644
--- a/apps/web/src/components/chat/ComposerPreviewAnnotationCards.tsx
+++ b/apps/web/src/components/chat/ComposerPreviewAnnotationCards.tsx
@@ -63,7 +63,7 @@ export function ComposerPreviewAnnotationCards({
/>
) : (
-
+
)}
@@ -131,7 +131,7 @@ export function ComposerPreviewAnnotationCards({
onRemove(annotation.id)}
>
diff --git a/apps/web/src/components/chat/ComposerPrimaryActions.test.ts b/apps/web/src/components/chat/ComposerPrimaryActions.test.ts
index 3dbcd39e9d13..d65f64f9292c 100644
--- a/apps/web/src/components/chat/ComposerPrimaryActions.test.ts
+++ b/apps/web/src/components/chat/ComposerPrimaryActions.test.ts
@@ -192,7 +192,7 @@ describe("ComposerPrimaryActions", () => {
it("matches the small pending action size without changing the standalone size", () => {
expect(renderPendingActions(true)).toContain("size-8 sm:size-7");
- expect(renderStandaloneStop()).toContain("size-8 sm:h-8 sm:w-8");
+ expect(renderStandaloneStop()).toContain("size-9 sm:size-8");
expect(renderStandaloneStop()).not.toContain("sm:size-7");
});
diff --git a/apps/web/src/components/chat/ComposerPrimaryActions.tsx b/apps/web/src/components/chat/ComposerPrimaryActions.tsx
index d8626496ae7d..6cda4cbc1ec4 100644
--- a/apps/web/src/components/chat/ComposerPrimaryActions.tsx
+++ b/apps/web/src/components/chat/ComposerPrimaryActions.tsx
@@ -86,7 +86,7 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({
type="button"
className={cn(
"flex cursor-pointer items-center justify-center rounded-full bg-destructive/90 text-white shadow-xs shadow-destructive/24 inset-shadow-[0_1px_--theme(--color-white/16%)] transition-all duration-150 hover:bg-destructive hover:scale-105 active:inset-shadow-[0_1px_--theme(--color-black/8%)] active:shadow-none",
- insidePendingAction ? "size-8 sm:size-7" : "size-8 sm:h-8 sm:w-8",
+ insidePendingAction ? "size-8 sm:size-7" : "size-9 sm:size-8",
)}
{...pointerFocusProps}
onClick={onInterrupt}
diff --git a/apps/web/src/components/chat/ComposerStashMenu.tsx b/apps/web/src/components/chat/ComposerStashMenu.tsx
index 9e9238515332..84ef77cd87da 100644
--- a/apps/web/src/components/chat/ComposerStashMenu.tsx
+++ b/apps/web/src/components/chat/ComposerStashMenu.tsx
@@ -156,7 +156,7 @@ export const ComposerStashMenu = memo(function ComposerStashMenu(props: {
{
event.stopPropagation();
diff --git a/apps/web/src/components/chat/DraftHeroHeadline.tsx b/apps/web/src/components/chat/DraftHeroHeadline.tsx
index 0fb187a54621..0c01cce6dbd4 100644
--- a/apps/web/src/components/chat/DraftHeroHeadline.tsx
+++ b/apps/web/src/components/chat/DraftHeroHeadline.tsx
@@ -101,7 +101,7 @@ export function DraftHeroHeadline({
{activeProjectDisplayName ?? "Choose a project"}
diff --git a/apps/web/src/components/chat/ExpandedImageDialog.tsx b/apps/web/src/components/chat/ExpandedImageDialog.tsx
index fd14c68b0c4d..2e04d5c3c8d4 100644
--- a/apps/web/src/components/chat/ExpandedImageDialog.tsx
+++ b/apps/web/src/components/chat/ExpandedImageDialog.tsx
@@ -76,7 +76,7 @@ export const ExpandedImageDialog = memo(function ExpandedImageDialog({
type="button"
size="icon-xs"
variant="ghost"
- className="absolute right-2 top-2"
+ className="absolute right-2 top-2 text-white/90 hover:bg-white/10 hover:text-white"
onClick={onClose}
aria-label="Close image preview"
>
diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx
index c6e28dcef5c5..b6880c712b94 100644
--- a/apps/web/src/components/chat/MessagesTimeline.tsx
+++ b/apps/web/src/components/chat/MessagesTimeline.tsx
@@ -178,7 +178,7 @@ function TimelineLoadEarlierHeader({
type="button"
onClick={onLoadEarlier}
disabled={loading}
- className="w-full py-1.5 text-xs text-muted-foreground/60 hover:text-foreground disabled:cursor-default"
+ className="w-full cursor-pointer py-1.5 text-xs text-muted-foreground/60 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70 disabled:cursor-default"
>
{loading ? "Loading earlier turns…" : "Load earlier turns"}
@@ -891,7 +891,7 @@ function TimelineMinimap({
transform: `translateY(${activeTooltipTranslate})`,
}}
>
-
+
{activeItem.userText ?? "User message"}
@@ -993,7 +993,7 @@ function UserTimelineRow({ row }: { row: Extract {
const preview = buildExpandedImagePreview(regularImages, image.id);
@@ -1210,11 +1210,13 @@ const TurnPlanTimelineRow = memo(function TurnPlanTimelineRow({
setExpanded((value) => !value)}
>
-
+
+
+
{steps.length > 1 ? (
{steps.map((step) => (
@@ -1543,7 +1545,7 @@ function UserMessagePreviewAnnotationCard(props: {
{props.image?.previewUrl ? (
{
if (!props.image) return;
@@ -1566,7 +1568,7 @@ function UserMessagePreviewAnnotationCard(props: {
) : null}
@@ -2192,7 +2194,7 @@ const AgentSpawnCtaRow = memo(function AgentSpawnCtaRow(props: { workEntry: Time
@@ -2316,7 +2318,7 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: {
{canExpand ? (
@@ -781,7 +781,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: {
onLayout={updateModelListScrollFades}
onScroll={updateModelListScrollFades}
className={cn(
- "model-picker-list scrollbar-gutter-stable h-full overflow-x-hidden overscroll-y-contain py-1.5 [--fade-size:1.5rem]",
+ "model-picker-list scrollbar-gutter-stable h-full overflow-x-hidden overscroll-y-contain py-1.5",
showTopScrollFade && "model-picker-list-scroll-fade-top",
showBottomScrollFade && "model-picker-list-scroll-fade-bottom",
)}
diff --git a/apps/web/src/components/chat/ModelPickerSidebar.tsx b/apps/web/src/components/chat/ModelPickerSidebar.tsx
index 05b44dcb7327..0166edd78d42 100644
--- a/apps/web/src/components/chat/ModelPickerSidebar.tsx
+++ b/apps/web/src/components/chat/ModelPickerSidebar.tsx
@@ -158,7 +158,7 @@ export const ModelPickerSidebar = memo(function ModelPickerSidebar(props: {
!isDisabled && handleSelect(entry.instanceId)}
diff --git a/apps/web/src/components/chat/ProviderModelPicker.tsx b/apps/web/src/components/chat/ProviderModelPicker.tsx
index a9b3a398115b..e91e5b4ac97a 100644
--- a/apps/web/src/components/chat/ProviderModelPicker.tsx
+++ b/apps/web/src/components/chat/ProviderModelPicker.tsx
@@ -176,7 +176,7 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: {
/>
) : null}
- }>
+ }>
{triggerTitle}
{triggerLabel}
diff --git a/apps/web/src/components/chat/ProviderStatusBanner.tsx b/apps/web/src/components/chat/ProviderStatusBanner.tsx
index f82c17b13ddd..0717ba539ca6 100644
--- a/apps/web/src/components/chat/ProviderStatusBanner.tsx
+++ b/apps/web/src/components/chat/ProviderStatusBanner.tsx
@@ -69,7 +69,7 @@ export const ProviderStatusBanner = memo(function ProviderStatusBanner({
diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx
index 8462757700e7..7548c9276d30 100644
--- a/apps/web/src/components/chat/TraitsPicker.tsx
+++ b/apps/web/src/components/chat/TraitsPicker.tsx
@@ -52,7 +52,7 @@ function DefaultBadge() {
return (
Default
@@ -305,7 +305,7 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
{index > 0 ?
: null}
-
+
{descriptor.label}
{ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id ? (
diff --git a/apps/web/src/components/clerk/MobileClientsUserProfilePage.tsx b/apps/web/src/components/clerk/MobileClientsUserProfilePage.tsx
index 22449c336742..a4651b14056b 100644
--- a/apps/web/src/components/clerk/MobileClientsUserProfilePage.tsx
+++ b/apps/web/src/components/clerk/MobileClientsUserProfilePage.tsx
@@ -1,7 +1,8 @@
import type { RelayClientDeviceRecord } from "@t3tools/contracts/relay";
-import { SmartphoneIcon } from "lucide-react";
+import { RefreshCwIcon, SmartphoneIcon } from "lucide-react";
import { useManagedRelayDevices } from "../../cloud/managedRelayState";
+import { cn } from "../../lib/utils";
import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty";
@@ -11,11 +12,6 @@ import {
mobileClientPlatformLabel,
mobileClientUpdatedAtLabel,
} from "./MobileClientsUserProfilePage.logic";
-import {
- ClerkUserProfilePage,
- ClerkUserProfileRefreshButton,
- ClerkUserProfileRow,
-} from "./ClerkUserProfilePage";
const MOBILE_CLIENT_SKELETON_ROWS = ["primary", "secondary"] as const;
@@ -35,47 +31,53 @@ function MobileClientStatusBadge({
function MobileClientRow({ device }: { readonly device: RelayClientDeviceRecord }) {
return (
-
}>
-
-
-
- {device.label}
-
-
- {mobileClientPlatformLabel(device)}
+
+
+
+
+
+
+
+
+
{device.label}
+
{mobileClientPlatformLabel(device)}
+
+
+ {mobileClientUpdatedAtLabel(device.updatedAt)}
+
+
+
+
+
+
+
+ {mobileClientNotificationDetail(device)}
-
- {mobileClientUpdatedAtLabel(device.updatedAt)}
-
-
-
-
-
-
- {mobileClientNotificationDetail(device)}
-
-
+
);
}
function MobileClientsSkeleton() {
return (
-
+
{MOBILE_CLIENT_SKELETON_ROWS.map((row) => (
-
+
-
+
@@ -87,13 +89,13 @@ function MobileClientsSkeleton() {
function EmptyMobileClients() {
return (
-
-
+
+
- No mobile clients
-
+ No mobile clients
+
Sign in to T3 Code on your iPhone to register it for push notifications and Live
Activities.
@@ -110,20 +112,29 @@ export function MobileClientsUserProfilePage() {
const hasErrorWithoutData = devicesState.error !== null && devicesState.data === null;
return (
-
+