From 216660878e755d81d607d83cd8229105d719ea26 Mon Sep 17 00:00:00 2001 From: Carson Seese Date: Fri, 28 Aug 2026 00:07:31 +0000 Subject: [PATCH 1/7] =?UTF-8?q?overhaul:=20foundation=20=E2=80=94=20legacy?= =?UTF-8?q?=20move,=20mise/pnpm,=20Astro=20scaffold,=20toolchain,=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Park the Next.js site in legacy/ and stand up the Astro project it will be replaced by, with the full final toolchain in place before any UI exists. - mise pins node 26.7.0 + pnpm 11.22.0 with committed checksums; tasks forward to package.json scripts, which stay the single source of truth. - pnpm-workspace.yaml enforces a 7-day release cooldown and denies install scripts except sharp and esbuild. Dependency pins are therefore the newest version at least a week old, not the newest version. - Astro 7 static scaffold: sitemap integration, Tailwind v4 via the vite plugin, strictest tsconfig, cn re-exported from a single sanctioned site. - oxlint (type-aware, with anti-slop vendored from dmmulroy/anti-slop) and oxfmt own .ts/.js/.json/.css; ESLint (typed + jsx-a11y-strict) and Prettier own .astro and .md. Ownership table and invocation quirks in docs/tooling.md, rationale and the oxc migration seam in docs/adr/0001. - knip, .editorconfig, VS Code settings, AGENTS.md (CLAUDE.md symlink), and a PostToolUse hook that routes each edited file to its owning toolchain and feeds lint failures back. - CI gates every PR on check + build + an offline link check over dist/. Typechecking functions/ surfaced a real bug: an absent CF-Connecting-IP header was being sent to Turnstile as the string "null". The header is optional, so it is now omitted when missing. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YRfxMh7FLjQtDbb1BEsCbR --- .claude/hooks/format-lint.sh | 63 + .claude/settings.json | 16 + .editorconfig | 13 + .github/workflows/ci.yml | 41 + .gitignore | 27 +- .oxfmtrc.json | 15 + .oxlintrc.json | 72 + .prettierignore | 13 + .prettierrc | 10 + .vscode/extensions.json | 8 +- .vscode/settings.json | 37 +- AGENTS.md | 57 + CLAUDE.md | 1 + astro.config.ts | 13 + docs/adr/0001-toolchain-split.md | 63 + docs/adr/0002-tabler-icons-direct.md | 41 + docs/tooling.md | 104 + eslint.config.ts | 39 + functions/api/form/submit.ts | 5 +- functions/tsconfig.json | 11 +- functions/util.ts | 14 +- knip.jsonc | 27 + .env.development => legacy/.env.development | 0 .env.production => legacy/.env.production | 0 .nvmrc => legacy/.nvmrc | 0 legacy/README.md | 9 + biome.json => legacy/biome.json | 0 components.json => legacy/components.json | 0 {data => legacy/data}/config.ts | 0 {data => legacy/data}/sponsors.ts | 0 next.config.ts => legacy/next.config.ts | 0 package-lock.json => legacy/package-lock.json | 0 legacy/package.json | 56 + .../postcss.config.mjs | 0 {src => legacy/src}/app/about/components.tsx | 0 {src => legacy/src}/app/about/page.tsx | 0 .../src}/app/calendar/[name]/page.tsx | 0 {src => legacy/src}/app/contact/page.tsx | 0 {src => legacy/src}/app/donate/page.tsx | 0 {src => legacy/src}/app/favicon.ico | Bin {src => legacy/src}/app/get-involved/page.tsx | 0 {src => legacy/src}/app/layout.tsx | 0 {src => legacy/src}/app/not-found.tsx | 0 {src => legacy/src}/app/opengraph-image.png | Bin {src => legacy/src}/app/openhouse/page.tsx | 0 {src => legacy/src}/app/page.tsx | 0 .../src}/app/programs/fll/layout.tsx | 0 {src => legacy/src}/app/programs/fll/page.tsx | 0 .../src}/app/programs/frc/components.tsx | 0 .../src}/app/programs/frc/kickoff/page.tsx | 0 .../src}/app/programs/frc/layout.tsx | 0 {src => legacy/src}/app/programs/frc/page.tsx | 0 .../src}/app/programs/frc/robots/page.tsx | 0 {src => legacy/src}/app/sponsors/page.tsx | 0 {src => legacy/src}/components/ActionIcon.tsx | 0 {src => legacy/src}/components/Calendar.tsx | 0 {src => legacy/src}/components/Carousel.tsx | 0 {src => legacy/src}/components/Countdown.tsx | 0 {src => legacy/src}/components/Footer.tsx | 0 {src => legacy/src}/components/Image.tsx | 0 {src => legacy/src}/components/LinkButton.tsx | 0 {src => legacy/src}/components/Logo.tsx | 0 .../src}/components/ManualCarousel.tsx | 0 {src => legacy/src}/components/Modal.tsx | 0 {src => legacy/src}/components/Navbar.tsx | 0 .../src}/components/PatternBackground.tsx | 0 {src => legacy/src}/components/Providers.tsx | 0 .../src}/components/VideoPlayer.tsx | 0 .../src}/components/cards/FeatureCard.tsx | 0 .../src}/components/forms/ContactForm.tsx | 0 .../src}/components/forms/fields/Input.tsx | 0 .../src}/components/forms/fields/TextArea.tsx | 0 .../src}/components/forms/fields/util.ts | 0 .../src}/components/page/HeroHeader.tsx | 0 .../src}/components/page/Section.tsx | 0 .../src}/components/page/VideoHeader.tsx | 0 .../src}/components/shadcn/ui/button.tsx | 0 .../src}/components/shadcn/ui/carousel.tsx | 0 {src => legacy/src}/components/spans.tsx | 0 {src => legacy/src}/layouts/Default.tsx | 0 {src => legacy/src}/lib/utils.ts | 0 {src => legacy/src}/styles/fonts.ts | 0 {src => legacy/src}/styles/globals.css | 0 {src => legacy/src}/styles/hero.ts | 0 {src => legacy/src}/styles/theme.ts | 0 legacy/tsconfig.json | 62 + mise.lock | 65 + mise.toml | 60 + package.json | 85 +- plan/01-foundation.md | 29 +- pnpm-lock.yaml | 5847 +++++++++++++++++ pnpm-workspace.yaml | 9 + src/lib/cn.ts | 5 + src/pages/index.astro | 22 + src/styles/global.css | 3 + tools/lint/anti-slop/LICENSE | 21 + tools/lint/anti-slop/VENDOR.md | 11 + tools/lint/anti-slop/index.ts | 41 + .../rules/no-chained-type-assertions.ts | 77 + .../no-conditional-empty-object-spread.ts | 49 + .../rules/no-known-value-widening.ts | 247 + .../lint/anti-slop/rules/no-module-mocking.ts | 91 + .../anti-slop/rules/no-object-parameters.ts | 126 + .../lint/anti-slop/rules/no-reflect-apply.ts | 28 + tools/lint/anti-slop/rules/no-reflect-get.ts | 28 + .../lint/anti-slop/rules/no-runtime-typeof.ts | 67 + .../rules/no-shape-in-symbol-names.ts | 39 + .../anti-slop/rules/no-unknown-parameters.ts | 83 + .../anti-slop/rules/no-unknown-returns.ts | 115 + .../rules/no-unknown-type-aliases.ts | 70 + .../rules/no-unsafe-dictionary-type.ts | 134 + .../anti-slop/rules/no-widen-then-assert.ts | 366 ++ ...quire-safety-comment-for-type-assertion.ts | 62 + .../lint/anti-slop/shared/dictionary-types.ts | 502 ++ .../shared/lexical-type-parameters.ts | 61 + tools/lint/anti-slop/shared/reflect-method.ts | 35 + tsconfig.json | 67 +- 117 files changed, 9097 insertions(+), 165 deletions(-) create mode 100755 .claude/hooks/format-lint.sh create mode 100644 .claude/settings.json create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 AGENTS.md create mode 120000 CLAUDE.md create mode 100644 astro.config.ts create mode 100644 docs/adr/0001-toolchain-split.md create mode 100644 docs/adr/0002-tabler-icons-direct.md create mode 100644 docs/tooling.md create mode 100644 eslint.config.ts create mode 100644 knip.jsonc rename .env.development => legacy/.env.development (100%) rename .env.production => legacy/.env.production (100%) rename .nvmrc => legacy/.nvmrc (100%) create mode 100644 legacy/README.md rename biome.json => legacy/biome.json (100%) rename components.json => legacy/components.json (100%) rename {data => legacy/data}/config.ts (100%) rename {data => legacy/data}/sponsors.ts (100%) rename next.config.ts => legacy/next.config.ts (100%) rename package-lock.json => legacy/package-lock.json (100%) create mode 100644 legacy/package.json rename postcss.config.mjs => legacy/postcss.config.mjs (100%) rename {src => legacy/src}/app/about/components.tsx (100%) rename {src => legacy/src}/app/about/page.tsx (100%) rename {src => legacy/src}/app/calendar/[name]/page.tsx (100%) rename {src => legacy/src}/app/contact/page.tsx (100%) rename {src => legacy/src}/app/donate/page.tsx (100%) rename {src => legacy/src}/app/favicon.ico (100%) rename {src => legacy/src}/app/get-involved/page.tsx (100%) rename {src => legacy/src}/app/layout.tsx (100%) rename {src => legacy/src}/app/not-found.tsx (100%) rename {src => legacy/src}/app/opengraph-image.png (100%) rename {src => legacy/src}/app/openhouse/page.tsx (100%) rename {src => legacy/src}/app/page.tsx (100%) rename {src => legacy/src}/app/programs/fll/layout.tsx (100%) rename {src => legacy/src}/app/programs/fll/page.tsx (100%) rename {src => legacy/src}/app/programs/frc/components.tsx (100%) rename {src => legacy/src}/app/programs/frc/kickoff/page.tsx (100%) rename {src => legacy/src}/app/programs/frc/layout.tsx (100%) rename {src => legacy/src}/app/programs/frc/page.tsx (100%) rename {src => legacy/src}/app/programs/frc/robots/page.tsx (100%) rename {src => legacy/src}/app/sponsors/page.tsx (100%) rename {src => legacy/src}/components/ActionIcon.tsx (100%) rename {src => legacy/src}/components/Calendar.tsx (100%) rename {src => legacy/src}/components/Carousel.tsx (100%) rename {src => legacy/src}/components/Countdown.tsx (100%) rename {src => legacy/src}/components/Footer.tsx (100%) rename {src => legacy/src}/components/Image.tsx (100%) rename {src => legacy/src}/components/LinkButton.tsx (100%) rename {src => legacy/src}/components/Logo.tsx (100%) rename {src => legacy/src}/components/ManualCarousel.tsx (100%) rename {src => legacy/src}/components/Modal.tsx (100%) rename {src => legacy/src}/components/Navbar.tsx (100%) rename {src => legacy/src}/components/PatternBackground.tsx (100%) rename {src => legacy/src}/components/Providers.tsx (100%) rename {src => legacy/src}/components/VideoPlayer.tsx (100%) rename {src => legacy/src}/components/cards/FeatureCard.tsx (100%) rename {src => legacy/src}/components/forms/ContactForm.tsx (100%) rename {src => legacy/src}/components/forms/fields/Input.tsx (100%) rename {src => legacy/src}/components/forms/fields/TextArea.tsx (100%) rename {src => legacy/src}/components/forms/fields/util.ts (100%) rename {src => legacy/src}/components/page/HeroHeader.tsx (100%) rename {src => legacy/src}/components/page/Section.tsx (100%) rename {src => legacy/src}/components/page/VideoHeader.tsx (100%) rename {src => legacy/src}/components/shadcn/ui/button.tsx (100%) rename {src => legacy/src}/components/shadcn/ui/carousel.tsx (100%) rename {src => legacy/src}/components/spans.tsx (100%) rename {src => legacy/src}/layouts/Default.tsx (100%) rename {src => legacy/src}/lib/utils.ts (100%) rename {src => legacy/src}/styles/fonts.ts (100%) rename {src => legacy/src}/styles/globals.css (100%) rename {src => legacy/src}/styles/hero.ts (100%) rename {src => legacy/src}/styles/theme.ts (100%) create mode 100644 legacy/tsconfig.json create mode 100644 mise.lock create mode 100644 mise.toml create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 src/lib/cn.ts create mode 100644 src/pages/index.astro create mode 100644 src/styles/global.css create mode 100644 tools/lint/anti-slop/LICENSE create mode 100644 tools/lint/anti-slop/VENDOR.md create mode 100644 tools/lint/anti-slop/index.ts create mode 100644 tools/lint/anti-slop/rules/no-chained-type-assertions.ts create mode 100644 tools/lint/anti-slop/rules/no-conditional-empty-object-spread.ts create mode 100644 tools/lint/anti-slop/rules/no-known-value-widening.ts create mode 100644 tools/lint/anti-slop/rules/no-module-mocking.ts create mode 100644 tools/lint/anti-slop/rules/no-object-parameters.ts create mode 100644 tools/lint/anti-slop/rules/no-reflect-apply.ts create mode 100644 tools/lint/anti-slop/rules/no-reflect-get.ts create mode 100644 tools/lint/anti-slop/rules/no-runtime-typeof.ts create mode 100644 tools/lint/anti-slop/rules/no-shape-in-symbol-names.ts create mode 100644 tools/lint/anti-slop/rules/no-unknown-parameters.ts create mode 100644 tools/lint/anti-slop/rules/no-unknown-returns.ts create mode 100644 tools/lint/anti-slop/rules/no-unknown-type-aliases.ts create mode 100644 tools/lint/anti-slop/rules/no-unsafe-dictionary-type.ts create mode 100644 tools/lint/anti-slop/rules/no-widen-then-assert.ts create mode 100644 tools/lint/anti-slop/rules/require-safety-comment-for-type-assertion.ts create mode 100644 tools/lint/anti-slop/shared/dictionary-types.ts create mode 100644 tools/lint/anti-slop/shared/lexical-type-parameters.ts create mode 100644 tools/lint/anti-slop/shared/reflect-method.ts diff --git a/.claude/hooks/format-lint.sh b/.claude/hooks/format-lint.sh new file mode 100755 index 0000000..4ec5fcb --- /dev/null +++ b/.claude/hooks/format-lint.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Formats and lints one edited file with whichever toolchain owns its extension +# (docs/tooling.md). Lint failures exit 2 so the findings go back to the agent. +set -uo pipefail + +repo_root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null)}" +[[ -n "$repo_root" ]] || exit 0 + +payload="$(cat)" +file="$(printf '%s' "$payload" | jq -r '.tool_response.filePath // .tool_input.file_path // empty')" +[[ -n "$file" ]] || exit 0 +[[ -f "$file" ]] || exit 0 + +# Absolute, and inside this repo. +case "$file" in + /*) ;; + *) file="$repo_root/$file" ;; +esac +case "$file" in + "$repo_root"/*) ;; + *) exit 0 ;; +esac + +rel="${file#"$repo_root"/}" +case "$rel" in + legacy/* | dist/* | .astro/* | node_modules/* | plan/* | tools/lint/anti-slop/* | pnpm-lock.yaml) + exit 0 + ;; +esac + +cd "$repo_root" || exit 0 + +# Use the installed binaries directly so the hook works without pnpm on PATH. +run() { + local tool="$1"; shift + local bin="$repo_root/node_modules/.bin/$tool" + [[ -x "$bin" ]] || { command -v pnpm >/dev/null && pnpm exec "$tool" "$@" 2>&1; return; } + "$bin" "$@" 2>&1 +} + +case "${file##*.}" in + ts | tsx | js | jsx | mjs | cjs | json | jsonc | css) + # Without --ignore-path, oxfmt also reads .prettierignore, which excludes + # every extension oxfmt owns. + run oxfmt --ignore-path .gitignore "$file" >/dev/null + if ! findings="$(run oxlint --type-aware "$file")"; then + printf '%s\n' "$findings" >&2 + exit 2 + fi + ;; + astro) + run prettier --write "$file" >/dev/null + if ! findings="$(run eslint --max-warnings 0 "$file")"; then + printf '%s\n' "$findings" >&2 + exit 2 + fi + ;; + md) + run prettier --write "$file" >/dev/null + ;; +esac + +exit 0 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..cb0b19c --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,16 @@ +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "Edit|Write|NotebookEdit", + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/format-lint.sh", + "timeout": 60 + } + ] + } + ] + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..879a99a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# For what neither oxfmt nor Prettier covers (yaml, toml, shell, gitignore-likes). +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..25bb26a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: CI + +on: + pull_request: + +# Deploys are owned by Cloudflare Pages' git integration (D14). This workflow is a PR gate only. +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: check + build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Install mise tools + uses: jdx/mise-action@v3 + with: + version: 2026.8.14 + install: true + cache: true + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Check (typecheck + lint + format + knip) + run: pnpm check + + - name: Build + run: pnpm build + + - name: Link check + uses: lycheeverse/lychee-action@v2 + with: + args: --offline --include-fragments --no-progress 'dist/**/*.html' + fail: true diff --git a/.gitignore b/.gitignore index fd3dbb5..b8a7d65 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,10 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - # dependencies -/node_modules -/.pnp -.pnp.js -.yarn/install-state.gz - -# testing -/coverage +node_modules +.pnpm-store -# next.js -/.next/ -/out/ - -# production -/build +# astro +.astro +dist # misc .DS_Store @@ -22,15 +12,10 @@ # debug npm-debug.log* -yarn-debug.log* -yarn-error.log* +pnpm-debug.log* # local env files .env*.local -# vercel -.vercel - # typescript *.tsbuildinfo -next-env.d.ts diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..10fca43 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,15 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "sortImports": true, + "sortPackageJson": true, + "sortTailwindcss": true, + "ignorePatterns": [ + "legacy/**", + "dist/**", + ".astro/**", + ".claude/**", + "tools/lint/anti-slop/**", + "**/*.md", + "**/*.astro" + ] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..dd4bbef --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,72 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "unicorn", "oxc", "import"], + "options": { + "typeAware": true, + "typeCheck": true + }, + "categories": { + "correctness": "error", + "suspicious": "error", + "perf": "warn", + "style": "off" + }, + "jsPlugins": [ + { + "name": "anti-slop", + "specifier": "./tools/lint/anti-slop/index.ts" + } + ], + "rules": { + "anti-slop/no-chained-type-assertions": "error", + "anti-slop/no-conditional-empty-object-spread": "error", + "anti-slop/no-known-value-widening": "error", + "anti-slop/no-module-mocking": "error", + "anti-slop/no-object-parameters": "error", + "anti-slop/no-reflect-apply": "error", + "anti-slop/no-reflect-get": "error", + "anti-slop/no-runtime-typeof": "error", + "anti-slop/no-shape-in-symbol-names": "error", + "anti-slop/no-unknown-parameters": "error", + "anti-slop/no-unknown-returns": "error", + "anti-slop/no-unknown-type-aliases": "error", + "anti-slop/no-unsafe-dictionary-type": "error", + "anti-slop/no-widen-then-assert": "error", + "anti-slop/require-safety-comment-for-type-assertion": "error", + "no-restricted-imports": [ + "error", + { + "paths": [ + { + "name": "clsx", + "message": "use cn from @/lib/cn (cnfast)" + }, + { + "name": "classnames", + "message": "use cn from @/lib/cn (cnfast)" + }, + { + "name": "tailwind-merge", + "message": "use cn from @/lib/cn (cnfast)" + } + ], + "patterns": [ + { + "group": ["legacy/*", "**/legacy/*", "../legacy/*"], + "message": "legacy/ is reference only \u2014 never import from it (plan/00-overview.md)" + } + ] + } + ] + }, + "ignorePatterns": [ + "**/*.astro", + "legacy/**", + "dist/**", + ".astro/**", + "tools/lint/anti-slop/**", + ".claude/**", + "plan/**", + "node_modules/**" + ] +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..61fd6f4 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,13 @@ +# Prettier is invoked against an explicit `**/*.{astro,md}` glob (see package.json), +# because a blanket `*` ignore prunes directories and cannot be undone per-file. +# oxfmt owns every other extension — see docs/tooling.md. +legacy/** +dist/** +.astro/** +node_modules/** +tools/lint/anti-slop/** + +# Hand-formatted markdown: tables, checkboxes, and line breaks here are deliberate. +plan/** +docs/adr/** +DESIGN.md diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..8cf118c --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "printWidth": 100, + "plugins": ["prettier-plugin-astro", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": "*.astro", + "options": { "parser": "astro" } + } + ] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index e11f647..11498de 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,9 @@ { - "recommendations": ["bradlc.vscode-tailwindcss", "biomejs.biome"] + "recommendations": [ + "astro-build.astro-vscode", + "oxc.oxc-vscode", + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss", + "hverlin.mise-vscode" + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index bae114e..6812d4d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,25 +1,26 @@ { - "editor.defaultFormatter": "biomejs.biome", - "editor.tabSize": 2, "editor.formatOnSave": true, + "editor.defaultFormatter": "oxc.oxc-vscode", "editor.codeActionsOnSave": { - "source.fixAll.biome": "explicit" + "source.fixAll.oxc": "explicit", + "source.organizeImports.oxc": "explicit" }, - "js/ts.preferences.importModuleSpecifier": "non-relative", + + "[astro]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[typescript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[javascript]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[json]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[jsonc]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + "[css]": { "editor.defaultFormatter": "oxc.oxc-vscode" }, + + "files.associations": { "*.css": "tailwindcss" }, + "tailwindCSS.classFunctions": ["cn", "cva"], "tailwindCSS.experimental.classRegex": [ - [ - "clsx\\(([^)]*)\\)", - "(?:'|\"|`)([^']*)(?:'|\"|`)", - "cn\\(([^)]*)\\)", - "classNames\\(([^)]+)\\)" - ] + ["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"], + ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*)[\"'`]"] ], - "js/ts.tsdk.path": "./node_modules/typescript/lib", - "workbench.editor.customLabels.enabled": true, - "workbench.editor.customLabels.patterns": { - "**/app/**/page.tsx": "${dirname} - Page", - "**/app/**/layout.tsx": "${dirname} - Layout", - "**/app/**/template.tsx": "${dirname} - Template" - }, - "css.lint.unknownAtRules": "ignore" + + "search.exclude": { "legacy/**": true, "dist/**": true, "pnpm-lock.yaml": true }, + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3a3e0f3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,57 @@ +# AGENTS.md + +## What this is + +The website of the South Central STEM Collective (SC2), a 501(c)(3) running FIRST robotics +and hands-on STEM programs in Franklin County, PA. Static Astro site, deployed by Cloudflare +Pages. **Zero client-side framework runtime** — `.astro` components and plain `