From 1cb538fdeaf524d47bd627c92ad47df52444f796 Mon Sep 17 00:00:00 2001 From: Ravi Suhag Date: Sun, 13 Sep 2026 22:04:39 -0500 Subject: [PATCH 1/2] fix(icons): widen the lucide peer range to cover 1.x MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lucide-react shipped 1.0, and the peer range `>=0.500.0 <1.0.0` excludes it. An app on the current lucide cannot install Apsara without a peer warning, and its only options are to downgrade lucide or ignore the warning. Neither is right: nothing Apsara needs from lucide changed. Widen the ceiling to `<2.0.0` so both lines satisfy it. Apsara relies on three things from lucide and nothing else: the drawings it imports exist under those names, `width`/`height`/`strokeWidth` are plain SVG props, and the viewBox is 24 units (which is what makes `strokeWidth={1.5}` a 1px stroke at 16px). All three hold on 1.30.0, checked by hand. `icons/__tests__/lucide-contract.test.tsx` now pins them so a future lucide release that breaks one fails here rather than in a consumer's app. The devDependency stays on ^0.548.0 — the range is what consumers see, and bumping the dev pin is a separate call. --- .../src/content/docs/(overview)/upgrading.mdx | 6 +-- packages/raystack/CHANGELOG.md | 4 +- .../icons/__tests__/lucide-contract.test.tsx | 54 +++++++++++++++++++ packages/raystack/package.json | 2 +- 4 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 packages/raystack/icons/__tests__/lucide-contract.test.tsx diff --git a/apps/www/src/content/docs/(overview)/upgrading.mdx b/apps/www/src/content/docs/(overview)/upgrading.mdx index f4a51c02d..51cbf7302 100644 --- a/apps/www/src/content/docs/(overview)/upgrading.mdx +++ b/apps/www/src/content/docs/(overview)/upgrading.mdx @@ -22,9 +22,9 @@ names `@raystack/apsara/icons` exports have changed. npm install lucide-react ``` -The range is wide, `>=0.500.0 <1.0.0`, so your app picks the version. If a -lucide release changes a drawing you care about, replace that one icon (step 5) -rather than pinning the whole library. +The range is wide, `>=0.500.0 <2.0.0`, so your app picks the version — both the +0.x and 1.x lines satisfy it. If a lucide release changes a drawing you care +about, replace that one icon (step 5) rather than pinning the whole library. `@radix-ui/react-icons` is no longer a dependency of Apsara. If your own code diff --git a/packages/raystack/CHANGELOG.md b/packages/raystack/CHANGELOG.md index 594a2dd4e..c1e5a4dd2 100644 --- a/packages/raystack/CHANGELOG.md +++ b/packages/raystack/CHANGELOG.md @@ -13,8 +13,8 @@ and [Icons](https://apsara.raystack.io/docs/theme/icons). #### Breaking changes -- **`lucide-react` is a new peer dependency**, range `>=0.500.0 <1.0.0`. - Install it. +- **`lucide-react` is a new peer dependency**, range `>=0.500.0 <2.0.0`, + so both the 0.x and 1.x lines satisfy it. Install it. - **`@radix-ui/react-icons` is no longer a dependency.** If your own code imports from it, keep it in your own `dependencies`. - **`@raystack/apsara/icons` exports icon components, not raw SVG diff --git a/packages/raystack/icons/__tests__/lucide-contract.test.tsx b/packages/raystack/icons/__tests__/lucide-contract.test.tsx new file mode 100644 index 000000000..a72474d77 --- /dev/null +++ b/packages/raystack/icons/__tests__/lucide-contract.test.tsx @@ -0,0 +1,54 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { render } from '@testing-library/react'; +import * as lucide from 'lucide-react'; +import { describe, expect, it } from 'vitest'; +import { CheckIcon } from '../icons'; + +/** + * The peer range spans two lucide majors (`>=0.500.0 <2.0.0`), so what Apsara + * relies on has to hold across both. It relies on three things only, and this + * file pins each one: + * + * 1. every drawing it imports is still exported under that name, + * 2. `width` / `height` / `strokeWidth` are plain SVG props, and + * 3. the viewBox is 24 units, which is what makes `strokeWidth={1.5}` a 1px + * stroke at 16px. + * + * A lucide release that breaks any of them fails here rather than in a + * consumer's app. + */ + +/** vitest runs with the package root as the cwd. */ +const ICONS_SRC = resolve(process.cwd(), 'icons/icons.tsx'); + +/** The lucide names `icons.tsx` imports, read from its own import block. */ +function importedNames(): string[] { + const src = readFileSync(ICONS_SRC, 'utf8'); + const block = src.match(/import \{([\s\S]*?)\} from 'lucide-react';/); + if (!block) throw new Error('no lucide import block in icons.tsx'); + return block[1] + .split(',') + .map(name => name.trim()) + .filter(Boolean); +} + +describe('lucide contract', () => { + it('exports every drawing icons.tsx imports', () => { + const names = importedNames(); + + expect(names.length).toBeGreaterThan(0); + expect(names.filter(name => !(name in lucide))).toEqual([]); + }); + + it('takes size and stroke as SVG props, on a 24-unit viewBox', () => { + const { container } = render(); + const svg = container.querySelector('svg'); + + expect(svg).toHaveAttribute('width', '16'); + expect(svg).toHaveAttribute('height', '16'); + expect(svg).toHaveAttribute('stroke-width', '1.5'); + // Not 0 0 16 16: the rendered stroke is strokeWidth x size / 24. + expect(svg).toHaveAttribute('viewBox', '0 0 24 24'); + }); +}); diff --git a/packages/raystack/package.json b/packages/raystack/package.json index 1ea09e940..bfaefd8b2 100644 --- a/packages/raystack/package.json +++ b/packages/raystack/package.json @@ -135,7 +135,7 @@ }, "peerDependencies": { "@types/react": "^19", - "lucide-react": ">=0.500.0 <1.0.0", + "lucide-react": ">=0.500.0 <2.0.0", "react": "^19", "react-dom": "^19" }, From 60e11beb4a11a8785f63bd9227f95f7c6a4677c3 Mon Sep 17 00:00:00 2001 From: Ravi Suhag Date: Sun, 13 Sep 2026 22:19:59 -0500 Subject: [PATCH 2/2] chore(deps): move lucide to 1.x, and draw the GitHub mark ourselves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The peer range now spans two majors, so the repo should develop against the one consumers will actually install. It was pinned to ^0.548.0, a release from 2025-10-24 — ten months old and a major behind on the day #883 adopted lucide, since 1.0 shipped 2026-03-23. Nothing chose that; the ceiling just capped at the next major without noticing it had already landed. There is no renovate or dependabot config to have caught it either. Bump the devDependency in `packages/raystack` and `apps/www` to ^1.45.0. One name breaks: lucide dropped its brand icons in 1.0, so `Github` is gone. It was used once, on the docs navbar's "View source" link. A brand mark is the one icon a general set can't carry — the shape belongs to its owner — so draw it beside the link that needs it instead of reaching for another dependency. Every other name the workspace imports, in the package and in the docs app alike, resolves unchanged on 1.45.0. --- apps/www/package.json | 2 +- apps/www/src/components/docs/github-mark.tsx | 24 +++++++++++++++ apps/www/src/components/docs/navbar.tsx | 7 ++--- packages/raystack/CHANGELOG.md | 4 ++- packages/raystack/package.json | 2 +- pnpm-lock.yaml | 32 ++++++++++---------- 6 files changed, 48 insertions(+), 23 deletions(-) create mode 100644 apps/www/src/components/docs/github-mark.tsx diff --git a/apps/www/package.json b/apps/www/package.json index f66b19f81..8fcd3f29a 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -22,7 +22,7 @@ "fumadocs-mdx": "13.0.5", "fumadocs-typescript": "^4.0.6", "fumadocs-ui": "16.0.7", - "lucide-react": "^0.548.0", + "lucide-react": "^1.45.0", "next": "16.0.7", "next-themes": "^0.4.4", "prettier": "^2.8.8", diff --git a/apps/www/src/components/docs/github-mark.tsx b/apps/www/src/components/docs/github-mark.tsx new file mode 100644 index 000000000..319bb6502 --- /dev/null +++ b/apps/www/src/components/docs/github-mark.tsx @@ -0,0 +1,24 @@ +import type { SVGProps } from 'react'; + +/** + * The GitHub mark, drawn here rather than imported. + * + * lucide dropped its brand icons in 1.0, so `Github` no longer exists. A brand + * mark is the one kind of icon a general icon set can't carry — the shape + * belongs to its owner, not to the set — so it lives with the one link that + * uses it. + */ +export function GithubMark(props: SVGProps) { + return ( + + ); +} diff --git a/apps/www/src/components/docs/navbar.tsx b/apps/www/src/components/docs/navbar.tsx index babe95fc2..fe10bd3e3 100644 --- a/apps/www/src/components/docs/navbar.tsx +++ b/apps/www/src/components/docs/navbar.tsx @@ -2,12 +2,13 @@ import { Breadcrumb, Button, CopyIcon } from '@raystack/apsara'; import { useBreadcrumb } from 'fumadocs-core/breadcrumb'; import { Root } from 'fumadocs-core/page-tree'; -import { Component, Github } from 'lucide-react'; +import { Component } from 'lucide-react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { Fragment, useState } from 'react'; import { SourceType } from '@/lib/types'; import { useDemoContext } from '../demo/demo-context'; +import { GithubMark } from './github-mark'; import styles from './navbar.module.css'; const cache = new Map(); @@ -114,9 +115,7 @@ export default function DocsNavbar({ variant='outline' color='neutral' size='small' - leadingIcon={ - - } + leadingIcon={} > View source diff --git a/packages/raystack/CHANGELOG.md b/packages/raystack/CHANGELOG.md index c1e5a4dd2..d7acbce8f 100644 --- a/packages/raystack/CHANGELOG.md +++ b/packages/raystack/CHANGELOG.md @@ -14,7 +14,9 @@ and [Icons](https://apsara.raystack.io/docs/theme/icons). #### Breaking changes - **`lucide-react` is a new peer dependency**, range `>=0.500.0 <2.0.0`, - so both the 0.x and 1.x lines satisfy it. Install it. + so both the 0.x and 1.x lines satisfy it. Install it. Note that lucide + dropped its brand icons in 1.0, so a name like `Github` no longer + exists there — none of Apsara's 31 keys draw one. - **`@radix-ui/react-icons` is no longer a dependency.** If your own code imports from it, keep it in your own `dependencies`. - **`@raystack/apsara/icons` exports icon components, not raw SVG diff --git a/packages/raystack/package.json b/packages/raystack/package.json index bfaefd8b2..a8503d827 100644 --- a/packages/raystack/package.json +++ b/packages/raystack/package.json @@ -98,7 +98,7 @@ "dotenv": "^17.2.2", "identity-obj-proxy": "^3.0.0", "jsdom": "^26.1.0", - "lucide-react": "^0.548.0", + "lucide-react": "^1.45.0", "postcss": "^8.4.24", "postcss-import": "^16.1.0", "postcss-modules": "^6.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f7baba41..f7c2ddafd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -55,22 +55,22 @@ importers: version: 1.11.20 fumadocs-core: specifier: 16.0.7 - version: 16.0.7(@types/react@19.2.2)(lucide-react@0.548.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + version: 16.0.7(@types/react@19.2.2)(lucide-react@1.45.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) fumadocs-docgen: specifier: ^1.3.8 version: 1.3.8(typescript@5.9.3) fumadocs-mdx: specifier: 13.0.5 - version: 13.0.5(fumadocs-core@16.0.7(@types/react@19.2.2)(lucide-react@0.548.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)(vite@7.1.3(@types/node@24.10.0)) + version: 13.0.5(fumadocs-core@16.0.7(@types/react@19.2.2)(lucide-react@1.45.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)(vite@7.1.3(@types/node@24.10.0)) fumadocs-typescript: specifier: ^4.0.6 version: 4.0.6(@types/react@19.2.2)(typescript@5.9.3) fumadocs-ui: specifier: 16.0.7 - version: 16.0.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(lucide-react@0.548.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(tailwindcss@4.1.16) + version: 16.0.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(lucide-react@1.45.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(tailwindcss@4.1.16) lucide-react: - specifier: ^0.548.0 - version: 0.548.0(react@19.2.1) + specifier: ^1.45.0 + version: 1.45.0(react@19.2.1) next: specifier: 16.0.7 version: 16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -270,8 +270,8 @@ importers: specifier: ^26.1.0 version: 26.1.0 lucide-react: - specifier: ^0.548.0 - version: 0.548.0(react@19.2.1) + specifier: ^1.45.0 + version: 1.45.0(react@19.2.1) postcss: specifier: ^8.4.24 version: 8.4.24 @@ -4190,8 +4190,8 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} - lucide-react@0.548.0: - resolution: {integrity: sha512-63b16z63jM9yc1MwxajHeuu0FRZFsDtljtDjYm26Kd86UQ5HQzu9ksEtoUUw4RBuewodw/tGFmvipePvRsKeDA==} + lucide-react@1.45.0: + resolution: {integrity: sha512-yH1ubCAduho9UR7oJhRXIQXogksRILBiTuZC4/bQIGeB9JOkxMlSuEHyyZpo1Z3S0yWJO2KTSUZbjiNvVxeOUw==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -9458,7 +9458,7 @@ snapshots: fsevents@2.3.3: optional: true - fumadocs-core@16.0.7(@types/react@19.2.2)(lucide-react@0.548.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + fumadocs-core@16.0.7(@types/react@19.2.2)(lucide-react@1.45.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: '@formatjs/intl-localematcher': 0.6.2 '@orama/orama': 3.1.16 @@ -9480,7 +9480,7 @@ snapshots: unist-util-visit: 5.0.0 optionalDependencies: '@types/react': 19.2.2 - lucide-react: 0.548.0(react@19.2.1) + lucide-react: 1.45.0(react@19.2.1) next: 16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1) react: 19.2.1 react-dom: 19.2.1(react@19.2.1) @@ -9501,14 +9501,14 @@ snapshots: - supports-color - typescript - fumadocs-mdx@13.0.5(fumadocs-core@16.0.7(@types/react@19.2.2)(lucide-react@0.548.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)(vite@7.1.3(@types/node@24.10.0)): + fumadocs-mdx@13.0.5(fumadocs-core@16.0.7(@types/react@19.2.2)(lucide-react@1.45.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)(vite@7.1.3(@types/node@24.10.0)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.0.0 chokidar: 4.0.3 esbuild: 0.25.12 estree-util-value-to-estree: 3.5.0 - fumadocs-core: 16.0.7(@types/react@19.2.2)(lucide-react@0.548.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + fumadocs-core: 16.0.7(@types/react@19.2.2)(lucide-react@1.45.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) js-yaml: 4.1.0 lru-cache: 11.2.2 mdast-util-to-markdown: 2.1.2 @@ -9560,7 +9560,7 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-ui@16.0.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(lucide-react@0.548.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(tailwindcss@4.1.16): + fumadocs-ui@16.0.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(lucide-react@1.45.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(tailwindcss@4.1.16): dependencies: '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -9573,7 +9573,7 @@ snapshots: '@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.1) '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) class-variance-authority: 0.7.1 - fumadocs-core: 16.0.7(@types/react@19.2.2)(lucide-react@0.548.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + fumadocs-core: 16.0.7(@types/react@19.2.2)(lucide-react@1.45.0(react@19.2.1))(next@16.0.7(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react-dom@19.2.1(react@19.2.1))(react@19.2.1) lodash.merge: 4.6.2 next-themes: 0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) postcss-selector-parser: 7.1.0 @@ -10382,7 +10382,7 @@ snapshots: dependencies: yallist: 4.0.0 - lucide-react@0.548.0(react@19.2.1): + lucide-react@1.45.0(react@19.2.1): dependencies: react: 19.2.1