Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .claude/MEMORY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# datum.net Project Rules

## Stack
- **Framework**: Astro 5.x + TypeScript strict mode
- **Styling**: TailwindCSS v4 with CSS layers
- **Interactivity**: Alpine.js
- **Testing**: Playwright (E2E)

## File Naming
| Type | Convention | Example |
|------|------------|---------|
| Components | PascalCase | `BlogItem.astro`, `NavMenu.astro` |
| Utils | camelCase + suffix | `dateUtils.ts`, `imageUtils.ts` |
| Libs | camelCase | `string.ts`, `postgres.ts` |
| Types | camelCase | `common.ts`, `navigation.ts` |
| Pages | kebab-case | `request-access.astro` |
| CSS | kebab-case + prefix | `components-blog.css`, `page-home.css` |

## CSS Class Naming (BEM-like)
- **Single dash (-)**: children/elements → `nav-menu-item`, `content-title`
- **Double dash (--)**: variants/modifiers → `content-image--left`, `blog-featured--title`

## Critical Rules
1. **No inline styles** - All styles go in CSS files under `src/v1/styles/`
2. **No inline Tailwind classes** - Put Tailwind in CSS with `@apply`
3. **No arbitrary values** - Check `@theme` layer for variables
4. **Start components with path comment** → `// src/components/Button.astro`
5. **Use path aliases** → `@components/*`, `@utils/*`, `@libs/*`, `@types/*`, `@v1/*`

## CSS Organization
```
src/v1/styles/
base.css # Base styles
theme.css # Theme variables
variables.css # CSS custom properties
utilities.css # Global utilities
components-*.css # Component styles
page-*.css # Page-specific styles
```

## Astro Patterns
```astro
---
// src/components/Example.astro
import type { ExampleProps } from '@types/common';

const { class: className = '', prop1, prop2 = 'default' } = Astro.props as ExampleProps;
---
<div class:list={['base-class', condition && 'conditional-class']}></div>
```

## Import Order
1. Astro imports
2. Local imports
3. Types (use `import type`)

## Formatting
- Single quotes, semicolons required
- 2-space indent, 100 char width
- Trailing commas (ES5)

See [detailed-rules.md](./detailed-rules.md) for full conventions.
148 changes: 148 additions & 0 deletions .claude/rules/detailed-rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# Detailed Project Rules for datum.net

## TypeScript Conventions

### Type Safety
- Use `strict` TypeScript config (extends `astro/tsconfigs/strict`)
- Prefer `@ts-expect-error` over `@ts-ignore`
- Prefix unused variables with `_` (e.g., `_unusedParam`)
- Use `const` assertions: `as const`
- Explicit return types for public functions

### JSDoc Pattern
```typescript
/**
* Brief description of function purpose
* @param paramName - Description of parameter
* @returns Description of return value
*/
export const functionName = (paramName: Type): ReturnType => {
// implementation
};
```

### Path Aliases
| Alias | Path |
|-------|------|
| `@/*` | root |
| `@components/*` | `./src/components/*` |
| `@utils/*` | `./src/utils/*` |
| `@libs/*` | `./src/libs/*` |
| `@types/*` | `./src/types/*` |
| `@layouts/*` | `./src/layouts/*` |
| `@v1/*` | `./src/v1/*` |
| `@content/*` | `./src/content/*` |
| `@data/*` | `./src/data/*` |
| `@styles/*` | `./src/styles/*` |
| `@assets/*` | `./src/assets/*` |

## TailwindCSS v4 Rules

### DO
- Use `@layer components` for component styles
- Use `@apply` directive for Tailwind utilities
- Use nested SCSS format
- Check `@theme` for existing variables before adding custom values

### DON'T
- No arbitrary values like `w-[123px]`
- No inline Tailwind classes in components
- No `<style>` tags in components

### CSS File Pattern
```css
@layer components {
.component-name {
@apply flex items-center gap-4;

&--variant {
@apply bg-blue-500;
}

&-child {
@apply text-sm;
}
}
}
```

## Astro Component Rules

### Required Structure
1. Start with path comment
2. Imports in frontmatter
3. Props interface with destructuring
4. Clean template with `class:list`

### Props Pattern
```astro
---
// src/components/ComponentName.astro
import type { ComponentProps } from '@types/common';
import '@v1/styles/components-feature.css';

const {
class: className = '',
requiredProp,
optionalProp = 'default',
...restProps
} = Astro.props as ComponentProps;
---

<div class:list={['component-name', className]} {...restProps}>
{/* content */}
</div>
```

### Conditional Rendering
```astro
{condition && <Component />}
{condition ? <ComponentA /> : <ComponentB />}
```

## Server Actions

- Use `defineAction` from `astro:actions`
- Validate with Zod
- Return appropriate HTTP status codes
- Handle errors with try/catch

## Performance

- Use `server:defer` for non-critical components
- Use Astro's `Image` component for images
- Add `data-astro-prefetch="hover"` for link prefetching
- Prefer SSR over client-side JS

## Figma Dev Mode

- Use localhost source directly for images/SVGs from Figma MCP
- Do NOT import new icon packages - use Figma assets
- Do NOT create placeholders if localhost source provided

## Project Structure
```
src/
components/ # PascalCase, grouped by feature
blog/
forms/
home/
pages/ # kebab-case routes
utils/ # camelCase + suffix
libs/ # camelCase, library code
types/ # TypeScript definitions
actions/ # Server actions
layouts/ # Layout components
content/ # MDX/MD collections
v1/
styles/ # All CSS files
assets/ # Images, SVGs, icons
```

## Code Quality Checklist
- [ ] Comments describe purpose, not effect
- [ ] No commented-out code
- [ ] Type safety maintained
- [ ] Semantic HTML used
- [ ] Accessibility attributes included
- [ ] ESLint/Prettier clean
5 changes: 5 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"permissions": {
"allow": ["WebFetch(domain:www.figma.com)", "mcp__claude_ai_Figma__get_design_context"]
}
}
1 change: 1 addition & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"default": true,
"MD007": false,
"MD013": false,
"MD024": false,
"MD033": false,
Expand Down
3 changes: 3 additions & 0 deletions .markdownlintignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ node_modules/

# Logs
*.log

# Claude metadata (exclude from markdown lint)
.claude/
24 changes: 24 additions & 0 deletions src/components/Icon.astro
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import LinkedinIcon from '@v1/assets/icons/linkedin.svg';
import CloseIcon from '@v1/assets/icons/close.svg';
import PlayIcon from '@v1/assets/icons/play.svg';
import GithubIcon from '@v1/assets/icons/github.svg';
import AppleIcon from '@v1/assets/icons/apple.svg';
import WindowsIcon from '@v1/assets/icons/windows.svg';
import LinuxIcon from '@v1/assets/icons/linux.svg';
import GoIcon from '@v1/assets/icons/go.svg';
import TsIcon from '@v1/assets/icons/ts.svg';
import RustIcon from '@v1/assets/icons/rust.svg';
import TerminaIcon from '@v1/assets/icons/terminal.svg';
import WorkflowIcon from '@v1/assets/icons/workflow.svg';

const { name, class: className = '', size = 'md' } = Astro.props;

Expand Down Expand Up @@ -42,6 +50,22 @@ const sizeClass = sizeClasses[size as keyof typeof sizeClasses] || sizeClasses.m
<PlayIcon class={`${sizeClass} ${className}`} />
) : name === 'github' ? (
<GithubIcon class={`${sizeClass} ${className}`} />
) : name === 'apple' ? (
<AppleIcon class={`${sizeClass} ${className}`} />
) : name === 'windows' ? (
<WindowsIcon class={`${sizeClass} ${className}`} />
) : name === 'linux' ? (
<LinuxIcon class={`${sizeClass} ${className}`} />
) : name === 'go' ? (
<GoIcon class={`${sizeClass} ${className}`} />
) : name === 'ts' ? (
<TsIcon class={`${sizeClass} ${className}`} />
) : name === 'rust' ? (
<RustIcon class={`${sizeClass} ${className}`} />
) : name === 'terminal' ? (
<TerminaIcon class={`${sizeClass} ${className}`} />
) : name === 'workflow' ? (
<WorkflowIcon class={`${sizeClass} ${className}`} />
) : IconComponent ? (
<IconComponent class={`${sizeClass} ${className}`} />
) : (
Expand Down
21 changes: 21 additions & 0 deletions src/components/download/Download.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
// src/components/download/Download.astro
import '@v1/styles/page-download.css';
import DownloadNav from '@components/download/DownloadNav.astro';

type DownloadProps = {
activeId?: string;
class?: string;
};

const { activeId, class: className = '' } = Astro.props as DownloadProps;
---

<div class={`download-container ${className}`}>
<aside class="download-sidebar">
<DownloadNav activeId={activeId} />
</aside>
<main class="download-content">
<slot />
</main>
</div>
Loading
Loading