diff --git a/.claude/rules/sim-settings-pages.md b/.claude/rules/sim-settings-pages.md index f2e7a5f63b5..38e9a364554 100644 --- a/.claude/rules/sim-settings-pages.md +++ b/.claude/rules/sim-settings-pages.md @@ -96,6 +96,55 @@ Adding a new settings page: `settings/[section]/settings.tsx`. 3. Build the component body inside `` — no shell, no title block. +## Text-scale tokens (no literal pixel sizes) + +Settings pages never use a literal `text-[Npx]` class — always the named Tailwind +scale token from `apps/sim/tailwind.config.ts`'s `fontSize` extension (`text-micro` +10px, `text-xs` 11px, `text-caption` 12px, `text-small` 13px, `text-sm` 14px +[Tailwind default, unmodified], `text-base` 15px, `text-md` 16px, `text-lg` 18px +[Tailwind default]). A literal size is either a straight rename to the equivalent +token (if the pixel value matches one exactly) or a sign the page never migrated — +grep `text-\[1[0-8]px\]` under `apps/sim/app/workspace/*/settings/**` and +`apps/sim/ee/**` to find stragglers. + +For a two-line list row (title/value on top, a muted subtitle below — a name + +email, a tool name + description, a server name + status), the established +pairing is: + +- **Title / row value**: `text-[var(--text-body)] text-sm` +- **Subtitle / muted description**: `text-[var(--text-muted)] text-caption` + +This is not a stylistic guess — it is the tokenized form of the literal-pixel +pairing (`text-[14px] text-[var(--text-body)]` / `text-[12px] +text-[var(--text-muted)]`) already used for this exact row shape across +`member-list.tsx`, `api-keys.tsx`, `mcp.tsx`, `billing.tsx`, `credential-sets.tsx`, +`workflow-mcp-servers.tsx`, and others — keep new rows consistent with it rather +than inventing a new size pairing. + +For a toggle row (a `Switch` with a title and optional description), use the emcn +`Label` component for the title — never a hand-rolled `` — paired with +`Switch`'s `id`/`Label`'s `htmlFor`: + +```tsx +
+
+ +

One-line description.

+
+ +
+``` + +`Label`'s own default styling (`font-medium text-[var(--text-primary)] +text-small`) already matches the established title treatment — do not add a +`className` overriding its size/color unless the row genuinely needs something +different. + +`--text-primary`/`--text-secondary` and `--text-body`/`--text-muted` are both real, +independently-defined tokens (not interchangeable — they resolve to different +colors) and both see legitimate use across settings pages; this rule only pins +down the **row title/subtitle** shape above, not every text element on every page. + ## Other shared settings primitives (do not re-roll these) - **`SettingsEmptyState`** (`…/components/settings-empty-state`) — the canonical @@ -175,4 +224,5 @@ A settings page is design-system-clean when: - [ ] Detail sub-views and entitlement/loading gates keep their own chrome (intentional). - [ ] If it has editable state: Save/Discard go through `SaveDiscardActions`, dirty is wired via `useSettingsUnsavedGuard` (called before any early-return gate), and there is **no** hand-rolled Save button / `beforeunload` / "Unsaved changes" modal. - [ ] No business logic, handlers, or conditional rendering changed by the migration. +- [ ] No literal `text-[Npx]` classes — named scale tokens only (see "Text-scale tokens" above). - [ ] `tsc`, `biome`, and the page's tests pass. diff --git a/.claude/skills/add-settings-page/SKILL.md b/.claude/skills/add-settings-page/SKILL.md index 2da63f9a08e..69173332ad4 100644 --- a/.claude/skills/add-settings-page/SKILL.md +++ b/.claude/skills/add-settings-page/SKILL.md @@ -50,21 +50,30 @@ For each page component, confirm the checklist in `.claude/rules/sim-settings-pa **gate** early-return. Anything else is a page that still needs migrating. 2. Find hand-rolled title blocks (should be 0 outside detail views): `git grep -n "text-\[var(--text-body)\] text-lg" -- 'apps/sim/**/settings/' 'apps/sim/ee/'` -3. Confirm each page imports `SettingsPanel` and that its `NavigationItem` has an +3. Find literal pixel text sizes (should be 0 — see "Text-scale tokens" in + `.claude/rules/sim-settings-pages.md` for the token map and the row + title/subtitle pairing convention): + `git grep -n "text-\[1[0-8]px\]" -- 'apps/sim/**/settings/' 'apps/sim/ee/'` +4. Confirm each page imports `SettingsPanel` and that its `NavigationItem` has an accurate `description` of consistent length with its peers. - Editable pages: confirm Save/Discard go through `SaveDiscardActions` and dirty is wired via `useSettingsUnsavedGuard` (called before early-return gates) — flag any hand-rolled Save button, `beforeunload`, or unsaved modal. `git grep -n "beforeunload" -- 'apps/sim/**/settings/' 'apps/sim/ee/'` should only hit the centralized `use-settings-before-unload.ts`. -4. When migrating a page, change ONLY the structural shell→`SettingsPanel` swap: +5. When migrating a page, change ONLY the structural shell→`SettingsPanel` swap: move header chips to `actions`, the standalone search to `search`, delete the `

` title block, replace the three closing `` (column/scroll/shell) with ``, and keep modal siblings in a `<>` fragment. Do NOT touch handlers, state, queries, conditional rendering, or detail/gate returns. Drop per-page `gap-*`/`pt-*` on the content column in favor of the panel default. -5. Remove now-unused imports (`ChipInput`/`Search`) ONLY after grepping that +6. When fixing literal pixel text sizes, replace ONLY the size class with its + exact-pixel-equivalent named token (e.g. `text-[12px]` → `text-caption`, + never a different size) — this must render pixel-identical, not restyle the + page. Leave color tokens (`--text-primary` vs `--text-body`, etc.) untouched + unless they're also being changed for an unrelated, deliberate reason. +7. Remove now-unused imports (`ChipInput`/`Search`) ONLY after grepping that they are not still used elsewhere in the file (e.g. by a detail view). -6. **Verify the whole sweep:** `tsc --noEmit`, `biome check` on every touched +8. **Verify the whole sweep:** `tsc --noEmit`, `biome check` on every touched file, and run the affected pages' tests. Diff each file against the base and confirm the change is purely structural before shipping. diff --git a/apps/sim/app/workspace/[workspaceId]/settings/billing/credit-usage/credit-usage-view.tsx b/apps/sim/app/workspace/[workspaceId]/settings/billing/credit-usage/credit-usage-view.tsx index 5e29dfc68f1..e44851bf789 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/billing/credit-usage/credit-usage-view.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/billing/credit-usage/credit-usage-view.tsx @@ -50,13 +50,13 @@ interface UsageLogRowProps { function UsageLogRow({ log }: UsageLogRowProps) { return (
- + {formatDateTime(new Date(log.createdAt))} - + {rowLabel(log)} - + {formatApportionedCreditCost(log.creditCost, log.dollarCost)}
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/admin/admin.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/admin/admin.tsx index f2fdfd98a44..95e1faadb0b 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/admin/admin.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/admin/admin.tsx @@ -251,7 +251,7 @@ export function Admin() { unbanUser.error || impersonateUser.error || impersonationGuardError) && ( -

+

{impersonationGuardError || (setUserRole.error || banUser.error || unbanUser.error || impersonateUser.error) ?.message || @@ -304,7 +304,7 @@ export function Admin() { <> @@ -393,7 +393,7 @@ export function WhitelabelingSettings() { variant='ghost' size='sm' onClick={logoUpload.handleRemove} - className='text-[13px] text-[var(--text-muted)] hover:text-[var(--text-primary)]' + className='text-[var(--text-muted)] text-small hover:text-[var(--text-primary)]' > @@ -442,7 +442,7 @@ export function WhitelabelingSettings() { size='sm' onClick={wordmarkUpload.handleThumbnailClick} disabled={wordmarkUpload.isUploading} - className='text-[13px]' + className='text-small' > {wordmarkUpload.previewUrl ? 'Change' : 'Upload'} @@ -451,7 +451,7 @@ export function WhitelabelingSettings() { variant='ghost' size='sm' onClick={wordmarkUpload.handleRemove} - className='text-[13px] text-[var(--text-muted)] hover:text-[var(--text-primary)]' + className='text-[var(--text-muted)] text-small hover:text-[var(--text-primary)]' >