diff --git a/.trae/TODO.md b/.trae/TODO.md
new file mode 100644
index 00000000..b9fdd041
--- /dev/null
+++ b/.trae/TODO.md
@@ -0,0 +1,14 @@
+# TODO:
+
+- [x] 1: Update bundler.ts to handle RSLint instead of Rspack/Turbopack (priority: High)
+- [x] 2: Update data.tsx to use RSLint-specific KV keys and data structure (priority: High)
+- [x] 7: Set up comprehensive end-to-end testing framework (priority: High)
+- [x] 8: Create mock KV data and test utilities for RSLint test results (priority: High)
+- [x] 3: Update HeatMap components to display RSLint test results with appropriate GitHub links (priority: Medium)
+- [x] 4: Update UI text and branding throughout the app to reflect RSLint instead of bundlers (priority: Medium)
+- [x] 6: Test the updated implementation (priority: Medium)
+- [x] 9: Add tests for data fetching and processing functions (priority: Medium)
+- [x] 10: Create tests for HeatMap rendering with RSLint test results (priority: Medium)
+- [x] 5: Update page titles and metadata to reflect RSLint tracking (priority: Low)
+- [ ] 11: Run end-to-end tests with Playwright to verify complete application functionality (**IN PROGRESS**) (priority: High)
+- [ ] 12: Verify the application works correctly in the browser (priority: Medium)
diff --git a/arewerslintyet/.gitignore b/arewerslintyet/.gitignore
new file mode 100644
index 00000000..d7be0e1b
--- /dev/null
+++ b/arewerslintyet/.gitignore
@@ -0,0 +1,41 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+node_modules
+.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# next.js
+.next/
+out/
+
+# production
+build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# playwright
+playwright-report/
+test-results/
+/playwright-report
+/test-results
diff --git a/arewerslintyet/.npmrc b/arewerslintyet/.npmrc
new file mode 100644
index 00000000..3e775efb
--- /dev/null
+++ b/arewerslintyet/.npmrc
@@ -0,0 +1 @@
+auto-install-peers=true
diff --git a/arewerslintyet/README.md b/arewerslintyet/README.md
new file mode 100644
index 00000000..feffa701
--- /dev/null
+++ b/arewerslintyet/README.md
@@ -0,0 +1,9 @@
+# Are We Turbo Yet?
+
+This site tracks test passing for Turbopack inside of Next.js.
+
+## Development
+
+1. Clone the repository
+1. `vercel link`
+1. `vercel env pull .env.local`
diff --git a/arewerslintyet/app/Footer.js b/arewerslintyet/app/Footer.js
new file mode 100644
index 00000000..983c4a76
--- /dev/null
+++ b/arewerslintyet/app/Footer.js
@@ -0,0 +1,43 @@
+import { Bundler, getBundler } from './bundler';
+
+function FooterLink({ href, children }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export default function Footer() {
+ const bundler = getBundler();
+ return (
+
+ {bundler === Bundler.Turbopack ? (
+ <>
+
+ Turbopack Docs
+
+ ·
+
+ Next.js 15
+
+ >
+ ) : (
+ <>
+ Rspack Docs
+ ·
+ Next.js Docs
+ ·
+
+ Are We Turbo Yet?
+
+ >
+ )}
+
+ );
+}
diff --git a/arewerslintyet/app/Geist-Regular.woff2 b/arewerslintyet/app/Geist-Regular.woff2
new file mode 100644
index 00000000..f03a99e0
Binary files /dev/null and b/arewerslintyet/app/Geist-Regular.woff2 differ
diff --git a/arewerslintyet/app/Geist-SemiBold.woff2 b/arewerslintyet/app/Geist-SemiBold.woff2
new file mode 100644
index 00000000..33704bc9
Binary files /dev/null and b/arewerslintyet/app/Geist-SemiBold.woff2 differ
diff --git a/arewerslintyet/app/Graph.tsx b/arewerslintyet/app/Graph.tsx
new file mode 100644
index 00000000..a9ce011e
--- /dev/null
+++ b/arewerslintyet/app/Graph.tsx
@@ -0,0 +1,115 @@
+'use client';
+
+import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from 'recharts';
+
+import {
+ ChartConfig,
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+} from '@/components/ui/chart';
+
+const CHART_CONFIG = {
+ percentPassing: {
+ color: 'hsl(var(--chart))',
+ },
+} satisfies ChartConfig;
+
+const DATE_FORMAT = new Intl.DateTimeFormat(undefined, {
+ month: 'short',
+ year: 'numeric',
+});
+
+function tooltipFormatter(_value, _name, item) {
+ let data = item.payload;
+ let gitHash = data.gitHash.slice(0, 7);
+ let progress = `${data.passing} / ${data.total}`;
+ return (
+ <>
+ {`${new Date(data.date).toLocaleString('default', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ })}\nā ${gitHash}`}
+ {`${data.percent}% (${progress})`}
+ >
+ );
+}
+
+function formatDateTick(dateSinceEpoch: number) {
+ return DATE_FORMAT.format(dateSinceEpoch);
+}
+
+export default function TestPassingGraph({ graphData }) {
+ // compute this manually, otherwise AreaChart will always start at zero
+ const minPercent = Math.floor(Math.min(...graphData.map(d => d.percent)));
+ return (
+
+
+
+
+
+
+ }
+ />
+ {/* Ideally the fill would use a linear gradient like shadcn/ui's
+ example charts do, but that doesn't play well with allowDataOverflow,
+ so just use a solid color instead. */}
+
+
+
+
+ );
+}
diff --git a/arewerslintyet/app/GraphDataDevelopment.tsx b/arewerslintyet/app/GraphDataDevelopment.tsx
new file mode 100644
index 00000000..a7941079
--- /dev/null
+++ b/arewerslintyet/app/GraphDataDevelopment.tsx
@@ -0,0 +1,8 @@
+import { getDevelopmentLintRuns } from './data';
+import Graph from './Graph';
+
+export default async function GraphDataDevelopment() {
+ const { graphData } = await getDevelopmentLintRuns();
+
+ return ;
+}
diff --git a/arewerslintyet/app/GraphDataProduction.tsx b/arewerslintyet/app/GraphDataProduction.tsx
new file mode 100644
index 00000000..727fd7b5
--- /dev/null
+++ b/arewerslintyet/app/GraphDataProduction.tsx
@@ -0,0 +1,11 @@
+import { getProductionLintRuns } from './data';
+import Graph from './Graph';
+
+export default async function GraphDataProduction() {
+ const { graphData } = await getProductionLintRuns();
+ if (graphData.length === 0) {
+ return null;
+ }
+
+ return ;
+}
diff --git a/arewerslintyet/app/HeatMap.tsx b/arewerslintyet/app/HeatMap.tsx
new file mode 100644
index 00000000..09436646
--- /dev/null
+++ b/arewerslintyet/app/HeatMap.tsx
@@ -0,0 +1,47 @@
+import HeatMapItem from './HeatMapItem';
+
+function getTooltipContent(data) {
+ let ruleName = data.test.slice(2);
+ return `rule: "${ruleName}"`;
+}
+
+export function HeapMap({ lintResults }) {
+ let index = 0;
+ let testData = {};
+
+ Object.keys(lintResults).forEach(status => {
+ const value = lintResults[status];
+ if (!value) return;
+ value.split('\n\n').forEach(ruleGroup => {
+ let lines = ruleGroup.replace(/\n$/, '').split('\n');
+ let file = lines[0];
+ let rules = lines.slice(1);
+ if (!testData[file]) {
+ testData[file] = {};
+ }
+ testData[file][status] = rules.map(test => {
+ const tooltipContent = getTooltipContent({ file, test });
+ return (
+
+ );
+ });
+ });
+ });
+
+ let items = [];
+ Object.keys(testData).forEach(file => {
+ let testList = testData[file];
+ items = items.concat(
+ Object.keys(testList).map(status => {
+ return testList[status];
+ }),
+ );
+ });
+
+ return <>{items}>;
+}
diff --git a/arewerslintyet/app/HeatMapDevelopment.tsx b/arewerslintyet/app/HeatMapDevelopment.tsx
new file mode 100644
index 00000000..f1528b7f
--- /dev/null
+++ b/arewerslintyet/app/HeatMapDevelopment.tsx
@@ -0,0 +1,16 @@
+import { getDevelopmentLintResults } from './data';
+import { HeapMap } from './HeatMap';
+
+export default async function HeatMapDevelopment() {
+ const data = await getDevelopmentLintResults();
+
+ if (!data) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/arewerslintyet/app/HeatMapExamples.tsx b/arewerslintyet/app/HeatMapExamples.tsx
new file mode 100644
index 00000000..94caaf79
--- /dev/null
+++ b/arewerslintyet/app/HeatMapExamples.tsx
@@ -0,0 +1,30 @@
+import { getRuleExamplesResults } from './data';
+import HeatMapItem from './HeatMapItem';
+
+export async function HeapMapExamples() {
+ const ruleExamplesResult = await getRuleExamplesResults();
+
+ if (!ruleExamplesResult || Object.keys(ruleExamplesResult).length === 0) {
+ return null;
+ }
+
+ let items = [];
+ for (const ruleName in ruleExamplesResult) {
+ const isPassing = ruleExamplesResult[ruleName];
+ items.push(
+ ,
+ );
+ }
+
+ return (
+ <>
+ Rule Examples
+
+ >
+ );
+}
diff --git a/arewerslintyet/app/HeatMapItem.tsx b/arewerslintyet/app/HeatMapItem.tsx
new file mode 100644
index 00000000..79041a41
--- /dev/null
+++ b/arewerslintyet/app/HeatMapItem.tsx
@@ -0,0 +1,31 @@
+'use client';
+
+import React, { useEffect } from 'react';
+import { twJoin } from 'tailwind-merge';
+import { useTooltip } from './TooltipContext';
+
+function HeatMapItem({ tooltipContent, href, isPassing }) {
+ const { onMouseOver, onMouseOut } = useTooltip();
+ const handleMouseOver = event => {
+ onMouseOver(event, tooltipContent, isPassing ? 'passing' : 'failing');
+ };
+
+ return (
+ // biome-ignore lint/a11y/useAnchorContent: aria-label is sufficient
+
+ );
+}
+
+export default React.memo(HeatMapItem);
diff --git a/arewerslintyet/app/HeatMapProduction.tsx b/arewerslintyet/app/HeatMapProduction.tsx
new file mode 100644
index 00000000..d26b6702
--- /dev/null
+++ b/arewerslintyet/app/HeatMapProduction.tsx
@@ -0,0 +1,16 @@
+import { getProductionLintResults } from './data';
+import { HeapMap } from './HeatMap';
+
+export default async function HeatMapProduction() {
+ const data = await getProductionLintResults();
+
+ if (!data) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/arewerslintyet/app/IsItReady.tsx b/arewerslintyet/app/IsItReady.tsx
new file mode 100644
index 00000000..8a1dc8b7
--- /dev/null
+++ b/arewerslintyet/app/IsItReady.tsx
@@ -0,0 +1,64 @@
+import { Bundler, getBundler } from './bundler';
+
+interface Props {
+ title: string;
+ description: string;
+ percent: number;
+ decision?: 'yes' | 'no' | null;
+}
+
+const RSPACK_WARNING = (
+
+ next-rspack is currently experimental. It's not an official Next.js plugin,
+ and is supported by the Rspack team in partnership with Next.js. Help
+ improve Next.js and Rspack{' '}
+
+ by providing feedback
+
+ .
+
+);
+
+export default function IsItReady({
+ title,
+ description,
+ percent,
+ decision: forcedDecision,
+}: Props) {
+ const decision =
+ forcedDecision === 'yes'
+ ? true
+ : forcedDecision === 'no'
+ ? false
+ : percent === 100;
+
+ return (
+
+ {decision ? (
+
+ {title}: YES
+
+ {'\ud83c\udf89'}
+
+
+ ) : (
+ <>
+
+ {title}: NO
+
+
+ {percent}% of Next.js{' '}
+ {description} are passing though
+
+ {'\u2705'}
+
+
+ >
+ )}
+ {getBundler() === Bundler.Rspack && RSPACK_WARNING}
+
+ );
+}
diff --git a/arewerslintyet/app/IsItReadyDevelopment.tsx b/arewerslintyet/app/IsItReadyDevelopment.tsx
new file mode 100644
index 00000000..b53718b2
--- /dev/null
+++ b/arewerslintyet/app/IsItReadyDevelopment.tsx
@@ -0,0 +1,20 @@
+import { Linter, getLinter } from './bundler';
+import { getDevelopmentLintRuns } from './data';
+import IsItReady from './IsItReady';
+
+export default async function IsItReadyDevelopment() {
+ const { mostRecent } = await getDevelopmentLintRuns();
+
+ if (!mostRecent) {
+ return null;
+ }
+
+ return (
+ = 90 ? 'yes' : 'no'}
+ />
+ );
+}
diff --git a/arewerslintyet/app/IsitReadyProduction.tsx b/arewerslintyet/app/IsitReadyProduction.tsx
new file mode 100644
index 00000000..73f8371b
--- /dev/null
+++ b/arewerslintyet/app/IsitReadyProduction.tsx
@@ -0,0 +1,20 @@
+import { Linter, getLinter } from './bundler';
+import { getProductionLintRuns } from './data';
+import IsItReady from './IsItReady';
+
+export default async function IsItReadyProduction() {
+ const { mostRecent } = await getProductionLintRuns();
+
+ if (!mostRecent) {
+ return null;
+ }
+
+ return (
+ = 90 ? 'yes' : 'no'}
+ />
+ );
+}
diff --git a/arewerslintyet/app/ProgressBar.tsx b/arewerslintyet/app/ProgressBar.tsx
new file mode 100644
index 00000000..805fe491
--- /dev/null
+++ b/arewerslintyet/app/ProgressBar.tsx
@@ -0,0 +1,29 @@
+'use client';
+
+import { ModeToggle } from '@/components/ui/dark-mode-toggle';
+import { Linter } from './bundler';
+import Switcher from './Switcher';
+
+export function ProgressBar({ linter, mostRecent, dev }) {
+ const testsLeft = mostRecent.total - mostRecent.passing;
+ return (
+
+
+
+ š¦ RSLint
+
+ {mostRecent.passing} of {mostRecent.total}{' '}
+ {dev ? 'development' : 'production'} lint tests passing
+
+
+
+ ({testsLeft > 0 ? <>{testsLeft} left for 100%> : '100%'})
+
+
+
+
+
+
+
+ );
+}
diff --git a/arewerslintyet/app/ProgressBarDevelopment.tsx b/arewerslintyet/app/ProgressBarDevelopment.tsx
new file mode 100644
index 00000000..9c406715
--- /dev/null
+++ b/arewerslintyet/app/ProgressBarDevelopment.tsx
@@ -0,0 +1,15 @@
+import { getLinter } from './bundler';
+import { getDevelopmentLintRuns } from './data';
+import { ProgressBar } from './ProgressBar';
+
+export default async function ProgressBarDevelopment() {
+ const { mostRecent } = await getDevelopmentLintRuns();
+
+ if (!mostRecent) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/arewerslintyet/app/ProgressBarProduction.tsx b/arewerslintyet/app/ProgressBarProduction.tsx
new file mode 100644
index 00000000..a2ab999a
--- /dev/null
+++ b/arewerslintyet/app/ProgressBarProduction.tsx
@@ -0,0 +1,15 @@
+import { getLinter } from './bundler';
+import { getProductionLintRuns } from './data';
+import { ProgressBar } from './ProgressBar';
+
+export default async function ProgressBarProduction() {
+ const { mostRecent } = await getProductionLintRuns();
+
+ if (!mostRecent) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/arewerslintyet/app/Switcher.tsx b/arewerslintyet/app/Switcher.tsx
new file mode 100644
index 00000000..ecef703a
--- /dev/null
+++ b/arewerslintyet/app/Switcher.tsx
@@ -0,0 +1,40 @@
+'use client';
+import { usePathname, useRouter } from 'next/navigation';
+import { useEffect } from 'react';
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+
+export default function Switcher() {
+ const pathname = usePathname();
+ const router = useRouter();
+ const isProduction = pathname === '/';
+
+ useEffect(() => {
+ router.prefetch(pathname === 'development' ? '/' : '/build');
+ }, [pathname, router.prefetch]);
+
+ return (
+ {
+ router.push(value === 'development' ? '/dev' : '/');
+ }}
+ defaultValue={isProduction ? 'production' : 'development'}
+ >
+
+
+
+
+
+ Development
+ Production
+
+
+
+ );
+}
diff --git a/arewerslintyet/app/TooltipContext.tsx b/arewerslintyet/app/TooltipContext.tsx
new file mode 100644
index 00000000..bc36bf2b
--- /dev/null
+++ b/arewerslintyet/app/TooltipContext.tsx
@@ -0,0 +1,115 @@
+'use client';
+
+import React, {
+ Context,
+ CSSProperties,
+ MouseEvent,
+ ReactNode,
+ useCallback,
+ useContext,
+ useMemo,
+ useState,
+} from 'react';
+
+type TooltipStatus = 'passing' | 'failing';
+
+interface TooltipProps {
+ flip?: boolean;
+ status?: TooltipStatus;
+ left?: number | string;
+ top?: number | string;
+ content?: ReactNode;
+}
+
+const tooltipIcons: Record = {
+ passing: '\u2705',
+ failing: '\u274C',
+};
+
+const tooltipLabels: Record = {
+ passing: 'passing',
+ failing: 'failing',
+};
+
+const Tooltip: React.FC = props => {
+ let contentStyle: CSSProperties = {
+ right: props.flip ? -15 : 'auto',
+ left: props.flip ? 'auto' : -15,
+ };
+
+ let statusRow: ReactNode = null;
+ if (props.status) {
+ let icon = tooltipIcons[props.status];
+ let text = tooltipLabels[props.status];
+ statusRow = (
+
+ {icon}
+ {text}
+
+ );
+ }
+
+ return (
+
+
+ {props.content}
+ {statusRow}
+
+
+ );
+};
+
+interface TooltipContextValue {
+ onMouseOver: (
+ event: UIEvent,
+ content: ReactNode,
+ status: TooltipStatus,
+ ) => void;
+ onMouseOut: () => void;
+}
+
+const TooltipContext: Context =
+ React.createContext(null);
+
+interface TooltipProviderProps {
+ children: ReactNode;
+}
+
+export const TooltipProvider: React.FC = props => {
+ const [data, setData] = useState(null);
+
+ const onMouseOver = useCallback(
+ (event: UIEvent, content: ReactNode, status: TooltipStatus) => {
+ if (!(event.target instanceof Element)) {
+ return;
+ }
+ let rect = event.target.getBoundingClientRect();
+ let left = Math.round(rect.left + rect.width / 2 + window.scrollX);
+ let top = Math.round(rect.top + window.scrollY);
+ let flip = left > document.documentElement.clientWidth / 2;
+ setData({ left, top, content, status, flip });
+ },
+ [],
+ );
+
+ const onMouseOut = useCallback(() => {
+ setData(null);
+ }, []);
+
+ const value = useMemo(
+ () => ({ onMouseOver, onMouseOut }),
+ [onMouseOver, onMouseOut],
+ );
+
+ return (
+
+ {props.children}
+ {data && }
+
+ );
+};
+
+export function useTooltip(): TooltipContextValue | null {
+ const callbacks = useContext(TooltipContext);
+ return callbacks;
+}
diff --git a/arewerslintyet/app/api/revalidate/route.ts b/arewerslintyet/app/api/revalidate/route.ts
new file mode 100644
index 00000000..26af45b5
--- /dev/null
+++ b/arewerslintyet/app/api/revalidate/route.ts
@@ -0,0 +1,48 @@
+import { NextApiRequest, NextApiResponse } from 'next';
+import { type NextRequest, NextResponse } from 'next/server';
+import { revalidateAll } from '@/app/data';
+
+const API_TOKEN = process.env.AREWETURBOYET_TOKEN;
+
+interface RevalidationSuccess {
+ revalidated: true;
+}
+
+interface RevalidationError {
+ error?: string;
+}
+
+type Revalidation = RevalidationSuccess | RevalidationError;
+
+// Revalidates all of the data caches associated with this deployment. Intended
+// to be called from GitHub actions after new data is pushed to the KV store, so
+// it can be reflected immediately in the UI.
+//
+// Note: areweturboyet and arewerspackyet must be revalidated independently, as
+// they're separate vercel projects with separate data caches.
+//
+// Example: https://nextjs.org/docs/app/api-reference/functions/revalidateTag#route-handler
+export async function POST(
+ req: NextRequest,
+): Promise> {
+ // Check for the API key in the request headers. This isn't particularly
+ // sensitive, but it could cost us money if somebody hit it maliciously.
+ const headerToken = req.headers.get('X-Auth-Token');
+ if (!API_TOKEN || headerToken !== API_TOKEN) {
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
+ }
+
+ try {
+ revalidateAll();
+ return NextResponse.json({
+ revalidated: true,
+ });
+ } catch (error) {
+ return NextResponse.json(
+ {
+ error: (error as Error).message,
+ },
+ { status: 500 },
+ );
+ }
+}
diff --git a/arewerslintyet/app/bundler.ts b/arewerslintyet/app/bundler.ts
new file mode 100644
index 00000000..3416a0f8
--- /dev/null
+++ b/arewerslintyet/app/bundler.ts
@@ -0,0 +1,13 @@
+export enum Linter {
+ RSLint = 'rslint',
+}
+
+export function getLinter(): Linter {
+ // Always return RSLint since we're only tracking one linter
+ return Linter.RSLint;
+}
+
+// Legacy function name for compatibility
+export function getBundler(): Linter {
+ return getLinter();
+}
diff --git a/arewerslintyet/app/data.tsx b/arewerslintyet/app/data.tsx
new file mode 100644
index 00000000..1be2f372
--- /dev/null
+++ b/arewerslintyet/app/data.tsx
@@ -0,0 +1,119 @@
+import 'server-only';
+import { kv } from '@vercel/kv';
+import { revalidateTag, unstable_cache } from 'next/cache';
+import { Linter, getLinter } from './bundler';
+
+const kvPrefix = 'rslint-';
+const linterTag = 'rslint';
+
+export function revalidateAll() {
+ revalidateTag(linterTag);
+}
+
+function processGraphData(rawGraphData: string[]) {
+ return rawGraphData
+ .map(string => {
+ const [gitHash, dateStr, progress] = string.split(/[\t]/);
+ // convert to a unix epoch timestamp
+ const date = Date.parse(dateStr);
+ const [passing, total] = progress.split(/\//).map(parseFloat);
+ const percent = parseFloat(((passing / total) * 100).toFixed(1));
+
+ return {
+ gitHash: gitHash.slice(0, 7),
+ date,
+ total,
+ passing,
+ percent,
+ };
+ })
+ .filter(({ date, percent }) => Number.isFinite(date) && percent > 0);
+}
+
+export const getDevelopmentLintResults = unstable_cache(
+ async () => {
+ const [failing, passing] = await Promise.all([
+ kv.get(`${kvPrefix}failing-lint-tests`),
+ kv.get(`${kvPrefix}passing-lint-tests`),
+ ]);
+
+ if (failing === null && passing === null) {
+ return null;
+ }
+
+ return { passing, failing };
+ },
+ [kvPrefix, 'lint-results-new'],
+ {
+ tags: [linterTag],
+ revalidate: 600,
+ },
+);
+
+export const getProductionLintResults = unstable_cache(
+ async () => {
+ const [failing, passing] = await Promise.all([
+ kv.get(`${kvPrefix}failing-lint-tests-production`),
+ kv.get(`${kvPrefix}passing-lint-tests-production`),
+ ]);
+
+ if (failing === null && passing === null) {
+ return null;
+ }
+
+ return { passing, failing };
+ },
+ [kvPrefix, 'lint-results-new-production'],
+ {
+ tags: [linterTag],
+ revalidate: 600,
+ },
+);
+
+export const getRuleExamplesResults = unstable_cache(
+ async () => {
+ const data: { [ruleName: string]: /* isPassing */ boolean } = await kv.get(
+ `${kvPrefix}rule-examples-data`,
+ );
+ return data;
+ },
+ [kvPrefix, 'rule-examples-results'],
+ {
+ tags: [linterTag],
+ revalidate: 600,
+ },
+);
+
+export const getDevelopmentLintRuns = unstable_cache(
+ async () => {
+ const [graphData] = await Promise.all([
+ kv.lrange(`${kvPrefix}lint-runs`, 0, -1).then(processGraphData),
+ ]);
+
+ const mostRecent = graphData[graphData.length - 1];
+ return { graphData, mostRecent };
+ },
+ [kvPrefix, 'lint-runs-new'],
+ {
+ tags: [linterTag],
+ revalidate: 600,
+ },
+);
+
+export const getProductionLintRuns = unstable_cache(
+ async () => {
+ const [graphData] = await Promise.all([
+ kv
+ .lrange(`${kvPrefix}lint-runs-production`, 0, -1)
+ .then(processGraphData),
+ ]);
+
+ const mostRecent = graphData[graphData.length - 1];
+ return { graphData, mostRecent };
+ },
+ [kvPrefix, 'lint-runs-new-production'],
+ {
+ tags: [linterTag],
+ revalidate: 600,
+ },
+);
diff --git a/arewerslintyet/app/dev/page.tsx b/arewerslintyet/app/dev/page.tsx
new file mode 100644
index 00000000..c4546e55
--- /dev/null
+++ b/arewerslintyet/app/dev/page.tsx
@@ -0,0 +1,39 @@
+import { HeapMapExamples } from 'app/HeatMapExamples';
+import { Suspense } from 'react';
+import Footer from '../Footer';
+import GraphDataDevelopment from '../GraphDataDevelopment';
+import HeatMapDevelopment from '../HeatMapDevelopment';
+import IsItReadyDevelopment from '../IsItReadyDevelopment';
+import ProgressBarDevelopment from '../ProgressBarDevelopment';
+import { TooltipProvider } from '../TooltipContext';
+
+export default function DevelopmentPage() {
+ return (
+
+ {/* Development */}
+
+
+
+
+
+
+
+
+
+
+
Lint Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/arewerslintyet/app/geist-design-system.css b/arewerslintyet/app/geist-design-system.css
new file mode 100644
index 00000000..363e41cc
--- /dev/null
+++ b/arewerslintyet/app/geist-design-system.css
@@ -0,0 +1,580 @@
+/*
+ * Copied from:
+ * https://github.com/vercel/turborepo/blob/fe92aba0d48a/docs/site/app/design-system.css
+ */
+
+:root,
+:host,
+[data-theme='dark'] .invert-theme,
+.dark-theme .invert-theme,
+.dark .invert-theme {
+ /* Seperate HSL values from opacity, so opacity can be dynamic for TailwindCSS */
+ /* Gray */
+ --ds-gray-100-value: 0, 0%, 95%;
+ --ds-gray-200-value: 0, 0%, 92%;
+ --ds-gray-300-value: 0, 0%, 90%;
+ --ds-gray-400-value: 0, 0%, 92%;
+ --ds-gray-500-value: 0, 0%, 79%;
+ --ds-gray-600-value: 0, 0%, 66%;
+ --ds-gray-700-value: 0, 0%, 56%;
+ --ds-gray-800-value: 0, 0%, 49%;
+ --ds-gray-900-value: 0, 0%, 40%;
+ --ds-gray-1000-value: 0, 0%, 9%;
+
+ /* Blue */
+ --ds-blue-100-value: 212, 100%, 97%;
+ --ds-blue-200-value: 210, 100%, 96%;
+ --ds-blue-300-value: 210, 100%, 94%;
+ --ds-blue-400-value: 209, 100%, 90%;
+ --ds-blue-500-value: 209, 100%, 80%;
+ --ds-blue-600-value: 208, 100%, 66%;
+ --ds-blue-700-value: 212, 100%, 48%;
+ --ds-blue-800-value: 212, 100%, 41%;
+ --ds-blue-900-value: 211, 100%, 42%;
+ --ds-blue-1000-value: 211, 100%, 15%;
+
+ /* Red */
+ --ds-red-100-value: 0, 100%, 97%;
+ --ds-red-200-value: 0, 100%, 96%;
+ --ds-red-300-value: 0, 100%, 95%;
+ --ds-red-400-value: 0, 90%, 92%;
+ --ds-red-500-value: 0, 82%, 85%;
+ --ds-red-600-value: 359, 90%, 71%;
+ --ds-red-700-value: 358, 75%, 59%;
+ --ds-red-800-value: 358, 70%, 52%;
+ --ds-red-900-value: 358, 66%, 48%;
+ --ds-red-1000-value: 355, 49%, 15%;
+
+ /* Amber */
+ --ds-amber-100-value: 39, 100%, 95%;
+ --ds-amber-200-value: 44, 100%, 92%;
+ --ds-amber-300-value: 43, 96%, 90%;
+ --ds-amber-400-value: 42, 100%, 78%;
+ --ds-amber-500-value: 38, 100%, 71%;
+ --ds-amber-600-value: 36, 90%, 62%;
+ --ds-amber-700-value: 39, 100%, 57%;
+ --ds-amber-800-value: 35, 100%, 52%;
+ --ds-amber-900-value: 30, 100%, 32%;
+ --ds-amber-1000-value: 20, 79%, 17%;
+
+ /* Green */
+ --ds-green-100-value: 120, 60%, 96%;
+ --ds-green-200-value: 120, 60%, 95%;
+ --ds-green-300-value: 120, 60%, 91%;
+ --ds-green-400-value: 122, 60%, 86%;
+ --ds-green-500-value: 124, 60%, 75%;
+ --ds-green-600-value: 125, 60%, 64%;
+ --ds-green-700-value: 131, 41%, 46%;
+ --ds-green-800-value: 132, 43%, 39%;
+ --ds-green-900-value: 133, 50%, 32%;
+ --ds-green-1000-value: 128, 29%, 15%;
+
+ /* Teal */
+ --ds-teal-100-value: 169, 70%, 96%;
+ --ds-teal-200-value: 167, 70%, 94%;
+ --ds-teal-300-value: 168, 70%, 90%;
+ --ds-teal-400-value: 170, 70%, 85%;
+ --ds-teal-500-value: 170, 70%, 72%;
+ --ds-teal-600-value: 170, 70%, 57%;
+ --ds-teal-700-value: 173, 80%, 36%;
+ --ds-teal-800-value: 173, 83%, 30%;
+ --ds-teal-900-value: 174, 91%, 25%;
+ --ds-teal-1000-value: 171, 80%, 13%;
+
+ /* Purple */
+ --ds-purple-100-value: 276, 100%, 97%;
+ --ds-purple-200-value: 277, 87%, 97%;
+ --ds-purple-300-value: 274, 78%, 95%;
+ --ds-purple-400-value: 276, 71%, 92%;
+ --ds-purple-500-value: 274, 70%, 82%;
+ --ds-purple-600-value: 273, 72%, 73%;
+ --ds-purple-700-value: 272, 51%, 54%;
+ --ds-purple-800-value: 272, 47%, 45%;
+ --ds-purple-900-value: 274, 71%, 43%;
+ --ds-purple-1000-value: 276, 100%, 15%;
+
+ /* Pink */
+ --ds-pink-100-value: 330, 100%, 96%;
+ --ds-pink-200-value: 340, 90%, 96%;
+ --ds-pink-300-value: 340, 82%, 94%;
+ --ds-pink-400-value: 341, 76%, 91%;
+ --ds-pink-500-value: 340, 75%, 84%;
+ --ds-pink-600-value: 341, 75%, 73%;
+ --ds-pink-700-value: 336, 80%, 58%;
+ --ds-pink-800-value: 336, 74%, 51%;
+ --ds-pink-900-value: 336, 65%, 45%;
+ --ds-pink-1000-value: 333, 74%, 15%;
+
+ --ds-gray-alpha-100: rgba(0, 0, 0, 0.05);
+ --ds-gray-alpha-200: hsla(0, 0%, 0%, 0.08);
+ --ds-gray-alpha-300: hsla(0, 0%, 0%, 0.1);
+ --ds-gray-alpha-400: hsla(0, 0%, 0%, 0.08);
+ --ds-gray-alpha-500: hsla(0, 0%, 0%, 0.21);
+ --ds-gray-alpha-600: hsla(0, 0%, 0%, 0.34);
+ --ds-gray-alpha-700: hsla(0, 0%, 0%, 0.44);
+ --ds-gray-alpha-800: hsla(0, 0%, 0%, 0.51);
+ --ds-gray-alpha-900: hsla(0, 0%, 0%, 0.61);
+ --ds-gray-alpha-1000: hsla(0, 0%, 0%, 0.91);
+
+ --ds-background-100-value: 0, 0%, 100%;
+ --ds-background-200-value: 0, 0%, 98%;
+ --ds-focus-border:
+ 0 0 0 1px var(--ds-gray-alpha-600), 0px 0px 0px 4px rgba(0, 0, 0, 0.16);
+ --ds-focus-color: var(--ds-blue-700);
+
+ --ds-shadow-border: 0 0 0 1px rgba(0, 0, 0, 0.08);
+ --ds-shadow-border-inset: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
+ --ds-shadow-small: 0px 2px 2px rgba(0, 0, 0, 0.04);
+ --ds-shadow-border-small: var(--ds-shadow-border), var(--ds-shadow-small);
+ --ds-shadow-medium:
+ 0px 2px 2px rgba(0, 0, 0, 0.04), 0px 8px 8px -8px rgba(0, 0, 0, 0.04);
+ --ds-shadow-border-medium: var(--ds-shadow-border), var(--ds-shadow-medium);
+ --ds-shadow-large:
+ 0px 2px 2px rgba(0, 0, 0, 0.04), 0px 8px 16px -4px rgba(0, 0, 0, 0.04);
+ --ds-shadow-border-large: var(--ds-shadow-border), var(--ds-shadow-large);
+ --ds-shadow-tooltip:
+ var(--ds-shadow-border), 0px 1px 1px rgba(0, 0, 0, 0.02),
+ 0px 4px 8px rgba(0, 0, 0, 0.04);
+ --ds-shadow-menu:
+ var(--ds-shadow-border), 0px 1px 1px rgba(0, 0, 0, 0.02),
+ 0px 4px 8px -4px rgba(0, 0, 0, 0.04), 0px 16px 24px -8px rgba(0, 0, 0, 0.06);
+ --ds-shadow-modal:
+ var(--ds-shadow-border), 0px 1px 1px rgba(0, 0, 0, 0.02),
+ 0px 8px 16px -4px rgba(0, 0, 0, 0.04),
+ 0px 24px 32px -8px rgba(0, 0, 0, 0.06);
+ --ds-shadow-fullscreen:
+ var(--ds-shadow-border), 0px 1px 1px rgba(0, 0, 0, 0.02),
+ 0px 8px 16px -4px rgba(0, 0, 0, 0.04),
+ 0px 24px 32px -8px rgba(0, 0, 0, 0.06);
+}
+
+:root,
+:host,
+[data-theme='dark'],
+.dark,
+.dark-theme,
+.invert-theme {
+ /* Wrap values to actual color Variables intended for use */
+ --ds-gray-100: hsla(var(--ds-gray-100-value), 1);
+ --ds-gray-200: hsla(var(--ds-gray-200-value), 1);
+ --ds-gray-300: hsla(var(--ds-gray-300-value), 1);
+ --ds-gray-400: hsla(var(--ds-gray-400-value), 1);
+ --ds-gray-500: hsla(var(--ds-gray-500-value), 1);
+ --ds-gray-600: hsla(var(--ds-gray-600-value), 1);
+ --ds-gray-700: hsla(var(--ds-gray-700-value), 1);
+ --ds-gray-800: hsla(var(--ds-gray-800-value), 1);
+ --ds-gray-900: hsla(var(--ds-gray-900-value), 1);
+ --ds-gray-1000: hsla(var(--ds-gray-1000-value), 1);
+
+ --ds-blue-100: hsla(var(--ds-blue-100-value), 1);
+ --ds-blue-200: hsla(var(--ds-blue-200-value), 1);
+ --ds-blue-300: hsla(var(--ds-blue-300-value), 1);
+ --ds-blue-400: hsla(var(--ds-blue-400-value), 1);
+ --ds-blue-500: hsla(var(--ds-blue-500-value), 1);
+ --ds-blue-600: hsla(var(--ds-blue-600-value), 1);
+ --ds-blue-700: hsla(var(--ds-blue-700-value), 1);
+ --ds-blue-800: hsla(var(--ds-blue-800-value), 1);
+ --ds-blue-900: hsla(var(--ds-blue-900-value), 1);
+ --ds-blue-1000: hsla(var(--ds-blue-1000-value), 1);
+
+ --ds-red-100: hsla(var(--ds-red-100-value), 1);
+ --ds-red-200: hsla(var(--ds-red-200-value), 1);
+ --ds-red-300: hsla(var(--ds-red-300-value), 1);
+ --ds-red-400: hsla(var(--ds-red-400-value), 1);
+ --ds-red-500: hsla(var(--ds-red-500-value), 1);
+ --ds-red-600: hsla(var(--ds-red-600-value), 1);
+ --ds-red-700: hsla(var(--ds-red-700-value), 1);
+ --ds-red-800: hsla(var(--ds-red-800-value), 1);
+ --ds-red-900: hsla(var(--ds-red-900-value), 1);
+ --ds-red-1000: hsla(var(--ds-red-1000-value), 1);
+
+ --ds-red-100: hsla(var(--ds-red-100-value), 1);
+ --ds-red-200: hsla(var(--ds-red-200-value), 1);
+ --ds-red-300: hsla(var(--ds-red-300-value), 1);
+ --ds-red-400: hsla(var(--ds-red-400-value), 1);
+ --ds-red-500: hsla(var(--ds-red-500-value), 1);
+ --ds-red-600: hsla(var(--ds-red-600-value), 1);
+ --ds-red-700: hsla(var(--ds-red-700-value), 1);
+ --ds-red-800: hsla(var(--ds-red-800-value), 1);
+ --ds-red-900: hsla(var(--ds-red-900-value), 1);
+ --ds-red-1000: hsla(var(--ds-red-1000-value), 1);
+
+ --ds-amber-100: hsla(var(--ds-amber-100-value), 1);
+ --ds-amber-200: hsla(var(--ds-amber-200-value), 1);
+ --ds-amber-300: hsla(var(--ds-amber-300-value), 1);
+ --ds-amber-400: hsla(var(--ds-amber-400-value), 1);
+ --ds-amber-500: hsla(var(--ds-amber-500-value), 1);
+ --ds-amber-600: hsla(var(--ds-amber-600-value), 1);
+ --ds-amber-700: hsla(var(--ds-amber-700-value), 1);
+ --ds-amber-800: hsla(var(--ds-amber-800-value), 1);
+ --ds-amber-900: hsla(var(--ds-amber-900-value), 1);
+ --ds-amber-1000: hsla(var(--ds-amber-1000-value), 1);
+
+ --ds-red-100: hsla(var(--ds-red-100-value), 1);
+ --ds-red-200: hsla(var(--ds-red-200-value), 1);
+ --ds-red-300: hsla(var(--ds-red-300-value), 1);
+ --ds-red-400: hsla(var(--ds-red-400-value), 1);
+ --ds-red-500: hsla(var(--ds-red-500-value), 1);
+ --ds-red-600: hsla(var(--ds-red-600-value), 1);
+ --ds-red-700: hsla(var(--ds-red-700-value), 1);
+ --ds-red-800: hsla(var(--ds-red-800-value), 1);
+ --ds-red-900: hsla(var(--ds-red-900-value), 1);
+ --ds-red-1000: hsla(var(--ds-red-1000-value), 1);
+
+ --ds-green-100: hsla(var(--ds-green-100-value), 1);
+ --ds-green-200: hsla(var(--ds-green-200-value), 1);
+ --ds-green-300: hsla(var(--ds-green-300-value), 1);
+ --ds-green-400: hsla(var(--ds-green-400-value), 1);
+ --ds-green-500: hsla(var(--ds-green-500-value), 1);
+ --ds-green-600: hsla(var(--ds-green-600-value), 1);
+ --ds-green-700: hsla(var(--ds-green-700-value), 1);
+ --ds-green-800: hsla(var(--ds-green-800-value), 1);
+ --ds-green-900: hsla(var(--ds-green-900-value), 1);
+ --ds-green-1000: hsla(var(--ds-green-1000-value), 1);
+
+ --ds-teal-100: hsla(var(--ds-teal-100-value), 1);
+ --ds-teal-200: hsla(var(--ds-teal-200-value), 1);
+ --ds-teal-300: hsla(var(--ds-teal-300-value), 1);
+ --ds-teal-400: hsla(var(--ds-teal-400-value), 1);
+ --ds-teal-500: hsla(var(--ds-teal-500-value), 1);
+ --ds-teal-600: hsla(var(--ds-teal-600-value), 1);
+ --ds-teal-700: hsla(var(--ds-teal-700-value), 1);
+ --ds-teal-800: hsla(var(--ds-teal-800-value), 1);
+ --ds-teal-900: hsla(var(--ds-teal-900-value), 1);
+ --ds-teal-1000: hsla(var(--ds-teal-1000-value), 1);
+
+ --ds-purple-100: hsla(var(--ds-purple-100-value), 1);
+ --ds-purple-200: hsla(var(--ds-purple-200-value), 1);
+ --ds-purple-300: hsla(var(--ds-purple-300-value), 1);
+ --ds-purple-400: hsla(var(--ds-purple-400-value), 1);
+ --ds-purple-500: hsla(var(--ds-purple-500-value), 1);
+ --ds-purple-600: hsla(var(--ds-purple-600-value), 1);
+ --ds-purple-700: hsla(var(--ds-purple-700-value), 1);
+ --ds-purple-800: hsla(var(--ds-purple-800-value), 1);
+ --ds-purple-900: hsla(var(--ds-purple-900-value), 1);
+ --ds-purple-1000: hsla(var(--ds-purple-1000-value), 1);
+
+ --ds-pink-100: hsla(var(--ds-pink-100-value), 1);
+ --ds-pink-200: hsla(var(--ds-pink-200-value), 1);
+ --ds-pink-300: hsla(var(--ds-pink-300-value), 1);
+ --ds-pink-400: hsla(var(--ds-pink-400-value), 1);
+ --ds-pink-500: hsla(var(--ds-pink-500-value), 1);
+ --ds-pink-600: hsla(var(--ds-pink-600-value), 1);
+ --ds-pink-700: hsla(var(--ds-pink-700-value), 1);
+ --ds-pink-800: hsla(var(--ds-pink-800-value), 1);
+ --ds-pink-900: hsla(var(--ds-pink-900-value), 1);
+ --ds-pink-1000: hsla(var(--ds-pink-1000-value), 1);
+
+ --ds-background-100: hsla(var(--ds-background-100-value), 1);
+ --ds-background-200: hsla(var(--ds-background-200-value), 1);
+}
+
+[data-theme='dark'],
+.dark,
+.dark-theme,
+.invert-theme {
+ --ds-gray-100-value: 0, 0%, 10%;
+ --ds-gray-200-value: 0, 0%, 12%;
+ --ds-gray-300-value: 0, 0%, 16%;
+ --ds-gray-400-value: 0, 0%, 18%;
+ --ds-gray-500-value: 0, 0%, 27%;
+ --ds-gray-600-value: 0, 0%, 53%;
+ --ds-gray-700-value: 0, 0%, 56%;
+ --ds-gray-800-value: 0, 0%, 49%;
+ --ds-gray-900-value: 0, 0%, 63%;
+ --ds-gray-1000-value: 0, 0%, 93%;
+
+ --ds-blue-100-value: 216, 50%, 12%;
+ --ds-blue-200-value: 214, 59%, 15%;
+ --ds-blue-300-value: 213, 71%, 20%;
+ --ds-blue-400-value: 212, 78%, 23%;
+ --ds-blue-500-value: 211, 86%, 27%;
+ --ds-blue-600-value: 206, 100%, 50%;
+ --ds-blue-700-value: 212, 100%, 48%;
+ --ds-blue-800-value: 212, 100%, 41%;
+ --ds-blue-900-value: 210, 100%, 66%;
+ --ds-blue-1000-value: 206, 100%, 96%;
+
+ --ds-red-100-value: 357, 37%, 12%;
+ --ds-red-200-value: 357, 46%, 16%;
+ --ds-red-300-value: 356, 54%, 22%;
+ --ds-red-400-value: 357, 55%, 26%;
+ --ds-red-500-value: 357, 60%, 32%;
+ --ds-red-600-value: 358, 75%, 59%;
+ --ds-red-700-value: 358, 75%, 59%;
+ --ds-red-800-value: 358, 69%, 52%;
+ --ds-red-900-value: 358, 100%, 69%;
+ --ds-red-1000-value: 353, 90%, 96%;
+
+ --ds-amber-100-value: 35, 100%, 8%;
+ --ds-amber-200-value: 32, 100%, 10%;
+ --ds-amber-300-value: 33, 100%, 15%;
+ --ds-amber-400-value: 35, 100%, 17%;
+ --ds-amber-500-value: 35, 91%, 22%;
+ --ds-amber-600-value: 39, 85%, 49%;
+ --ds-amber-700-value: 39, 100%, 57%;
+ --ds-amber-800-value: 35, 100%, 52%;
+ --ds-amber-900-value: 35, 100%, 52%;
+ --ds-amber-1000-value: 40, 94%, 93%;
+
+ --ds-green-100-value: 136, 50%, 9%;
+ --ds-green-200-value: 137, 50%, 12%;
+ --ds-green-300-value: 136, 50%, 14%;
+ --ds-green-400-value: 135, 70%, 16%;
+ --ds-green-500-value: 135, 70%, 23%;
+ --ds-green-600-value: 135, 70%, 34%;
+ --ds-green-700-value: 131, 41%, 46%;
+ --ds-green-800-value: 132, 43%, 39%;
+ --ds-green-900-value: 131, 43%, 57%;
+ --ds-green-1000-value: 136, 73%, 94%;
+
+ --ds-teal-100-value: 169, 78%, 7%;
+ --ds-teal-200-value: 170, 74%, 9%;
+ --ds-teal-300-value: 171, 75%, 13%;
+ --ds-teal-400-value: 171, 85%, 13%;
+ --ds-teal-500-value: 172, 85%, 20%;
+ --ds-teal-600-value: 172, 85%, 32%;
+ --ds-teal-700-value: 173, 80%, 36%;
+ --ds-teal-800-value: 173, 83%, 30%;
+ --ds-teal-900-value: 174, 90%, 41%;
+ --ds-teal-1000-value: 166, 71%, 93%;
+
+ --ds-purple-100-value: 283, 30%, 12%;
+ --ds-purple-200-value: 281, 38%, 16%;
+ --ds-purple-300-value: 279, 44%, 23%;
+ --ds-purple-400-value: 277, 46%, 28%;
+ --ds-purple-500-value: 274, 49%, 35%;
+ --ds-purple-600-value: 272, 51%, 54%;
+ --ds-purple-700-value: 272, 51%, 54%;
+ --ds-purple-800-value: 272, 47%, 45%;
+ --ds-purple-900-value: 275, 80%, 71%;
+ --ds-purple-1000-value: 281, 73%, 96%;
+
+ --ds-pink-100-value: 335, 32%, 12%;
+ --ds-pink-200-value: 335, 43%, 16%;
+ --ds-pink-300-value: 335, 47%, 21%;
+ --ds-pink-400-value: 335, 51%, 22%;
+ --ds-pink-500-value: 335, 57%, 27%;
+ --ds-pink-600-value: 336, 75%, 40%;
+ --ds-pink-700-value: 336, 80%, 58%;
+ --ds-pink-800-value: 336, 74%, 51%;
+ --ds-pink-900-value: 341, 90%, 67%;
+ --ds-pink-1000-value: 333, 90%, 96%;
+
+ --ds-gray-alpha-100: rgba(255, 255, 255, 0.06);
+ --ds-gray-alpha-200: hsla(0, 0%, 100%, 0.09);
+ --ds-gray-alpha-300: hsla(0, 0%, 100%, 0.13);
+ --ds-gray-alpha-400: hsla(0, 0%, 100%, 0.14);
+ --ds-gray-alpha-500: hsla(0, 0%, 100%, 0.24);
+ --ds-gray-alpha-600: hsla(0, 0%, 100%, 0.51);
+ --ds-gray-alpha-700: hsla(0, 0%, 100%, 0.54);
+ --ds-gray-alpha-800: hsla(0, 0%, 100%, 0.47);
+ --ds-gray-alpha-900: hsla(0, 0%, 100%, 0.61);
+ --ds-gray-alpha-1000: hsla(0, 0%, 100%, 0.92);
+
+ --ds-background-100-value: 0, 0%, 4%;
+ --ds-background-200-value: 0, 0%, 0%;
+ --ds-focus-border:
+ 0 0 0 1px var(--ds-gray-alpha-600),
+ 0px 0px 0px 4px rgba(255, 255, 255, 0.24);
+ --ds-focus-color: var(--ds-blue-900);
+ --ds-shadow-border-inset: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
+
+ --ds-shadow-border: 0 0 0 1px rgba(255, 255, 255, 0.145);
+ --ds-shadow-small: 0px 1px 2px rgba(0, 0, 0, 0.16);
+ --ds-shadow-border-small:
+ var(--ds-shadow-border), 0px 1px 2px rgba(0, 0, 0, 0.16);
+ --ds-shadow-medium:
+ 0px 2px 2px rgba(0, 0, 0, 0.32), 0px 8px 8px -8px rgba(0, 0, 0, 0.16);
+ --ds-shadow-border-medium:
+ var(--ds-shadow-border), 0px 2px 2px rgba(0, 0, 0, 0.32),
+ 0px 8px 8px -8px rgba(0, 0, 0, 0.16);
+ --ds-shadow-large:
+ 0px 2px 2px rgba(0, 0, 0, 0.04), 0px 8px 16px -4px rgba(0, 0, 0, 0.04);
+ --ds-shadow-border-large:
+ var(--ds-shadow-border), 0px 2px 2px rgba(0, 0, 0, 0.04),
+ 0px 8px 16px -4px rgba(0, 0, 0, 0.04);
+ --ds-shadow-tooltip:
+ var(--ds-shadow-border), 0px 1px 1px rgba(0, 0, 0, 0.02),
+ 0px 4px 8px rgba(0, 0, 0, 0.04);
+ --ds-shadow-menu:
+ var(--ds-shadow-border), 0px 1px 1px rgba(0, 0, 0, 0.02),
+ 0px 4px 8px -4px rgba(0, 0, 0, 0.04), 0px 16px 24px -8px rgba(0, 0, 0, 0.06);
+ --ds-shadow-modal:
+ var(--ds-shadow-border), 0px 1px 1px rgba(0, 0, 0, 0.02),
+ 0px 8px 16px -4px rgba(0, 0, 0, 0.04),
+ 0px 24px 32px -8px rgba(0, 0, 0, 0.06);
+ --ds-shadow-fullscreen:
+ var(--ds-shadow-border), 0px 1px 1px rgba(0, 0, 0, 0.02),
+ 0px 8px 16px -4px rgba(0, 0, 0, 0.04),
+ 0px 24px 32px -8px rgba(0, 0, 0, 0.06);
+}
+
+/* P3 Colors */
+@media (color-gamut: p3) {
+ @supports (color: oklch(0 0 0)) {
+ :root,
+ :host,
+ [data-theme='dark'] .invert-theme,
+ .dark .invert-theme,
+ .dark-theme .invert-theme {
+ --ds-blue-100: oklch(97.32% 0.0141 251.56);
+ --ds-blue-200: oklch(96.29% 0.0195 250.59);
+ --ds-blue-300: oklch(94.58% 0.0293 249.84870859673202);
+ --ds-blue-400: oklch(91.58% 0.0473 245.11621922481282);
+ --ds-blue-500: oklch(82.75% 0.0979 248.48);
+ --ds-blue-600: oklch(73.08% 0.1583 248.133320980386);
+ --ds-blue-700: oklch(57.61% 0.2508 258.23);
+ --ds-blue-800: oklch(51.51% 0.2399 257.85);
+ --ds-blue-900: oklch(53.18% 0.2399 256.9900584162342);
+ --ds-blue-1000: oklch(26.67% 0.1099 254.34);
+
+ --ds-red-100: oklch(96.5% 0.0223 13.09);
+ --ds-red-200: oklch(95.41% 0.0299 14.252646656611997);
+ --ds-red-300: oklch(94.33% 0.0369 15.011509923860523);
+ --ds-red-400: oklch(91.51% 0.0471 19.8);
+ --ds-red-500: oklch(84.47% 0.1018 17.71);
+ --ds-red-600: oklch(71.12% 0.1881 21.22);
+ --ds-red-700: oklch(62.56% 0.2524 23.03);
+ --ds-red-800: oklch(58.19% 0.2482 25.15);
+ --ds-red-900: oklch(54.99% 0.232 25.29);
+ --ds-red-1000: oklch(24.8% 0.1041 18.86);
+
+ --ds-amber-100: oklch(97.48% 0.0331 85.79);
+ --ds-amber-200: oklch(96.81% 0.0495 90.24227879900472);
+ --ds-amber-300: oklch(95.93% 0.0636 90.52);
+ --ds-amber-400: oklch(91.02% 0.1322 88.25);
+ --ds-amber-500: oklch(86.55% 0.1583 79.63);
+ --ds-amber-600: oklch(80.25% 0.1953 73.59);
+ --ds-amber-700: oklch(81.87% 0.1969 76.46);
+ --ds-amber-800: oklch(77.21% 0.1991 64.28);
+ --ds-amber-900: oklch(52.79% 0.1496 54.65);
+ --ds-amber-1000: oklch(30.83% 0.099 45.48);
+
+ --ds-green-100: oklch(97.59% 0.0289 145.42);
+ --ds-green-200: oklch(96.92% 0.037 147.15);
+ --ds-green-300: oklch(94.6% 0.0674 144.23);
+ --ds-green-400: oklch(91.49% 0.0976 146.24);
+ --ds-green-500: oklch(85.45% 0.1627 146.3);
+ --ds-green-600: oklch(80.25% 0.214 145.18);
+ --ds-green-700: oklch(64.58% 0.1746 147.27);
+ --ds-green-800: oklch(57.81% 0.1507 147.5);
+ --ds-green-900: oklch(51.75% 0.1453 147.65);
+ --ds-green-1000: oklch(29.15% 0.1197 147.38);
+
+ --ds-teal-100: oklch(97.72% 0.0359 186.7);
+ --ds-teal-200: oklch(97.06% 0.0347 180.66);
+ --ds-teal-300: oklch(94.92% 0.0478 182.07);
+ --ds-teal-400: oklch(92.76% 0.0718 183.78);
+ --ds-teal-500: oklch(86.88% 0.1344 182.42);
+ --ds-teal-600: oklch(81.5% 0.161 178.96);
+ --ds-teal-700: oklch(64.92% 0.1572 181.95);
+ --ds-teal-800: oklch(57.53% 0.1392 181.66);
+ --ds-teal-900: oklch(52.08% 0.1251 182.93);
+ --ds-teal-1000: oklch(32.11% 0.0788 179.82);
+
+ --ds-purple-100: oklch(96.65% 0.0244 312.1890119359961);
+ --ds-purple-200: oklch(96.73% 0.0228 309.8);
+ --ds-purple-300: oklch(94.85% 0.0364 310.15);
+ --ds-purple-400: oklch(91.77% 0.0614 312.82);
+ --ds-purple-500: oklch(81.26% 0.1409 310.8);
+ --ds-purple-600: oklch(72.07% 0.2083 308.19);
+ --ds-purple-700: oklch(55.5% 0.3008 306.12);
+ --ds-purple-800: oklch(48.58% 0.2638 305.73);
+ --ds-purple-900: oklch(47.18% 0.2579 304);
+ --ds-purple-1000: oklch(23.96% 0.13 305.66);
+
+ --ds-pink-100: oklch(95.69% 0.0359 344.6218910697224);
+ --ds-pink-200: oklch(95.71% 0.0321 353.14);
+ --ds-pink-300: oklch(93.83% 0.0451 356.29);
+ --ds-pink-400: oklch(91.12% 0.0573 358.82);
+ --ds-pink-500: oklch(84.28% 0.0915 356.99);
+ --ds-pink-600: oklch(74.33% 0.1547 0.24);
+ --ds-pink-700: oklch(63.52% 0.238 1.01);
+ --ds-pink-800: oklch(59.51% 0.2339 4.21);
+ --ds-pink-900: oklch(53.5% 0.2058 2.84);
+ --ds-pink-1000: oklch(26% 0.0977 359);
+ }
+
+ .dark,
+ .dark-theme,
+ .invert-theme {
+ --ds-blue-100: oklch(22.17% 0.069 259.89);
+ --ds-blue-200: oklch(25.45% 0.0811 255.8);
+ --ds-blue-300: oklch(30.86% 0.1022 255.21);
+ --ds-blue-400: oklch(34.1% 0.121 254.74);
+ --ds-blue-500: oklch(38.5% 0.1403 254.4);
+ --ds-blue-600: oklch(64.94% 0.1982 251.8131841760864);
+ --ds-blue-700: oklch(57.61% 0.2321 258.23);
+ --ds-blue-800: oklch(51.51% 0.2307 257.85);
+ --ds-blue-900: oklch(71.7% 0.1648 250.79360374054167);
+ --ds-blue-1000: oklch(96.75% 0.0179 242.4234217368056);
+
+ --ds-red-100: oklch(22.1% 0.0657 15.11);
+ --ds-red-200: oklch(25.93% 0.0834 19.02);
+ --ds-red-300: oklch(31.47% 0.1105 20.96);
+ --ds-red-400: oklch(35.27% 0.1273 21.23);
+ --ds-red-500: oklch(40.68% 0.1479 23.16);
+ --ds-red-600: oklch(62.56% 0.2277 23.03);
+ --ds-red-700: oklch(62.56% 0.2234 23.03);
+ --ds-red-800: oklch(58.01% 0.227 25.12);
+ --ds-red-900: oklch(69.96% 0.2136 22.03);
+ --ds-red-1000: oklch(95.6% 0.0293 6.61);
+
+ --ds-amber-100: oklch(22.46% 0.0538 76.04);
+ --ds-amber-200: oklch(24.95% 0.0642 64.78);
+ --ds-amber-300: oklch(32.34% 0.0837 63.83);
+ --ds-amber-400: oklch(35.53% 0.0903 66.29707162673735);
+ --ds-amber-500: oklch(41.55% 0.1044 67.98);
+ --ds-amber-600: oklch(75.04% 0.1737 74.49);
+ --ds-amber-700: oklch(81.87% 0.1969 76.46);
+ --ds-amber-800: oklch(77.21% 0.1991 64.28);
+ --ds-amber-900: oklch(77.21% 0.1991 64.28);
+ --ds-amber-1000: oklch(96.7% 0.0418 84.59);
+
+ --ds-green-100: oklch(23.09% 0.0716 149.68);
+ --ds-green-200: oklch(27.12% 0.0895 150.09);
+ --ds-green-300: oklch(29.84% 0.096 149.25);
+ --ds-green-400: oklch(34.39% 0.1039 147.78);
+ --ds-green-500: oklch(44.19% 0.1484 147.2);
+ --ds-green-600: oklch(58.11% 0.1815 146.55);
+ --ds-green-700: oklch(64.58% 0.199 147.27);
+ --ds-green-800: oklch(57.81% 0.1776 147.5);
+ --ds-green-900: oklch(73.1% 0.2158 148.29);
+ --ds-green-1000: oklch(96.76% 0.056 154.18);
+
+ --ds-teal-100: oklch(22.1% 0.0544 178.74);
+ --ds-teal-200: oklch(25.06% 0.062 178.76);
+ --ds-teal-300: oklch(31.5% 0.0767 180.99);
+ --ds-teal-400: oklch(32.43% 0.0763 180.13);
+ --ds-teal-500: oklch(43.35% 0.1055 180.97);
+ --ds-teal-600: oklch(60.71% 0.1485 180.24);
+ --ds-teal-700: oklch(64.92% 0.1403 181.95);
+ --ds-teal-800: oklch(57.53% 0.1392 181.66);
+ --ds-teal-900: oklch(74.56% 0.1765 182.8);
+ --ds-teal-1000: oklch(96.46% 0.056 180.29);
+
+ --ds-purple-100: oklch(22.34% 0.0779 316.87);
+ --ds-purple-200: oklch(25.91% 0.0921 314.41);
+ --ds-purple-300: oklch(31.98% 0.1219 312.41);
+ --ds-purple-400: oklch(35.93% 0.1504 309.78);
+ --ds-purple-500: oklch(40.99% 0.1721 307.92);
+ --ds-purple-600: oklch(55.5% 0.2191 306.12);
+ --ds-purple-700: oklch(55.5% 0.2186 306.12);
+ --ds-purple-800: oklch(48.58% 0.2102 305.73);
+ --ds-purple-900: oklch(69.87% 0.2037 309.51);
+ --ds-purple-1000: oklch(96.1% 0.0304 316.46);
+
+ --ds-pink-100: oklch(22.67% 0.0628 354.73);
+ --ds-pink-200: oklch(26.2% 0.0859 356.68);
+ --ds-pink-300: oklch(31.15% 0.1067 355.93);
+ --ds-pink-400: oklch(32.13% 0.1174 356.71);
+ --ds-pink-500: oklch(37.01% 0.1453 358.39);
+ --ds-pink-600: oklch(50.33% 0.2089 4.33);
+ --ds-pink-700: oklch(63.52% 0.2346 1.01);
+ --ds-pink-800: oklch(59.51% 0.2429 4.21);
+ --ds-pink-900: oklch(69.36% 0.2223 3.91);
+ --ds-pink-1000: oklch(95.74% 0.0326 350.08);
+ }
+ }
+}
diff --git a/arewerslintyet/app/globals.css b/arewerslintyet/app/globals.css
new file mode 100644
index 00000000..1bfa8124
--- /dev/null
+++ b/arewerslintyet/app/globals.css
@@ -0,0 +1,146 @@
+@import 'tailwindcss';
+@import './geist-design-system.css';
+@import './rspack-colors.css';
+
+@custom-variant dark (&:where(.dark, .dark *));
+
+/* this doesn't need flexbox, but flexbox layout is faster for the browser to
+ * compute */
+.HeatMap {
+ display: flex;
+ flex-wrap: wrap;
+ margin-bottom: 20px;
+}
+
+.Tooltip {
+ position: absolute;
+ line-height: 1.3;
+ pointer-events: none;
+}
+
+.TooltipContent {
+ background: rgb(38, 38, 38);
+ bottom: 8px;
+ color: white;
+ max-width: 300px;
+ overflow: hidden;
+ padding: 5px 10px;
+ position: absolute;
+ text-align: left;
+ text-overflow: ellipsis;
+ white-space: pre;
+}
+
+.TooltipStatus i {
+ left: -2px;
+ margin-right: 5px;
+ top: 1px;
+}
+
+.Tooltip:after {
+ background: #262626;
+ content: '';
+ height: 10px;
+ left: -5px;
+ position: absolute;
+ top: -13px;
+ transform: rotate(-45deg);
+ width: 10px;
+}
+
+.Footer {
+ text-align: center;
+}
+
+.FooterLink {
+ margin: 0 5px;
+}
+
+/* derived from shadcn/ui */
+@theme inline {
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-is-it-ready-no: var(--is-it-ready-no);
+ --color-passing-square: var(--passing-square);
+ --color-failing-square: var(--failing-square);
+}
+
+/**
+ * Use colors from the geist design system:
+ * https://vercel.com/geist/colors
+ */
+.turbopack {
+ --radius: 0.5rem;
+ --background: var(--ds-background-100);
+ --foreground: var(--ds-gray-1000);
+ --popover: var(--ds-background-100);
+ --popover-foreground: var(--ds-gray-1000);
+ --primary: var(--ds-blue-200);
+ --primary-foreground: var(--ds-gray-1000);
+ --secondary: var(--ds-background-200);
+ --secondary-foreground: var(--ds-gray-900);
+ --muted: var(--ds-background-200);
+ --muted-foreground: var(--ds-gray-900);
+ --accent: var(--ds-blue-200);
+ --accent-foreground: var(--ds-blue-700);
+ --border: var(--ds-gray-200);
+ --input: var(--ds-gray-200);
+ --ring: var(--ds-gray-500);
+ --chart: var(--ds-red-700);
+ --chart-area: var(--ds-red-400);
+ --chart-axis: var(--ds-blue-400);
+ --is-it-ready-no: var(--ds-red-700);
+ --passing-square: var(--ds-blue-400);
+ --failing-square: var(--ds-red-700);
+}
+
+.rspack {
+ --radius: var(--rp-radius);
+ --background: var(--rp-c-bg);
+ --foreground: var(--rp-c-text-1);
+ --popover: var(--rp-c-bg);
+ --popover-foreground: var(--rp-c-text-1);
+ --primary: var(--rp-c-brand-tint);
+ --primary-foreground: var(--rp-c-brand);
+ --secondary: var(--rp-c-bg-soft);
+ --secondary-foreground: var(--rp-c-text-2);
+ --muted: var(--rp-c-bg-mute);
+ --muted-foreground: var(--rp-c-text-2);
+ --accent: var(--rp-c-brand-tint);
+ --accent-foreground: var(--rp-c-brand-dark);
+ --border: var(--rp-c-divider-light);
+ --input: var(--rp-c-divider-light);
+ --ring: var(--rp-c-divider);
+ --chart: var(--rp-c-brand);
+ --chart-area: var(--rp-c-brand-tint);
+ --chart-axis: var(--rp-c-divider);
+ --is-it-ready-no: var(--rp-c-brand);
+ --passing-square: #66c2ff;
+ --failing-square: var(--rp-c-brand);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/arewerslintyet/app/layout.tsx b/arewerslintyet/app/layout.tsx
new file mode 100644
index 00000000..c8ad6333
--- /dev/null
+++ b/arewerslintyet/app/layout.tsx
@@ -0,0 +1,63 @@
+import './globals.css';
+import { Open_Sans as OpenSans } from 'next/font/google';
+import localFont from 'next/font/local';
+import { ThemeProvider } from '@/components/theme-provider';
+import { cn } from '@/lib/utils';
+import { Linter, getLinter } from './bundler';
+
+const geistFont = localFont({
+ src: [
+ {
+ path: './Geist-Regular.woff2',
+ weight: '400',
+ style: 'normal',
+ },
+ {
+ path: './Geist-SemiBold.woff2',
+ weight: '600',
+ style: 'normal',
+ },
+ ],
+ display: 'swap',
+});
+
+const openSansFont = OpenSans({
+ subsets: ['latin'],
+ display: 'swap',
+});
+
+export const metadata = {
+ title: 'Are We RSLint Yet?',
+ description:
+ 'Progress towards 100% of lint tests passing for RSLint - A fast JavaScript and TypeScript linter written in Rust',
+ icons: [
+ {
+ url: "data:image/svg+xml,š¦ ",
+ type: 'image/svg+xml',
+ },
+ ],
+};
+
+export default function RootLayout({ children }) {
+ const linter = getLinter();
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/arewerslintyet/app/page.tsx b/arewerslintyet/app/page.tsx
new file mode 100644
index 00000000..fe8eee9f
--- /dev/null
+++ b/arewerslintyet/app/page.tsx
@@ -0,0 +1,5 @@
+import ProductionPage from './build/page';
+
+export default function Homepage() {
+ return ;
+}
diff --git a/arewerslintyet/app/rspack-colors.css b/arewerslintyet/app/rspack-colors.css
new file mode 100644
index 00000000..dd503b3b
--- /dev/null
+++ b/arewerslintyet/app/rspack-colors.css
@@ -0,0 +1,63 @@
+/* copied from the rspack website */
+:root {
+ --rp-c-bg: #fff;
+ --rp-c-bg-soft: #f9f9f9;
+ --rp-c-bg-mute: #f1f1f1;
+ --rp-c-divider: #3c3c3c4a;
+ --rp-c-divider-light: #3c3c3c1a;
+ --rp-c-text-1: #213547;
+ --rp-c-text-2: #3c3c3ca8;
+ --rp-c-text-3: #3c3c3c54;
+ --rp-c-text-4: #3c3c3c2e;
+ --rp-c-text-code: #476573;
+ --rp-c-text-code-bg: #99a1b31a;
+ --rp-c-brand: #0095ff;
+ --rp-c-brand-light: #33adff;
+ --rp-c-brand-lighter: #66c2ff;
+ --rp-c-brand-dark: #07f;
+ --rp-c-brand-darker: #005fcc;
+ --rp-c-brand-tint: #7fa3ff29;
+ --rp-c-gray: #8e8e8e;
+ --rp-c-gray-light-1: #aeaeae;
+ --rp-c-gray-light-2: #c7c7c7;
+ --rp-c-gray-light-3: #d1d1d1;
+ --rp-c-gray-light-4: #e5e5e5;
+ --rp-c-gray-light-5: #f2f2f2;
+ --rp-c-dark: #000;
+ --rp-c-dark-light-1: #2f2f2f;
+ --rp-c-dark-light-2: #3a3a3a;
+ --rp-c-dark-light-3: #4a4a4a;
+ --rp-c-dark-light-4: #5c5c5c;
+ --rp-c-dark-light-5: #6b6b6b;
+ --rp-radius: 1rem;
+ --rp-radius-small: 0.5rem;
+ --rp-radius-large: 1.5rem;
+ --rp-c-link: var(--rp-c-brand-dark);
+}
+.dark {
+ --rp-c-bg: #191d24;
+ --rp-c-bg-soft: #292e37;
+ --rp-c-bg-mute: #343a46;
+ --rp-c-bg-alt: #000;
+ --rp-c-divider: #545454a6;
+ --rp-c-divider-light: #5454547a;
+ --rp-c-text-1: #fffff5ed;
+ --rp-c-text-2: #ebebeb8f;
+ --rp-c-text-3: #ebebeb61;
+ --rp-c-text-4: #ebebeb2e;
+ --rp-c-text-code: #c9def1;
+ --rp-c-link: var(--rp-c-brand-light);
+}
+
+:root {
+ --rp-c-brand: #ff5e00;
+ --rp-c-brand-dark: #ff704d;
+ --rp-c-brand-darker: #ff704d;
+ --rp-c-brand-light: #ff7524;
+ --rp-c-brand-lighter: #ff7524;
+ --rp-c-link: var(--rp-c-brand);
+ --rp-c-brand-tint: #ff5e0012;
+}
+.dark {
+ --rp-c-link: var(--rp-c-brand-light);
+}
diff --git a/arewerslintyet/biome.json b/arewerslintyet/biome.json
new file mode 100644
index 00000000..b9261116
--- /dev/null
+++ b/arewerslintyet/biome.json
@@ -0,0 +1,37 @@
+{
+ "$schema": "https://biomejs.dev/schemas/2.0.0-beta.1/schema.json",
+ "vcs": {
+ "enabled": true,
+ "clientKind": "git",
+ "useIgnoreFile": true
+ },
+ "files": {
+ "ignoreUnknown": false
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "space"
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true,
+ "security": {
+ "noDangerouslySetInnerHtml": "off"
+ }
+ }
+ },
+ "javascript": {
+ "formatter": {
+ "quoteStyle": "double"
+ }
+ },
+ "assist": {
+ "enabled": true,
+ "actions": {
+ "source": {
+ "organizeImports": "on"
+ }
+ }
+ }
+}
diff --git a/arewerslintyet/components.json b/arewerslintyet/components.json
new file mode 100644
index 00000000..a64445d7
--- /dev/null
+++ b/arewerslintyet/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "app/globals.css",
+ "baseColor": "zinc",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/arewerslintyet/components/theme-provider.tsx b/arewerslintyet/components/theme-provider.tsx
new file mode 100644
index 00000000..d7f1fa0e
--- /dev/null
+++ b/arewerslintyet/components/theme-provider.tsx
@@ -0,0 +1,10 @@
+'use client';
+
+import { ThemeProvider as NextThemesProvider } from 'next-themes';
+
+export function ThemeProvider({
+ children,
+ ...props
+}: React.ComponentProps) {
+ return {children} ;
+}
diff --git a/arewerslintyet/components/ui/button.tsx b/arewerslintyet/components/ui/button.tsx
new file mode 100644
index 00000000..65850fd0
--- /dev/null
+++ b/arewerslintyet/components/ui/button.tsx
@@ -0,0 +1,59 @@
+import { Slot } from '@radix-ui/react-slot';
+import { cva, type VariantProps } from 'class-variance-authority';
+import * as React from 'react';
+
+import { cn } from '@/lib/utils';
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default:
+ 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
+ destructive:
+ 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
+ secondary:
+ 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
+ ghost:
+ 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
+ link: 'text-primary underline-offset-4 hover:underline',
+ },
+ size: {
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
+ icon: 'size-9',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ },
+);
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> &
+ VariantProps & {
+ asChild?: boolean;
+ }) {
+ const Comp = asChild ? Slot : 'button';
+
+ return (
+
+ );
+}
+
+export { Button, buttonVariants };
diff --git a/arewerslintyet/components/ui/card.tsx b/arewerslintyet/components/ui/card.tsx
new file mode 100644
index 00000000..4898dfa6
--- /dev/null
+++ b/arewerslintyet/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from 'react';
+
+import { cn } from '@/lib/utils';
+
+function Card({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+};
diff --git a/arewerslintyet/components/ui/chart.tsx b/arewerslintyet/components/ui/chart.tsx
new file mode 100644
index 00000000..6afd3d4c
--- /dev/null
+++ b/arewerslintyet/components/ui/chart.tsx
@@ -0,0 +1,353 @@
+'use client';
+
+import * as React from 'react';
+import * as RechartsPrimitive from 'recharts';
+
+import { cn } from '@/lib/utils';
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: '', dark: '.dark' } as const;
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: React.ReactNode;
+ icon?: React.ComponentType;
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record }
+ );
+};
+
+type ChartContextProps = {
+ config: ChartConfig;
+};
+
+const ChartContext = React.createContext(null);
+
+function useChart() {
+ const context = React.useContext(ChartContext);
+
+ if (!context) {
+ throw new Error('useChart must be used within a ');
+ }
+
+ return context;
+}
+
+function ChartContainer({
+ id,
+ className,
+ children,
+ config,
+ ...props
+}: React.ComponentProps<'div'> & {
+ config: ChartConfig;
+ children: React.ComponentProps<
+ typeof RechartsPrimitive.ResponsiveContainer
+ >['children'];
+}) {
+ const uniqueId = React.useId();
+ const chartId = `chart-${id || uniqueId.replace(/:/g, '')}`;
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+}
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+ const colorConfig = Object.entries(config).filter(
+ ([, config]) => config.theme || config.color,
+ );
+
+ if (!colorConfig.length) {
+ return null;
+ }
+
+ return (
+