diff --git a/.claude/rules/sim-components.md b/.claude/rules/sim-components.md index 21a68a051e2..6c6179b2ef2 100644 --- a/.claude/rules/sim-components.md +++ b/.claude/rules/sim-components.md @@ -33,7 +33,7 @@ export function Component({ requiredProp, optionalProp = false }: ComponentProps When rendering or sorting a list of rows against a lookup collection (members, folders, tags), keep the per-row work O(1): - **Precompute a lookup `Map` once**, never `array.find(...)` per row. Build `const byId = useMemo(() => { const m = new Map(); for (const x of items ?? []) m.set(x.id, x); return m }, [items])` and read `byId.get(id)` in the sort comparator, `.map(...)`, and cell builders. A `.find` inside a sort comparator is O(n²·log n) — the worst offender. Depend memos on the derived `Map`, not the raw array. -- **Sort a copy with `[...array].sort(cmp)`**, never `toSorted` in client code — see `sim-react-performance.md` → "Never mutate a shared array in place". +- **Sort a copy with `[...array].sort(cmp)`**, never `toSorted` (`check:utils` bans it repo-wide) — see `sim-react-performance.md` → "Never mutate a shared array in place". - **Partition in a single pass** — when splitting one collection into several (`fileIds`/`folderIds`), do one `for…of` pushing into each bucket and return `{ a, b }` from a single `useMemo`, not two memos that each `map→filter→map` the same source twice. ## react-doctor (`bunx react-doctor`) — apply the wins, skip the false positives @@ -41,7 +41,7 @@ When rendering or sorting a list of rows against a lookup collection (members, f react-doctor diagnostics are hypotheses, not verdicts — confirm against the code before acting, and preserve behavior. Known repo-specific false positives to NOT "fix": - `no-barrel-import` — barrel imports are the repo convention (see sim-imports.md, "Barrel Exports"). Keep them. -- `js-tosorted-immutable` — won't-fix in `'use client'` code (see "List-render performance" above); apply it only in server-only modules. +- `js-tosorted-immutable` — won't-fix anywhere; `check:utils` bans the ES2023 array methods repo-wide. - `rerender-state-only-in-handlers` / "state set but never rendered" — a false positive when the `useState` is consumed by a `useEffect`/`useLayoutEffect` dependency (the effect must re-run on change). Only convert to a ref when nothing reads the value reactively. - `no-render-in-render` — a helper *called inline* (`{renderRow()}`) is reconciled by position and does **not** remount, so extracting it to a component is usually pure churn and can regress behavior (prop-drilling many closures, focus/scroll loss on the inner ``). Apply it only when the helper is genuinely a *component defined during render*, or when the move is mechanical (a stateless, ref-free helper whose closures become a small, explicit prop set). - `async-await-in-loop` on an upload/progress loop where sequential execution is intentional (per-item progress, server backpressure) — leave it. diff --git a/.claude/rules/sim-react-performance.md b/.claude/rules/sim-react-performance.md index a9bfeaa7f63..668e667a5d8 100644 --- a/.claude/rules/sim-react-performance.md +++ b/.claude/rules/sim-react-performance.md @@ -77,7 +77,7 @@ return items.sort(compare) return [...items].sort(compare) ``` -**Do NOT reach for `toSorted()` / `toReversed()` / `with()` / `toSpliced()` on client render paths.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is the correct default everywhere client code runs. Only consider the immutable methods in Node-only code (server routes, scripts) on Node ≥20, where the runtime is known. +**Do NOT use `toSorted()` / `toReversed()` / `with()` / `toSpliced()`.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is used everywhere: whether a module reaches the browser is not visible from its path. `check:utils` flags `toSorted`/`toReversed`/`toSpliced` repo-wide and `with` when called with a numeric index (an identifier index is indistinguishable from OpenTelemetry's `context.with`). ## Run independent awaits in parallel diff --git a/.cursor/rules/sim-components.mdc b/.cursor/rules/sim-components.mdc index dc49e5f8167..d592832a0c5 100644 --- a/.cursor/rules/sim-components.mdc +++ b/.cursor/rules/sim-components.mdc @@ -34,7 +34,7 @@ export function Component({ requiredProp, optionalProp = false }: ComponentProps When rendering or sorting a list of rows against a lookup collection (members, folders, tags), keep the per-row work O(1): - **Precompute a lookup `Map` once**, never `array.find(...)` per row. Build `const byId = useMemo(() => { const m = new Map(); for (const x of items ?? []) m.set(x.id, x); return m }, [items])` and read `byId.get(id)` in the sort comparator, `.map(...)`, and cell builders. A `.find` inside a sort comparator is O(n²·log n) — the worst offender. Depend memos on the derived `Map`, not the raw array. -- **Sort a copy with `[...array].sort(cmp)`**, never `toSorted` in client code — see `sim-react-performance.md` → "Never mutate a shared array in place". +- **Sort a copy with `[...array].sort(cmp)`**, never `toSorted` (`check:utils` bans it repo-wide) — see `sim-react-performance.md` → "Never mutate a shared array in place". - **Partition in a single pass** — when splitting one collection into several (`fileIds`/`folderIds`), do one `for…of` pushing into each bucket and return `{ a, b }` from a single `useMemo`, not two memos that each `map→filter→map` the same source twice. ## react-doctor (`bunx react-doctor`) — apply the wins, skip the false positives @@ -42,7 +42,7 @@ When rendering or sorting a list of rows against a lookup collection (members, f react-doctor diagnostics are hypotheses, not verdicts — confirm against the code before acting, and preserve behavior. Known repo-specific false positives to NOT "fix": - `no-barrel-import` — barrel imports are the repo convention (see sim-imports.md, "Barrel Exports"). Keep them. -- `js-tosorted-immutable` — won't-fix in `'use client'` code (see "List-render performance" above); apply it only in server-only modules. +- `js-tosorted-immutable` — won't-fix anywhere; `check:utils` bans the ES2023 array methods repo-wide. - `rerender-state-only-in-handlers` / "state set but never rendered" — a false positive when the `useState` is consumed by a `useEffect`/`useLayoutEffect` dependency (the effect must re-run on change). Only convert to a ref when nothing reads the value reactively. - `no-render-in-render` — a helper *called inline* (`{renderRow()}`) is reconciled by position and does **not** remount, so extracting it to a component is usually pure churn and can regress behavior (prop-drilling many closures, focus/scroll loss on the inner ``). Apply it only when the helper is genuinely a *component defined during render*, or when the move is mechanical (a stateless, ref-free helper whose closures become a small, explicit prop set). - `async-await-in-loop` on an upload/progress loop where sequential execution is intentional (per-item progress, server backpressure) — leave it. diff --git a/.cursor/rules/sim-react-performance.mdc b/.cursor/rules/sim-react-performance.mdc index f5f523f7e57..cf526216c5f 100644 --- a/.cursor/rules/sim-react-performance.mdc +++ b/.cursor/rules/sim-react-performance.mdc @@ -80,7 +80,7 @@ return items.sort(compare) return [...items].sort(compare) ``` -**Do NOT reach for `toSorted()` / `toReversed()` / `with()` / `toSpliced()` on client render paths.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is the correct default everywhere client code runs. Only consider the immutable methods in Node-only code (server routes, scripts) on Node ≥20, where the runtime is known. +**Do NOT use `toSorted()` / `toReversed()` / `with()` / `toSpliced()`.** They are ES2023 *runtime* methods — and a tsconfig `"lib": ["ES2023"]` only makes them **type-check**, it does not make them **run**. Next/SWC compiles syntax but does **not** polyfill prototype methods, and the default browserslist still includes browsers without them (`toSorted` landed in Safari 16 / iOS 16, so any device capped at iOS 15 throws `TypeError: x.toSorted is not a function` and crashes the page). The perf difference vs `[...arr].sort()` is negligible (both allocate one array), so the copy-then-sort form is used everywhere: whether a module reaches the browser is not visible from its path. `check:utils` flags `toSorted`/`toReversed`/`toSpliced` repo-wide and `with` when called with a numeric index (an identifier index is indistinguishable from OpenTelemetry's `context.with`). ## Run independent awaits in parallel diff --git a/CLAUDE.md b/CLAUDE.md index 1b8c8272095..815c747b91c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ This file (also `AGENTS.md`) holds the repo-wide rules. Area detail lives in `.c - **Logging**: `createLogger` from `@sim/logger`; `logger.info` / `logger.warn` / `logger.error`, never `console.log`. Inside `withRouteHandler` the logger already carries the request ID — no manual `withMetadata({ requestId })`. - **Comments**: TSDoc for documentation. An inline `//` only for a terse, non-obvious why, or for a script-enforced `// : ` annotation (`boundary-raw-fetch`, `double-cast-allowed`, `boundary-raw-json`, `untyped-response`, `rq-lint-allow`, `client-boundary-allow`, …). No `====` separators. - **ID generation**: `generateId()` (UUID v4, the default) or `generateShortId(size?)` (URL-safe, 21 chars by default) from `@sim/utils/id` — never `crypto.randomUUID()`, `nanoid`, or `uuid`. Both use `crypto.getRandomValues()`, so they also work in non-secure (HTTP) browsers. -- **Common utilities**: use the shared helpers from `@sim/utils` instead of inline implementations: +- **Common utilities**: use the shared helpers from `@sim/utils` instead of inline implementations (`check:utils` bans most of the inline forms below): - `sleep(ms)` from `@sim/utils/helpers` — never `new Promise(resolve => setTimeout(resolve, ms))` - `toError(e)` from `@sim/utils/errors` — normalize caught values to `Error`; never `e instanceof Error ? e : new Error(String(e))` - `getErrorMessage(e, fallback?)` from `@sim/utils/errors` — never `e instanceof Error ? e.message : 'fallback'` @@ -89,7 +89,7 @@ The `'use client'` server boundary, the app/worker runtime env split, and featur - **Imports**: absolute (`@/...`) only, never relative. A folder with 3+ exports gets an `index.ts` barrel; never re-export from a non-barrel file. `import type` for type-only imports. Order and lazy-loading through barrels: `.claude/rules/sim-imports.md`. - **TypeScript**: no `any` and no non-null `!` (use precise types or `unknown` with guards; `check:explicit-any` ratchets both); no export nothing imports (`check:unused-exports`); a props interface for every component; `as const` for constant objects/arrays; explicit ref types (`useRef(null)`). - **Unused bindings** fail lint (biome `noUnusedVariables`, `noUnusedFunctionParameters`): delete the dead variable, import, or parameter and update callers; write `catch {}` when the error is unused. Prefix `_` only for a parameter that must hold its position because a later one is used. `const { a, ...rest } = obj` to omit keys is allowed. The rules carry no autofix, so `bun run lint` will not rename anything for you. -- **Components**: `'use client'` only for hooks or browser APIs. Structure order, extraction thresholds, and list-render rules: `.claude/rules/sim-components.md`. Render-performance idioms (lazy-init refs, hoisting, `Map` pre-indexing, `[...arr].sort()` never `toSorted()` on client paths): `.claude/rules/sim-react-performance.md`. For effect/state/memo/callback anti-patterns use the `/you-might-not-need-*` skills and verify against the running UI. +- **Components**: `'use client'` only for hooks or browser APIs. Structure order, extraction thresholds, and list-render rules: `.claude/rules/sim-components.md`. Render-performance idioms (lazy-init refs, hoisting, `Map` pre-indexing, `[...arr].sort()`, never `toSorted()`): `.claude/rules/sim-react-performance.md`. For effect/state/memo/callback anti-patterns use the `/you-might-not-need-*` skills and verify against the running UI. - **State ownership**: React Query owns server data — never `useState` + `fetch`; shareable client view-state (tabs, filters, search, pagination, selected id) lives in the URL via `nuqs`; Zustand owns global client state; `useState` owns UI-only state. Hooks: `.claude/rules/sim-hooks.md`. Stores (`devtools`, `persist` only with an explicit `partialize` whitelist, workflow value invariants): `.claude/rules/sim-stores.md`. URL state: `.claude/rules/sim-url-state.md`. - **Utils**: inline a helper with one consumer; create `utils.ts` when 2+ files share it — in `lib/` (app-wide) or `feature/utils/` (feature-scoped). Check `lib/` before writing a new one. - **Lists and menus** mirror the order the user already reads elsewhere (toolbar, settings nav), encoded in one exported order constant (resource menus share `RESOURCE_MENU_ORDER`, a product order that does not mirror the sidebar); a separator marks only a change in what the action acts on (typically one, before the destructive action): `.claude/rules/sim-list-ordering.md`. diff --git a/apps/desktop/src/main/local-filesystem.ts b/apps/desktop/src/main/local-filesystem.ts index 46a6c30b9fb..bc3642c3ca7 100644 --- a/apps/desktop/src/main/local-filesystem.ts +++ b/apps/desktop/src/main/local-filesystem.ts @@ -19,7 +19,7 @@ import { } from '@sim/desktop-bridge/local-filesystem-limits' import { generateId } from '@sim/utils/id' import { isRecordLike } from '@sim/utils/object' -import { escapeRegExp } from '@sim/utils/string' +import { escapeRegExp, truncate } from '@sim/utils/string' import { app, dialog, shell } from 'electron' import micromatch from 'micromatch' import safeRegex from 'safe-regex2' @@ -1236,8 +1236,7 @@ export class LocalFilesystemService { matches.push({ uri: resultUri, line: request.lineNumbers === false ? 0 : contextIndex + 1, - text: - line.length > MAX_GREP_LINE_LENGTH ? `${line.slice(0, MAX_GREP_LINE_LENGTH)}…` : line, + text: truncate(line, MAX_GREP_LINE_LENGTH, '…'), }) if (matches.length >= maxResults) { truncated = true diff --git a/apps/docs/app/api/search/route.ts b/apps/docs/app/api/search/route.ts index 4bea9ead620..30915b87529 100644 --- a/apps/docs/app/api/search/route.ts +++ b/apps/docs/app/api/search/route.ts @@ -1,8 +1,11 @@ +import { createLogger } from '@sim/logger' import { sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { db, docsEmbeddings } from '@/lib/db' import { generateSearchEmbedding } from '@/lib/embeddings' +const logger = createLogger('DocsSearchAPI') + export const runtime = 'nodejs' export const revalidate = 0 @@ -196,7 +199,7 @@ export async function GET(request: NextRequest) { return NextResponse.json(searchResults) } catch (error) { - console.error('Semantic search error:', error) + logger.error('Semantic search error:', error) return NextResponse.json([]) } diff --git a/apps/docs/app/llms-full.txt/route.ts b/apps/docs/app/llms-full.txt/route.ts index 57bcd9045c5..244c7a62dc8 100644 --- a/apps/docs/app/llms-full.txt/route.ts +++ b/apps/docs/app/llms-full.txt/route.ts @@ -1,6 +1,9 @@ +import { createLogger } from '@sim/logger' import { getLLMText } from '@/lib/llms' import { source } from '@/lib/source' +const logger = createLogger('DocsLlmsFullText') + export const revalidate = false export async function GET() { @@ -18,7 +21,7 @@ export async function GET() { }, }) } catch (error) { - console.error('Error generating LLM full text:', error) + logger.error('Error generating LLM full text:', error) return new Response('Error generating full documentation text', { status: 500 }) } } diff --git a/apps/docs/app/llms.txt/route.ts b/apps/docs/app/llms.txt/route.ts index 0706a38595a..f43aeba1876 100644 --- a/apps/docs/app/llms.txt/route.ts +++ b/apps/docs/app/llms.txt/route.ts @@ -1,6 +1,9 @@ +import { createLogger } from '@sim/logger' import { source } from '@/lib/source' import { DOCS_BASE_URL } from '@/lib/urls' +const logger = createLogger('DocsLlmsManifest') + export const revalidate = false export async function GET() { @@ -70,7 +73,7 @@ See: https://llmstxt.org for specification` }, }) } catch (error) { - console.error('Error generating LLM manifest:', error) + logger.error('Error generating LLM manifest:', error) return new Response('Error generating documentation manifest', { status: 500 }) } } diff --git a/apps/docs/components/ui/video-placeholder.tsx b/apps/docs/components/ui/video-placeholder.tsx index e836b418b52..3490e13ca0f 100644 --- a/apps/docs/components/ui/video-placeholder.tsx +++ b/apps/docs/components/ui/video-placeholder.tsx @@ -113,7 +113,7 @@ export function VideoPlaceholder({ pendingSeek.current = null } }} - className='h-full w-full border-0' + className='size-full border-0' > diff --git a/apps/docs/components/workflow-preview/docs-container-node.tsx b/apps/docs/components/workflow-preview/docs-container-node.tsx index 20fbb118870..a81da4a995f 100644 --- a/apps/docs/components/workflow-preview/docs-container-node.tsx +++ b/apps/docs/components/workflow-preview/docs-container-node.tsx @@ -39,7 +39,7 @@ export const DocsContainerNode = memo(function DocsContainerNode({ } return ( -
+
@@ -329,7 +329,7 @@ export function WorkflowPreview({ className='fixed inset-0 m-auto h-[86dvh] max-h-none w-[92vw] max-w-none overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--bg)] p-0 text-[var(--text-body)] backdrop:bg-black/70 backdrop:backdrop-blur-sm' > {expanded && ( -
+
{workflow.name} diff --git a/apps/docs/package.json b/apps/docs/package.json index ac0e88dcf59..fd4a4d1816a 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -20,6 +20,7 @@ "dependencies": { "@sim/db": "workspace:*", "@sim/emcn": "workspace:*", + "@sim/logger": "workspace:*", "@sim/utils": "workspace:*", "@sim/workflow-renderer": "workspace:*", "@xyflow/react": "12.11.3", diff --git a/apps/sim/app/(interfaces)/chat/components/input/input.tsx b/apps/sim/app/(interfaces)/chat/components/input/input.tsx index b2c6c8b48f1..9bd9c9b8b60 100644 --- a/apps/sim/app/(interfaces)/chat/components/input/input.tsx +++ b/apps/sim/app/(interfaces)/chat/components/input/input.tsx @@ -177,10 +177,10 @@ export const ChatInput: React.FC<{ {file.name} ) : ( -
+
{file.name.split('.').pop()} diff --git a/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.tsx b/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.tsx index c02f862bce5..8c828e34d86 100644 --- a/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.tsx +++ b/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-chat-loop.tsx @@ -179,7 +179,7 @@ export function HeroChatLoop({ return (
diff --git a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.tsx b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.tsx index 1fa4b426954..7f7e27cc6f8 100644 --- a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.tsx +++ b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/hero-platform-loop.tsx @@ -266,7 +266,7 @@ export function HeroPlatformLoop() { >
diff --git a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/stage-block-card.tsx b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/stage-block-card.tsx index a679aef3881..7988b9fa5db 100644 --- a/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/stage-block-card.tsx +++ b/apps/sim/app/(landing)/components/hero/components/hero-platform-loop/stage-block-card.tsx @@ -350,7 +350,7 @@ export function StageBlockCard({ if (onSelect) handleKeyboardActivation(event, () => onSelect(block.id)) }} className={cn( - 'relative h-full w-full rounded-2xl', + 'relative size-full rounded-2xl', onSelect && 'cursor-pointer focus-visible:outline-none' )} > diff --git a/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/workflow-menu-preview/workflow-menu-preview.tsx b/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/workflow-menu-preview/workflow-menu-preview.tsx index 699e664ff05..52c86909022 100644 --- a/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/workflow-menu-preview/workflow-menu-preview.tsx +++ b/apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-preview/components/workflow-menu-preview/workflow-menu-preview.tsx @@ -31,12 +31,13 @@ interface WorkflowMenuPreviewProps { /** A cropped production canvas with a clear center and progressively softened inner edges. */ export function WorkflowMenuPreview({ layout = 'menu', onReady }: WorkflowMenuPreviewProps) { - const readyBlocksRef = useRef(new Set()) + const readyBlocksRef = useRef | null>(null) + const readyBlocks = (readyBlocksRef.current ??= new Set()) const handleBlockReady = (blockId: string) => { - if (readyBlocksRef.current.has(blockId)) return - readyBlocksRef.current.add(blockId) - if (readyBlocksRef.current.size === BLOCKS.length) onReady?.() + if (readyBlocks.has(blockId)) return + readyBlocks.add(blockId) + if (readyBlocks.size === BLOCKS.length) onReady?.() } return ( diff --git a/apps/sim/app/(landing)/components/platform-hero-visual/platform-hero-visual.tsx b/apps/sim/app/(landing)/components/platform-hero-visual/platform-hero-visual.tsx index 46048f236f6..ab40396ead9 100644 --- a/apps/sim/app/(landing)/components/platform-hero-visual/platform-hero-visual.tsx +++ b/apps/sim/app/(landing)/components/platform-hero-visual/platform-hero-visual.tsx @@ -20,7 +20,7 @@ interface PlatformHeroVisualProps { */ export function PlatformHeroVisual({ children }: PlatformHeroVisualProps) { return ( -
+
@@ -112,7 +112,7 @@ export function BuildAgentsLoop() { stageOpen ? 'w-1/2 border-l' : 'w-0 border-l-0' )} > -
+
-
+
diff --git a/apps/sim/app/(landing)/components/shared/editor-loop/editor-loop.tsx b/apps/sim/app/(landing)/components/shared/editor-loop/editor-loop.tsx index 42aa3408516..332bf4653fb 100644 --- a/apps/sim/app/(landing)/components/shared/editor-loop/editor-loop.tsx +++ b/apps/sim/app/(landing)/components/shared/editor-loop/editor-loop.tsx @@ -98,10 +98,10 @@ export function EditorLoop({ content }: EditorLoopProps) { workflows={content.sidebarWorkflows} activeItem={content.activeItem} > -
+
diff --git a/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx b/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx index a74fe87d45a..32e3eb88e6a 100644 --- a/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx +++ b/apps/sim/app/(landing)/components/solutions-page/components/solutions-card-row/components/solutions-card/solutions-card.tsx @@ -107,7 +107,7 @@ export function SolutionsCard({ card, headingId, tabletSpan = false }: Solutions wide && 'sm:max-lg:mt-0 sm:max-lg:w-auto sm:max-lg:min-w-0' )} > -
{card.visual}
+
{card.visual}
diff --git a/apps/sim/app/(landing)/components/solutions-page/components/solutions-visual-frame/solutions-visual-frame.tsx b/apps/sim/app/(landing)/components/solutions-page/components/solutions-visual-frame/solutions-visual-frame.tsx index d5d9fce410f..0b7b6ecdb06 100644 --- a/apps/sim/app/(landing)/components/solutions-page/components/solutions-visual-frame/solutions-visual-frame.tsx +++ b/apps/sim/app/(landing)/components/solutions-page/components/solutions-visual-frame/solutions-visual-frame.tsx @@ -8,7 +8,7 @@ import { SOLUTIONS_VISUAL } from '@/app/(landing)/components/solutions-page/cons * hero-visual family: `--surface-2` fill, `--border-1` hairline, `rounded-lg`, * `overflow-hidden`) and, crucially, its dimensions: it reserves a full-width * 16:9 aspect ratio for the solutions hero visual. Because the size is reserved - * before paint and the node fills `h-full w-full` inside, a dropped-in node can + * before paint and the node fills `size-full` inside, a dropped-in node can * neither shift surrounding layout (CLS = 0) nor change the frame's own padding. * * The frame is decorative chrome around product visuals, so it is `aria-hidden`; @@ -29,7 +29,7 @@ export function SolutionsVisualFrame({ children }: SolutionsVisualFrameProps) { SOLUTIONS_VISUAL.heroAspect )} > -
{children}
+
{children}
) } diff --git a/apps/sim/app/(landing)/components/solutions-page/constants.ts b/apps/sim/app/(landing)/components/solutions-page/constants.ts index d99293824fb..6675801aeb0 100644 --- a/apps/sim/app/(landing)/components/solutions-page/constants.ts +++ b/apps/sim/app/(landing)/components/solutions-page/constants.ts @@ -74,7 +74,7 @@ export const SOLUTIONS_TEXT_MEASURE = { * Reserved fixed dimensions for the component-owned visual frames. A dropped-in * `ReactNode` renders into a frame of exactly these dimensions, so it can never * shift surrounding layout (CLS = 0) nor change its own frame padding. The node - * fills `h-full w-full` inside; it owns nothing about the frame. + * fills `size-full` inside; it owns nothing about the frame. */ export const SOLUTIONS_VISUAL = { /** Full-width hero visual aspect ratio - reserves height before paint. */ @@ -105,7 +105,7 @@ export const SOLUTIONS_VISUAL = { featureTileContainer: '[container-type:inline-size]', featureTileScale: '[--tile-scale:min(1,tan(atan2(100cqw,352px)))]', featureTileCanvas: - 'h-[calc(100%/var(--tile-scale,1))] w-[calc(100%/var(--tile-scale,1))] origin-top-left [scale:var(--tile-scale,1)]', + 'size-[calc(100%/var(--tile-scale,1))] origin-top-left [scale:var(--tile-scale,1)]', /** * Feature-tile minimum height - the `440px` design-space height, multiplied * by the tile's current scale so height tracks the zoomed content. The diff --git a/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx b/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx index 534fa557cdc..bf89ed90878 100644 --- a/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx +++ b/apps/sim/app/(landing)/contact/components/contact-form/contact-form.tsx @@ -231,7 +231,7 @@ export function ContactForm() { >