Skip to content

Commit fc6eaea

Browse files
authored
feat(ui): share shadcn primitives in @sketchi/ui and lint the Tailwind apps with @shadcn/lint (#601)
The site hand-rolled its buttons in plain CSS while the playground kept its own copy of the shadcn primitives. The primitives now live once, in packages/ui, with the stylesheet that maps shadcn's tokens onto the shared theme. The playground imports them from there and the site uses the same Button: a `brand` variant and an `xl` size carry the site's call to action, and the mobile menu toggle is an outline icon button with lucide icons. Both apps have a components.json whose ui and utils aliases point at the package. @shadcn/lint and better-tailwindcss now run as Oxlint plugins in `pnpm run lint`, configured in tools/oxlint/ui-style-policy.json and described in docs/ui-style-contracts.md. What they found is fixed: classes with no CSS behind them, inline left/top and animation delays that now go through CSS custom properties, utility-shaped overrides moved to Tailwind classes, and the tw-animate-css classes the primitives use, which only the playground imported. The site now loads Tailwind's preflight; the docs page's numbered steps keep their numbers.
1 parent 9eb89b5 commit fc6eaea

71 files changed

Lines changed: 990 additions & 286 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.oxfmtrc.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"packages/diagram/agent/src/lib/code-mode/fixtures/**",
2929
// Vendored shadcn and AI Elements registry components
3030
"apps/playground/src/components/ai-elements/**",
31-
"apps/playground/src/components/ui/**",
31+
"packages/ui/src/components/**",
3232
// Raw pipeline artifacts, and state that editor and agent tools rewrite
3333
"packages/icons/catalog/pipeline-output/**",
3434
".agents/**",

‎.oxlintrc.json‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,12 @@
1818
"jsPlugins": [
1919
// Provides @nx/enforce-module-boundaries; ESLint itself is not installed.
2020
{ "name": "@nx", "specifier": "@nx/eslint-plugin" },
21-
"./tools/oxlint/sketchi-plugin.ts"
21+
"./tools/oxlint/sketchi-plugin.ts",
22+
"@shadcn/lint",
23+
"eslint-plugin-better-tailwindcss"
2224
],
25+
// Tailwind component contracts: docs/ui-style-contracts.md.
26+
"extends": ["./tools/oxlint/ui-style-policy.json"],
2327
"categories": {
2428
"correctness": "error"
2529
},
@@ -229,6 +233,7 @@
229233
"packages/diagram/ui/**",
230234
"packages/icons/catalog/**",
231235
"packages/svg-excalidraw/**",
236+
"packages/ui/**",
232237
"tools/sketchi-generators/**"
233238
],
234239
"rules": {

‎README.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ model candidate → typed diagram IR → deterministic scene → Excalidraw arti
147147
| `packages/icons/catalog` | Icon manifest, ranked search, SVG sources, node-logo rules |
148148
| `packages/studio/projects` | Studio project/diagram contracts and object-bucket persistence |
149149
| `packages/svg-excalidraw` | Native SVG-to-Excalidraw conversion and library serialization |
150+
| `packages/ui` | Shared shadcn primitives and tokens for the Tailwind apps |
150151

151152
The first high-reliability target is decision-heavy flowchart generation.
152153
`flowchart`, `mindmap`, `sequence`, `timeline`, `chart`, `state-machine`, `gantt`, `er`, `class`, `org-chart`, `venn`, `journey`, `sankey`, `wireframe`, and `quadrant` are explicit diagram families; unsupported families do
@@ -213,7 +214,9 @@ lint-rule tests.
213214
then runs [Oxlint](https://oxc.rs/docs/guide/usage/linter) over the whole
214215
repository with zero warnings allowed. `.oxlintrc.json` holds the rule set, the
215216
Nx module boundaries, and the Effect and Zod import policy; Sketchi's own rules
216-
live in `tools/oxlint` and run with `pnpm run test:lint-rules`. Every
217+
live in `tools/oxlint` and run with `pnpm run test:lint-rules`. `@shadcn/lint`
218+
holds the Tailwind apps to their shared primitives; see
219+
[UI style contracts](docs/ui-style-contracts.md). Every
217220
suppression is a single-line `oxlint-disable-next-line <rule> -- <reason>`.
218221
- `pnpm run format` applies [Oxfmt](https://oxc.rs/docs/guide/usage/formatter):
219222
tabs, 100 columns, double quotes, semicolons, trailing commas, and spaces in
@@ -294,7 +297,8 @@ apps/
294297
packages/
295298
├── diagram/{agent,core,excalidraw,generation,renderer,scenarios,ui}/
296299
├── studio/projects/ persistence contract and service
297-
└── svg-excalidraw/ native SVG conversion
300+
├── svg-excalidraw/ native SVG conversion
301+
└── ui/ shared shadcn primitives
298302
tools/sketchi-generators/ Nx generators and tests
299303
scripts/ deploy, cutover, and pipeline operations
300304
plugins/ Codex and Claude Code distribution payloads
@@ -313,6 +317,7 @@ build configuration; see the
313317

314318
- [Agent plugin quickstart](docs/code-mode-agent-plugins.md)
315319
- [Architecture](docs/architecture.md)
320+
- [UI style contracts](docs/ui-style-contracts.md)
316321
- [Agentic generation](docs/agentic-generation.md)
317322
- [MCP-first generation](docs/mcp-first-generation.md)
318323
- [SVG conversion plan](docs/svg-excalidraw-conversion-plan.md)

‎apps/playground/components.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
"iconLibrary": "lucide",
1414
"aliases": {
1515
"components": "@/components",
16-
"utils": "@/lib/utils",
17-
"ui": "@/components/ui",
16+
"utils": "@sketchi/ui/lib/utils",
17+
"ui": "@sketchi/ui/components",
1818
"lib": "@/lib",
1919
"hooks": "@/hooks"
2020
}

‎apps/playground/package.json‎

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,21 @@
1818
"@sketchi/icon-catalog": "workspace:*",
1919
"@sketchi/observability": "workspace:*",
2020
"@sketchi/studio-projects": "workspace:*",
21+
"@sketchi/ui": "workspace:*",
2122
"@streamdown/cjk": "1.0.4",
2223
"@streamdown/code": "2.0.0",
2324
"@streamdown/math": "1.0.3",
2425
"@tanstack/react-virtual": "3.14.13",
2526
"ai": "7.0.130",
26-
"class-variance-authority": "0.7.1",
27-
"clsx": "2.1.1",
28-
"cmdk": "1.1.1",
2927
"lucide-react": "1.52.0",
3028
"motion": "14.0.0",
3129
"nanoid": "6.0.2",
32-
"radix-ui": "1.7.0",
3330
"shiki": "4.5.0",
3431
"streamdown": "2.7.0",
35-
"tailwind-merge": "3.7.0",
3632
"use-stick-to-bottom": "1.1.6"
3733
},
3834
"devDependencies": {
3935
"@tailwindcss/vite": "4.3.3",
40-
"tailwindcss": "4.3.3",
41-
"tw-animate-css": "1.4.0"
36+
"tailwindcss": "4.3.3"
4237
}
4338
}

‎apps/playground/src/application-structure.test.ts‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,7 @@ describe("Playground application structure", () => {
180180
expect(existsSync(join(libRoot, path)), path).toBe(false);
181181
}
182182

183-
expect(
184-
sourceFilesUnder(libRoot)
185-
.map((path) => relative(libRoot, path))
186-
.sort(),
187-
).toEqual(["utils.ts"]);
183+
// `cn` and the shadcn primitives come from @sketchi/ui.
184+
expect(existsSync(libRoot)).toBe(false);
188185
});
189186
});

‎apps/playground/src/components/ai-elements/code-block.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
"use client";
22

3-
import { Button } from "@/components/ui/button";
3+
import { Button } from "@sketchi/ui/components/button";
44
import {
55
Select,
66
SelectContent,
77
SelectItem,
88
SelectTrigger,
99
SelectValue,
10-
} from "@/components/ui/select";
11-
import { cn } from "@/lib/utils";
10+
} from "@sketchi/ui/components/select";
11+
import { cn } from "@sketchi/ui/lib/utils";
1212
import { CheckIcon, CopyIcon } from "lucide-react";
1313
import type { ComponentProps, HTMLAttributes } from "react";
1414
import {

‎apps/playground/src/components/ai-elements/conversation.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"use client";
22

3-
import { Button } from "@/components/ui/button";
4-
import { cn } from "@/lib/utils";
3+
import { Button } from "@sketchi/ui/components/button";
4+
import { cn } from "@sketchi/ui/lib/utils";
55
import type { UIMessage } from "ai";
66
import { ArrowDownIcon, DownloadIcon } from "lucide-react";
77
import type { ComponentProps } from "react";

‎apps/playground/src/components/ai-elements/message.tsx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
"use client";
22

3-
import { Button } from "@/components/ui/button";
3+
import { Button } from "@sketchi/ui/components/button";
44
import {
55
ButtonGroup,
66
ButtonGroupText,
7-
} from "@/components/ui/button-group";
7+
} from "@sketchi/ui/components/button-group";
88
import {
99
Tooltip,
1010
TooltipContent,
1111
TooltipProvider,
1212
TooltipTrigger,
13-
} from "@/components/ui/tooltip";
14-
import { cn } from "@/lib/utils";
13+
} from "@sketchi/ui/components/tooltip";
14+
import { cn } from "@sketchi/ui/lib/utils";
1515
import type { UIMessage } from "ai";
1616
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
1717
import type { ComponentProps, HTMLAttributes, ReactElement } from "react";

‎apps/playground/src/components/ai-elements/prompt-input.tsx‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,38 @@ import {
88
CommandItem,
99
CommandList,
1010
CommandSeparator,
11-
} from "@/components/ui/command";
11+
} from "@sketchi/ui/components/command";
1212
import {
1313
DropdownMenu,
1414
DropdownMenuContent,
1515
DropdownMenuItem,
1616
DropdownMenuTrigger,
17-
} from "@/components/ui/dropdown-menu";
17+
} from "@sketchi/ui/components/dropdown-menu";
1818
import {
1919
HoverCard,
2020
HoverCardContent,
2121
HoverCardTrigger,
22-
} from "@/components/ui/hover-card";
22+
} from "@sketchi/ui/components/hover-card";
2323
import {
2424
InputGroup,
2525
InputGroupAddon,
2626
InputGroupButton,
2727
InputGroupTextarea,
28-
} from "@/components/ui/input-group";
28+
} from "@sketchi/ui/components/input-group";
2929
import {
3030
Select,
3131
SelectContent,
3232
SelectItem,
3333
SelectTrigger,
3434
SelectValue,
35-
} from "@/components/ui/select";
36-
import { Spinner } from "@/components/ui/spinner";
35+
} from "@sketchi/ui/components/select";
36+
import { Spinner } from "@sketchi/ui/components/spinner";
3737
import {
3838
Tooltip,
3939
TooltipContent,
4040
TooltipTrigger,
41-
} from "@/components/ui/tooltip";
42-
import { cn } from "@/lib/utils";
41+
} from "@sketchi/ui/components/tooltip";
42+
import { cn } from "@sketchi/ui/lib/utils";
4343
import type { ChatStatus, FileUIPart, SourceDocumentUIPart } from "ai";
4444
import {
4545
CornerDownLeftIcon,

0 commit comments

Comments
 (0)