Skip to content
Merged
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
105 changes: 105 additions & 0 deletions .agents/skills/codeatlas-dx/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
name: codeatlas-dx
description: "Use when creating files, components, or feature modules: DX-first clean code, file size limits, no god files."
alwaysApply: false
---

# DX-first development

Write for the next developer (or AI). Files stay small, responsibilities stay clear, and shared logic lives in one place.

## File size rules

| File type | Max lines | Action at limit |
|---|---|---|
| Svelte component | 200 | Extract sub-components or composables |
| TypeScript module | 150 | Split by domain |
| Route `+page.svelte` | 300 | Move logic to `+page.ts` or a feature module |
| Store / composable | 80 | One state shape per file |

If you're scrolling to find something, the file is too long. Split it.

## Component structure

```
src/
lib/
components/
UserCard/
UserCard.svelte # UI only, no data fetching
UserCard.types.ts # Props type, not shared globally
features/
users/
users.store.svelte.ts # $state store for this feature
users.api.ts # fetch calls
users.utils.ts # formatters, validators
utils/
format.ts # formatCurrency, formatDate (pure fns)
storage.ts # localStorage helpers
```

## Rules

1. One component = one visual responsibility. If you're doing two things, split.
2. Logic used in 2+ places → extract to `utils/` or a composable. First use: inline.
3. Types live next to what they describe. No monolithic `types.ts`.
4. `$derived` for computed values. `$effect` only for side effects that can't be derived.
5. No `export let` in Svelte 5. Use `$props()`.
6. API calls belong in `*.api.ts`, not in components or stores.
7. Constants belong in `*.constants.ts` or top of the file that uses them. Not scattered.

## Naming

| Thing | Convention | Example |
|---|---|---|
| Component file | PascalCase | `UserCard.svelte` |
| Composable / store | camelCase + `.svelte.ts` | `users.store.svelte.ts` |
| API module | camelCase + `.api.ts` | `users.api.ts` |
| Utils | camelCase + `.ts` | `format.ts` |
| Types | PascalCase | `UserCardProps` |
| Boolean props | `is` / `has` prefix | `isLoading`, `hasError` |

## Anti-patterns

```svelte
<!-- ✗ God component: fetches, formats, renders, handles errors, manages state -->
<script>
// 400 lines of mixed concerns
</script>

<!-- ✓ Split: page fetches, component renders -->
<!-- +page.ts: load fn fetches data -->
<!-- +page.svelte: passes data to components -->
<!-- UserTable.svelte: renders the table only -->
```

```ts
// ✗ Barrel that exports 80 things from one index.ts
export * from './components/UserCard';
export * from './components/UserTable';
export * from './stores/users';
export * from './utils/format';
// ...40 more lines

// ✓ Import directly from the source file when building features
import { UserCard } from '$lib/components/UserCard/UserCard.svelte';
import { formatCurrency } from '$lib/utils/format';
// Only create barrels for published libraries, not internal feature modules
```

```ts
// ✗ Inline computation that belongs in a util
const formatted = `${value.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`;

// ✓
import { formatCurrency } from '$lib/utils/format';
const formatted = formatCurrency(value);
```

## Before creating a new file

Ask:
- Does this already exist in `@codeatlasdev/ui`? Use it.
- Does a util already do this? Import it.
- Is this logic used only once? Keep it inline.
- Will this be used in 3+ places? Extract it.
166 changes: 166 additions & 0 deletions .agents/skills/codeatlas-ui/ANTIPATTERNS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Anti-patterns

Explicit don'ts. Each one has a correct alternative.

---

## Layout

```svelte
<!-- ✗ Manual flex wrapper -->
<div style="display: flex; gap: 12px; align-items: center;">

<!-- ✓ -->
<Row gap={12} align="center">
```

```svelte
<!-- ✗ Arbitrary margin -->
<div style="margin-top: 24px;">
<TextField .../>
</div>

<!-- ✓ Let FormSection or Stack handle spacing -->
<FormSection title="Details">
<TextField .../>
</FormSection>
```

```svelte
<!-- ✗ Hardcoded color value -->
<div style="background: #1e293b; color: #f8fafc;">

<!-- ✓ Semantic token -->
<div style="background: var(--ca-glass-bg); color: var(--ca-text-primary);">
```

```css
/* ✗ Tint in OKLCH: gray tokens have hue 0°, so blue drifts to purple/pink */
background: color-mix(in oklch, var(--ca-brand-main) 15%, var(--ca-glass-tint));

/* ✓ Mix a color with a neutral (gray token, black, white, transparent) in OKLab */
background: color-mix(in oklab, var(--ca-brand-main) 15%, var(--ca-glass-tint));
```

---

## GlassCard

```svelte
<!-- ✗ Manual padding override -->
<GlassCard style="padding: 0;">

<!-- ✓ -->
<GlassCard flush>

<!-- ✗ will-change + backdrop-filter: kills glass blur -->
<div style="will-change: transform; backdrop-filter: blur(12px);">

<!-- ✓ Never combine these two on the same element or its ancestors -->
```

---

## AppShell

```svelte
<!-- ✗ AppShellLayout in root layout -->
<!-- src/routes/+layout.svelte -->
<AppShellLayout ...> <!-- applies to /login, /404, etc. -->

<!-- ✓ Only in authenticated section -->
<!-- src/routes/painel/+layout.svelte -->
<AppShellLayout ...>
```

---

## Imports

```ts
// ✗ Internal path import
import Button from '../../../packages/ui/src/lib/components/Button/Button.svelte';

// ✓
import { Button } from '@codeatlasdev/ui';
```

```svelte
<!-- ✗ Importing CSS in nested layouts -->
<!-- src/routes/painel/+layout.svelte -->
<script>
import '@codeatlasdev/ui/styles/global.css'; // already imported in root
</script>

<!-- ✓ CSS imported once, in root layout only -->
```

---

## Components

```svelte
<!-- ✗ Stacking form fields manually inside FormSection -->
<FormSection title="Account">
<Stack gap={16}>
<TextField label="Name" />
<TextField label="Email" />
</Stack>
</FormSection>

<!-- ✓ FormSection layout prop handles it -->
<FormSection title="Account">
<TextField label="Name" />
<TextField label="Email" />
</FormSection>
```

```svelte
<!-- ✗ Using Tailwind utility classes -->
<div class="flex items-center gap-3 mt-4 px-[17px]">

<!-- ✓ Layout primitives + token gap values -->
<Row gap={12} align="center">
```

```svelte
<!-- ✗ Inline color for status -->
<span style="color: green;">Active</span>

<!-- ✓ -->
<StatusChip status="success" label="Active" />
```

---

## Svelte 5

```svelte
<!-- ✗ Legacy reactive declarations -->
$: fullName = `${first} ${last}`;

<!-- ✓ -->
const fullName = $derived(`${first} ${last}`);
```

```svelte
<!-- ✗ Legacy event directive -->
<button on:click={handler}>

<!-- ✓ -->
<button onclick={handler}>
```

```svelte
<!-- ✗ {@const} outside valid context -->
<div>
{@const Icon = item.icon} <!-- invalid: not inside #each/#if/snippet -->
<Icon />
</div>

<!-- ✓ Inside #each -->
{#each items as item}
{@const Icon = item.icon}
<Icon size={16} />
{/each}
```
Loading
Loading