-
Notifications
You must be signed in to change notification settings - Fork 896
Enforce strict co-location pattern in website app #46
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3bd9e94
fc038d3
8f49694
b06df11
272d785
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| --- | ||
| name: project-structure-validator | ||
| description: Validates project structure against co-location and architecture patterns defined in AGENTS.md | ||
| color: blue | ||
| --- | ||
|
|
||
| You are a project structure validator for the Superset monorepo. | ||
|
|
||
| ## Speed Optimization | ||
|
|
||
| **ALWAYS** build component graph first: | ||
| ```bash | ||
| bash .claude/agents/project-structure-validator/build-component-graph.sh [directory] | ||
| cat .claude/agents/project-structure-validator/.component-graph.json | ||
| ``` | ||
|
|
||
| This avoids slow grep operations for import counting. | ||
|
|
||
| ## Analysis Approach | ||
|
|
||
| **1. Find components** (fast): | ||
| ```bash | ||
| find [directory] -name "*.tsx" -type f ! -name "*.test.tsx" ! -name "*.stories.tsx" | ||
| ``` | ||
|
|
||
| **2. Count imports** (use graph, else grep): | ||
| ```bash | ||
| grep -r "from.*ComponentName" [directory] --include="*.tsx" --include="*.ts" | wc -l | ||
| ``` | ||
|
|
||
| **3. Multi-component check**: | ||
| ```bash | ||
| grep -c "^export function\|^export const.*=>" File.tsx | ||
| ``` | ||
|
|
||
| ## Rules from AGENTS.md | ||
|
|
||
| 1. Used once → nest under parent's `components/` | ||
| 2. Used 2+ → promote to shared parent's `components/` | ||
| 3. One component per file | ||
| 4. Co-locate utils/hooks/constants/tests/stories | ||
|
|
||
| ## Output Format (CONCISE) | ||
|
|
||
| ```markdown | ||
| ## Summary | ||
| Score: [%] | [N] components | [N] violations | ||
|
|
||
| ## Critical Issues | ||
| [VIOLATION] Component at wrong location (used Nx, at Y) | ||
| Fix: mv X Y | ||
|
|
||
| ## Metrics | ||
| - Components: [N], avg depth [N] | ||
| - Violations: [N] location, [N] multi-component | ||
|
|
||
| ## Performance Analysis | ||
| - Tool calls: [N] ([breakdown]) | ||
| - Slowest: [operation] ([reason]) | ||
| - Used component graph: [yes/no] | ||
| - Optimization: [suggestion] | ||
| ``` | ||
|
|
||
| ## Self-Improvement | ||
|
|
||
| At end of report, suggest modifications to THIS file (.claude/agents/project-structure-validator.md) that would make you faster/better. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Generated component dependency graph | ||
| .component-graph.json |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| #!/bin/bash | ||
| # Builds component dependency graph for fast lookup | ||
| # Output: .claude/agents/project-structure-validator/.component-graph.json | ||
|
|
||
| DIR="${1:-.}" | ||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| OUTPUT="$SCRIPT_DIR/.component-graph.json" | ||
|
|
||
| echo "{" > "$OUTPUT" | ||
| echo ' "components": {' >> "$OUTPUT" | ||
|
|
||
| first=true | ||
| find "$DIR" -name "*.tsx" -type f ! -path "*/node_modules/*" ! -name "*.test.tsx" ! -name "*.stories.tsx" | while read file; do | ||
| component=$(basename "$file" .tsx) | ||
| path="${file#$DIR/}" | ||
|
|
||
| # Count imports | ||
| count=$(grep -r "from.*['\"].*$component['\"]" "$DIR" --include="*.tsx" --include="*.ts" 2>/dev/null | grep -v "$file" | wc -l | tr -d ' ') | ||
|
|
||
| # Get importers | ||
| importers=$(grep -l "from.*['\"].*$component['\"]" "$DIR" --include="*.tsx" --include="*.ts" -r 2>/dev/null | grep -v "$file" | sed "s|^$DIR/||" | paste -sd "," -) | ||
|
|
||
| if [ "$first" = true ]; then | ||
| first=false | ||
| else | ||
| echo "," >> "$OUTPUT" | ||
| fi | ||
|
|
||
| echo -n " \"$path\": {\"component\": \"$component\", \"imports\": $count, \"importers\": [" >> "$OUTPUT" | ||
| if [ -n "$importers" ]; then | ||
| echo "$importers" | sed 's/,/","/g' | sed 's/^/"/' | sed 's/$/"/' | tr -d '\n' >> "$OUTPUT" | ||
| fi | ||
| echo -n "]}" >> "$OUTPUT" | ||
| done | ||
|
|
||
| echo "" >> "$OUTPUT" | ||
| echo ' },' >> "$OUTPUT" | ||
| echo " \"generated\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"" >> "$OUTPUT" | ||
| echo "}" >> "$OUTPUT" | ||
|
|
||
| echo "Built component graph: $OUTPUT" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,7 +11,8 @@ Bun + Turbo monorepo with: | |
| - `apps/docs` - Documentation site | ||
| - `apps/blog` - Blog site | ||
| - **Packages**: | ||
| - `packages/ui` - Shared UI components (shadcn/ui + TailwindCSS v4) | ||
| - `packages/ui` - Shared UI components (shadcn/ui + TailwindCSS v4). | ||
| - Add components: `npx shadcn@latest add <component>` (run in `packages/ui/`) | ||
| - `packages/db` - Drizzle ORM database schema | ||
| - `packages/constants` - Shared constants | ||
| - `packages/models` - Shared data models | ||
|
|
@@ -52,16 +53,6 @@ bun run clean # Clean root node_modules | |
| bun run clean:workspaces # Clean all workspace node_modules | ||
| ``` | ||
|
|
||
| ## UI Components | ||
|
|
||
| All components in `packages/ui`: | ||
| - **Import**: `@superset/ui/button`, `@superset/ui/input`, etc. | ||
| - **Icons**: `@superset/ui/icons` | ||
| - **Utils**: `@superset/ui/utils` | ||
| - **Hooks**: `@superset/ui/hooks` | ||
| - **Styles**: `@superset/ui/globals.css` | ||
| - **Add shadcn component**: `npx shadcn@latest add <component>` (run in `packages/ui/`) | ||
|
|
||
| ## Code Quality | ||
|
|
||
| **Biome runs at root level** (not per-package) for speed: | ||
|
|
@@ -74,9 +65,62 @@ All components in `packages/ui`: | |
|
|
||
| 1. **Keep diffs minimal** - targeted edits only | ||
| 2. **Follow existing patterns** - match the codebase style | ||
| 5. **Type safety** - avoid `any` unless necessary | ||
| 6. **Don't run dev servers** in automation | ||
| 7. **Search narrowly** - avoid reading large files/assets | ||
| 3. **Type safety** - avoid `any` unless necessary | ||
| 4. **Don't run dev servers** in automation | ||
| 5. **Search narrowly** - avoid reading large files/assets | ||
|
|
||
| ## Project Structure | ||
|
|
||
| All projects in this repo should be structured like this: | ||
|
|
||
| ``` | ||
| app/ | ||
| ├── page.tsx | ||
|
Comment on lines
+76
to
+78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix missing language identifier in fenced code block (MD040). The opening fence at line 76 is missing a language specifier, which violates MD040. This was previously flagged and remains unresolved. Update the opening fence to include -```
+```text
app/
├── page.tsx🧰 Tools🪛 markdownlint-cli2 (0.18.1)76-76: Fenced code blocks should have a language specified (MD040, fenced-code-language) 🤖 Prompt for AI Agents |
||
| ├── dashboard/ | ||
| │ ├── page.tsx | ||
| │ └── components/ | ||
| │ └── MetricsChart/ | ||
| │ ├── MetricsChart.tsx | ||
| │ ├── MetricsChart.test.tsx # Tests co-located | ||
| │ ├── index.ts | ||
| │ ├── useMetricsData.ts # Hook used only here | ||
| │ └── constants.ts | ||
| └── components/ | ||
| ├── Sidebar/ | ||
| │ ├── Sidebar.tsx | ||
| │ ├── Sidebar.test.tsx # Tests co-located | ||
| │ ├── index.ts | ||
| │ ├── components/ # Used 2+ times IN Sidebar | ||
| │ │ └── SidebarButton/ # Shared by SidebarNav + SidebarFooter | ||
| │ │ ├── SidebarButton.tsx | ||
| │ │ ├── SidebarButton.test.tsx | ||
| │ │ └── index.ts | ||
| │ ├── SidebarNav/ | ||
| │ │ ├── SidebarNav.tsx | ||
| │ │ └── index.ts | ||
| │ └── SidebarFooter/ | ||
| │ ├── SidebarFooter.tsx | ||
| │ └── index.ts | ||
| └── HeroSection/ | ||
| ├── HeroSection.tsx | ||
| ├── HeroSection.test.tsx # Tests co-located | ||
| ├── index.ts | ||
| └── components/ # Used ONLY by HeroSection | ||
| └── HeroCanvas/ | ||
| ├── HeroCanvas.tsx | ||
| ├── HeroCanvas.test.tsx | ||
| ├── HeroCanvas.stories.tsx | ||
| ├── index.ts | ||
| └── config.ts | ||
|
|
||
| components/ # Used in 2+ pages (last resort) | ||
| └── Header/ | ||
| ``` | ||
|
|
||
| 1. **One folder per component**: `ComponentName/ComponentName.tsx` + `index.ts` for barrel export | ||
| 2. **Co-locate by usage**: If used once, nest under parent's `components/`. If used 2+ times, promote to **highest shared parent's** `components/` (or `components/` as last resort) | ||
| 3. **One component per file**: No multi-component files | ||
| 4. **Co-locate dependencies**: Utils, hooks, constants, config, tests, stories live next to the file using them | ||
|
|
||
| ## Database Rules | ||
|
|
||
|
|
||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,63 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| import { motion } from "framer-motion"; | ||||||||||||||||||||||||||||||||||||||||||||||
| import { CLIENT_LOGOS } from "./constants"; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| export function ClientLogosSection() { | ||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <section className="py-12 sm:py-16 md:py-24 px-4 sm:px-6 md:px-8 bg-black overflow-hidden"> | ||||||||||||||||||||||||||||||||||||||||||||||
| <div className="max-w-7xl mx-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||
| <motion.div | ||||||||||||||||||||||||||||||||||||||||||||||
| initial={{ opacity: 0, y: 20 }} | ||||||||||||||||||||||||||||||||||||||||||||||
| whileInView={{ opacity: 1, y: 0 }} | ||||||||||||||||||||||||||||||||||||||||||||||
| viewport={{ once: true, margin: "-100px" }} | ||||||||||||||||||||||||||||||||||||||||||||||
| transition={{ duration: 0.5, ease: "easeOut" }} | ||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||
| <h2 className="text-xl sm:text-2xl font-normal text-center mb-4 sm:mb-8 text-white px-4"> | ||||||||||||||||||||||||||||||||||||||||||||||
| Trusted by engineers from | ||||||||||||||||||||||||||||||||||||||||||||||
| </h2> | ||||||||||||||||||||||||||||||||||||||||||||||
| </motion.div> | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| <motion.div | ||||||||||||||||||||||||||||||||||||||||||||||
| initial={{ opacity: 0, y: 20 }} | ||||||||||||||||||||||||||||||||||||||||||||||
| whileInView={{ opacity: 1, y: 0 }} | ||||||||||||||||||||||||||||||||||||||||||||||
| viewport={{ once: true, margin: "-100px" }} | ||||||||||||||||||||||||||||||||||||||||||||||
| transition={{ duration: 0.5, delay: 0.2 }} | ||||||||||||||||||||||||||||||||||||||||||||||
| className="relative" | ||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex overflow-hidden"> | ||||||||||||||||||||||||||||||||||||||||||||||
| <motion.div | ||||||||||||||||||||||||||||||||||||||||||||||
| className="flex gap-12 sm:gap-16 md:gap-24" | ||||||||||||||||||||||||||||||||||||||||||||||
| animate={{ | ||||||||||||||||||||||||||||||||||||||||||||||
| x: [0, -1000], | ||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||
| transition={{ | ||||||||||||||||||||||||||||||||||||||||||||||
| x: { | ||||||||||||||||||||||||||||||||||||||||||||||
| repeat: Number.POSITIVE_INFINITY, | ||||||||||||||||||||||||||||||||||||||||||||||
| repeatType: "loop", | ||||||||||||||||||||||||||||||||||||||||||||||
| duration: 20, | ||||||||||||||||||||||||||||||||||||||||||||||
| ease: "linear", | ||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+38
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix marquee offset to avoid gaps Hard-coding - animate={{
- x: [0, -1000],
- }}
+ animate={{
+ x: ["0%", "-33.333333%"],
+ }}
transition={{
x: {
repeat: Number.POSITIVE_INFINITY,
repeatType: "loop",
duration: 20,
ease: "linear",📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||
| {/* Render logos three times for seamless loop */} | ||||||||||||||||||||||||||||||||||||||||||||||
| {[...Array(3)].map((_, setIndex) => ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||
| key={setIndex} | ||||||||||||||||||||||||||||||||||||||||||||||
| className="flex gap-12 sm:gap-16 md:gap-24 items-center" | ||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||
| {CLIENT_LOGOS.map((client) => ( | ||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||
| key={`${setIndex}-${client.name}`} | ||||||||||||||||||||||||||||||||||||||||||||||
| className="text-white text-lg sm:text-xl md:text-2xl lg:text-3xl font-semibold opacity-60 hover:opacity-100 transition-opacity cursor-pointer whitespace-nowrap" | ||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||
| {client.logo} | ||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||
| </motion.div> | ||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||
| </motion.div> | ||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||
| </section> | ||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| export const CLIENT_LOGOS = [ | ||
| { name: "numbies", logo: "numbies.xyz" }, | ||
| { name: "findmymachinist", logo: "findmymachinist.com" }, | ||
| { name: "cadra", logo: "Cadra" }, | ||
| { name: "onlook", logo: "Onlook" }, | ||
| { name: "amazon", logo: "Amazon" }, | ||
| { name: "google", logo: "Google" }, | ||
| { name: "servicenow", logo: "ServiceNow" }, | ||
| { name: "ycombinator", logo: "Y Combinator" }, | ||
| { name: "scribe", logo: "Scribe" }, | ||
| ] as const; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { ClientLogosSection } from "./ClientLogosSection"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import { motion } from "framer-motion"; | ||
|
|
||
| interface FeaturesSectionProps { | ||
| onOpenWaitlist: () => void; | ||
| } | ||
|
|
||
| // Using the same SCALE_FEATURES from ScaleFeaturesSection | ||
| const FEATURES = [ | ||
| { | ||
| title: "Work in parallel", | ||
| description: | ||
| "Run multiple agents in parallel. Build features as quickly as you can come up with them.", | ||
| }, | ||
| { | ||
| title: "No downtime", | ||
| description: | ||
| "Code on the go. Always-on agents that work even when you're away from your laptop.", | ||
| }, | ||
| { | ||
| title: "Zero switching cost", | ||
| description: | ||
| "Be the human in the loop. We handle the port switching and context management so you're never overloaded.", | ||
| }, | ||
| { | ||
| title: "Bring your own tools", | ||
| description: | ||
| "We're a superset of your existing tools, not a replacement. Use your own coding setup, tools, and agents. We bring the tooling and gluing.", | ||
| }, | ||
| ] as const; | ||
|
|
||
| export function FeaturesSection({ onOpenWaitlist }: FeaturesSectionProps) { | ||
| return ( | ||
| <section className="py-16 sm:py-24 md:py-32 px-4 sm:px-6 md:px-8 bg-black"> | ||
| <div className="max-w-3xl mx-auto"> | ||
| <motion.div | ||
| initial={{ opacity: 0, y: 20 }} | ||
| whileInView={{ opacity: 1, y: 0 }} | ||
| viewport={{ once: true, margin: "-100px" }} | ||
| transition={{ duration: 0.5 }} | ||
| className="text-center mb-16 sm:mb-20" | ||
| > | ||
| <h2 className="text-3xl sm:text-4xl md:text-5xl font-bold text-white mb-4"> | ||
| Build like a VP of Engineering | ||
| </h2> | ||
| </motion.div> | ||
|
|
||
| <div className="space-y-8 sm:space-y-12"> | ||
| {FEATURES.map((feature, idx) => ( | ||
| <motion.div | ||
| key={feature.title} | ||
| initial={{ opacity: 0, y: 20 }} | ||
| whileInView={{ opacity: 1, y: 0 }} | ||
| viewport={{ once: true, margin: "-100px" }} | ||
| transition={{ duration: 0.5, delay: idx * 0.1 }} | ||
| > | ||
| <h3 className="text-2xl sm:text-3xl font-semibold mb-3 text-white"> | ||
| {feature.title} | ||
| </h3> | ||
| <p className="text-base sm:text-lg text-zinc-400"> | ||
| {feature.description} | ||
| </p> | ||
| </motion.div> | ||
| ))} | ||
| </div> | ||
|
|
||
| <motion.div | ||
| initial={{ opacity: 0, y: 20 }} | ||
| whileInView={{ opacity: 1, y: 0 }} | ||
| viewport={{ once: true, margin: "-100px" }} | ||
| transition={{ duration: 0.5, delay: 0.4 }} | ||
| className="flex justify-center mt-16 sm:mt-20" | ||
| > | ||
| <button | ||
| type="button" | ||
| onClick={onOpenWaitlist} | ||
| className="bg-white text-black px-6 py-3 rounded-lg text-base font-medium hover:bg-zinc-200 transition-colors" | ||
| > | ||
| Join waitlist | ||
| </button> | ||
| </motion.div> | ||
| </div> | ||
| </section> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { FeaturesSection } from "./FeaturesSection"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { Footer } from "./Footer"; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { Header } from "./Header"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add language identifier to fenced code block.
The code block is missing a language specifier, which violates MD040 and impacts syntax highlighting. Update the opening fence to include
textorbash.🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
76-76: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents