Read this file at the start of any coding phase. These are personal coding conventions that apply to every project, every language, every framework. They override defaults and must be followed unless explicitly told otherwise.
Code is navigated spatially. The folder tree is a mental map — every directory, file, and function name is a landmark. When the structure is clean and names are descriptive, you can find what you need without searching. When it's not, you're lost in your own codebase.
These rules exist to keep code navigable, scannable, and small enough to hold in your head.
These apply to ALL code in ALL languages. No exceptions.
Every name must tell you what it is at a glance. Files, folders, functions, variables, types, classes, routes, components, database tables, columns — everything.
The test: Would you know what this is from the file tree alone, without opening it?
| Bad | Good |
|---|---|
utils.ts |
dateFormatUtils.ts |
helpers.ts |
pricingCalculationHelpers.ts |
data.ts |
userSessionData.ts |
handler() |
handleFileUploadComplete() |
process() |
processIncomingWebhookPayload() |
calc() |
calculateMonthlySubscriptionCost() |
Item |
InventoryLineItem |
manage() |
manageConnectionPool() |
Service |
NotificationDeliveryService |
tmp |
pendingUploadBuffer |
Prefer longer, descriptive names over short, ambiguous ones. Abbreviations are acceptable only when universally understood (id, url, http, db, api, auth).
The ~100-line limit applies to the function body only — the executable logic. Documentation comments above the function do NOT count toward this limit.
- If a function body is growing past ~100 lines, look for logical extraction points
- Each function should do one thing and do it well
- Aim for concise, precise code — fewer lines that do more, not sprawling logic
- Use one blank line to separate logical chunks within a function for readability — but no more than one
- Don't add unnecessary blank lines just for spacing — simplify the logic instead
The 1,000-line limit applies to code only. Documentation comments, file headers, and inline documentation do NOT count toward this limit. This limit exists to enforce efficient, concise code — not to punish well-documented code.
- Prefer more files with fewer functions each
- Every function in a file must logically belong there — don't put camera rotation functions in a text editor file
- Split where it makes semantic sense, not just to hit a number
- The folder nesting structure exists to support these smaller units
Write all code as if every function could be extracted into a standalone library. The end goal is maximum reusability across projects.
Principles:
- Functions should be self-contained with minimal external dependencies
- Clean, well-defined interfaces — inputs and outputs should be obvious
- Avoid hard-coded project-specific values inside reusable logic — pass them as parameters
- Group related functions that could form a library into their own files/modules
- In any language that supports libraries (C, Rust, JS/TS, Swift, etc.), structure code so extraction into a library requires moving files, not rewriting them
For C specifically: This is the primary target. Every utility, algorithm, data structure, and system abstraction should be written as a reusable library from day one. Headers define the public API contract, source files contain the implementation. A well-written C library can be linked into any project.
Every write to a database, file, or storage must trace back to a deliberate user action. No exceptions unless explicitly requested.
Never implement:
- Auto-saving as the user types
- Debounced save on input change
- Watchers that persist reactive state changes
- Background sync intervals
- Persisting UI state automatically (sidebar position, tab selection, scroll)
- Writing to storage on mount/unmount/navigation
Always require:
- User clicks "Save" → write
- User submits form → persist
- User explicitly toggles setting → save
- User confirms action → execute
Pattern: User Action → Service Call → Write → Confirm Success
Directories are not just organization — they're how you navigate. The tree structure should let you drill down to the right file by following a logical path, not by guessing or searching.
Principles:
- Group by domain/feature, not by file type (prefer
auth/authService.tsoverservices/auth.tswhen the module is complex enough) - Each directory should have a clear, single purpose
- Nesting depth should match conceptual depth — don't nest just for nesting, don't flatten when there's real hierarchy
- A directory with 1 file is usually wrong — either the file belongs in the parent or more files belong in the directory
- Never swallow errors silently
- Catch at service boundaries where you can show user feedback
- Log technical details (
console.errorwith context) - Show human-readable messages to users — never raw errors, stack traces, or technical details
- If you catch it, either handle it meaningfully or log it. Never empty catch blocks.
Don't comment out code. Don't leave unused functions, imports, or variables. If it's not being used, delete it. Git has the history if you ever need it back.
Every source file carries its own documentation. This gives both the developer and Claude immediate context the moment a file is opened — no hunting through separate docs.
Every source file MUST have a header block at the top with three sections: status, issues, and todo. All three sections are always present, even when empty. The empty slots serve as a reminder that documentation can go there — if the sections aren't present, they get forgotten.
Template (JS/TS/Vue):
/**
* @file DescriptiveFileName
* @description What this file is for and what it does (1-3 concise lines)
*
* @status None
* @issues None
* @todo None
*/Template (Rust):
//! # DescriptiveModuleName
//! What this module is for and what it does (1-3 concise lines)
//!
//! ## Status
//! None
//!
//! ## Issues
//! None
//!
//! ## Todo
//! None
Template (C — header files):
/**
* @file descriptive_name.h
* @brief What this header defines and its purpose (1-3 concise lines)
*
* @status None
* @issues None
* @todo None
*/Template (C — source files):
/**
* @file descriptive_name.c
* @brief Implementation of [what this file implements]
*
* @status None
* @issues None
* @todo None
*/Template (Swift):
/// # DescriptiveFileName
/// What this file is for and what it does (1-3 concise lines)
///
/// ## Status
/// None
///
/// ## Issues
/// None
///
/// ## Todo
/// NoneWhen there IS something to document:
/**
* @file AvatarUploadService
* @description Handles user avatar upload, validation, and storage.
* Supports JPEG, PNG, and WebP formats up to 10MB.
*
* @status Avatar upload works but no progress indicator yet
* @issues Timeout on large files (>10MB) — needs chunked upload
* @todo
* - [ ] Add upload progress bar (blocked on chunked upload)
* - [ ] Validate image dimensions before upload
*/What each section means:
| Section | Purpose | Example |
|---|---|---|
@status |
Current state of the code — what works, what's partial | "Core CRUD works, batch operations not yet implemented" |
@issues |
Known bugs, performance problems, fragile areas | "Race condition when two users edit simultaneously" |
@todo |
Incomplete work left during a session — breadcrumbs | "- [ ] Add input validation for email field" |
When working in any file, always check the header first. If there are completed TODOs, resolved issues, or outdated status notes — clean them up. Reset sections to "None" when everything is addressed. This prevents documentation rot.
The @todo section in file headers is NOT the project plan. The real task tracking lives in plan files and .teamide/tasks/. File-level TODOs exist for the "mid-session, had to step away" situation:
- You're deep in a function, a priority comes in — drop a TODO so you don't lose your place
- You notice something needs doing but it's not the current task — note it so it doesn't get forgotten
- If you come back to that file a month later, the TODO catches what fell through the cracks
Cleanup context: When a file-level TODO is completed, remove it from the header. When all TODOs are done, reset to "None". If a TODO turns into a real task, move it to the project plan and remove it from the header.
Document every public/exported function. Internal helper functions need docs only when the logic isn't obvious from the name and signature.
JS/TS pattern:
/**
* Calculates the prorated subscription cost for a partial billing period.
* Uses daily rate × remaining days, rounded to 2 decimal places.
*
* @param planMonthlyRate - The full monthly rate in dollars
* @param startDate - When the subscription started (mid-cycle)
* @param billingCycleEnd - End date of the current billing cycle
* @returns Prorated cost in dollars (e.g., 14.52)
*/
function calculateProratedSubscriptionCost(
planMonthlyRate: number,
startDate: Date,
billingCycleEnd: Date
): number {
// function body...
}C header pattern (API contract):
/**
* Allocates a block from the thread-local memory pool.
* Falls back to system malloc if pool is exhausted.
*
* @param size Bytes to allocate (rounded up to pool alignment)
* @return Pointer to allocated block, or NULL on failure
*
* Thread-safe: yes (per-thread pools, no locks needed)
* Reentrant: no (modifies pool state)
*/
void *pool_alloc(size_t size);Rust pattern:
/// Calculates the prorated subscription cost for a partial billing period.
/// Uses daily rate × remaining days, rounded to 2 decimal places.
///
/// # Arguments
/// * `plan_monthly_rate` - The full monthly rate in dollars
/// * `start_date` - When the subscription started (mid-cycle)
/// * `billing_cycle_end` - End date of the current billing cycle
///
/// # Returns
/// Prorated cost in dollars (e.g., 14.52)
fn calculate_prorated_subscription_cost(
plan_monthly_rate: f64,
start_date: NaiveDate,
billing_cycle_end: NaiveDate,
) -> f64 {
// function body...
}Use inline comments to explain why, not what. The code shows what it does — comments explain the reasoning that isn't obvious.
// Offset by 1 because the API uses 1-based page indexing
const apiPage = displayPage + 1;
// Must check auth before loading data — the API returns 200 with empty
// results for unauthorized users instead of 401
await verifyAuthStatus();
const results = await loadDashboardData();Don't write comments that just restate the code:
// BAD — restates the code
const total = price * quantity; // multiply price by quantity
// GOOD — explains the business rule
const total = price * quantity; // pre-tax total, tax applied at checkoutThe core principle is the same everywhere: categorical nesting by domain, with descriptive names, keeping files small and focused. Here's how that maps to each language ecosystem.
src/
├── modules/ # Feature modules (self-contained)
│ ├── auth/
│ │ ├── index.ts # Module entry, re-exports
│ │ ├── store.ts # Pinia store (composition API)
│ │ ├── api.ts # API calls for this module
│ │ ├── types.ts # Types/interfaces for this module
│ │ ├── AuthLogin.vue # Components named descriptively
│ │ ├── AuthRegister.vue
│ │ └── components/ # Sub-components if needed
│ │ └── AuthFormField.vue
│ └── dashboard/
│ ├── index.ts
│ ├── store.ts
│ ├── api.ts
│ └── DashboardOverview.vue
├── services/ # Shared services (API client, DB, etc.)
│ ├── apiClient.ts # Centralized fetch wrapper
│ ├── db/
│ │ ├── client.ts
│ │ └── tables/
│ │ ├── userSettings.ts
│ │ └── sessionHistory.ts
│ └── fileWatcher.ts
├── stores/ # Global stores (app-wide state)
│ └── globalAppState.ts
├── components/ # Shared components (used across modules)
│ ├── AppHeader.vue
│ └── ConfirmDialog.vue
├── composables/ # Shared composables
│ └── useWindowResize.ts
├── types/ # Global types
│ └── appTypes.ts
├── layouts/ # Page layouts
├── pages/ # Route pages
├── router/ # Vue Router config
├── boot/ # Startup/init files
├── css/ # Global styles
└── utils/ # Platform detection, formatters
└── platformDetection.ts
Key conventions:
- Each feature module has
index.ts,store.ts,api.ts,types.ts - Components are PascalCase:
UserProfileCard.vue - Everything else is camelCase:
apiClient.ts,useAuth.ts - Path aliases:
@/forsrc/,@modules/,@components/,@stores/
Apply the same categorical nesting using Rust's module system:
src/
├── main.rs # Entry point, minimal — just init and run
├── app.rs # Application setup, config, state
├── lib.rs # Library entry (if crate is also a lib)
├── commands/ # Tauri commands or CLI handlers
│ ├── mod.rs # Re-exports all command modules
│ ├── file_operations.rs # File read/write/watch commands
│ ├── window_management.rs # Window creation/positioning
│ └── backend_lifecycle.rs # Start/stop/health-check backend
├── services/ # Business logic, reusable across commands
│ ├── mod.rs
│ ├── process_manager.rs # Spawn/kill/monitor child processes
│ ├── port_allocator.rs # Find available ports
│ └── log_writer.rs # Write structured logs to files
├── models/ # Data structures, enums, types
│ ├── mod.rs
│ ├── app_config.rs # Config structs
│ └── backend_state.rs # Backend process state
├── platform/ # OS-specific code
│ ├── mod.rs # Conditional compilation re-exports
│ ├── macos.rs # macOS-specific (menus, notifications)
│ ├── windows.rs
│ └── linux.rs
└── utils/ # Small, focused helpers
├── mod.rs
└── path_resolver.rs # Resolve app paths, home dir, etc.
Key conventions:
mod.rsin each directory for re-exports (likeindex.ts)- snake_case for everything: files, functions, variables, modules
- Descriptive module names:
backend_lifecycle.rsnotbackend.rs - Keep
main.rsthin — delegate toapp.rsor modules immediately - Group by responsibility, not by Rust convention alone
- Include
lib.rswhen the crate should be usable as a library
Follow categorical nesting for headers, with source files mirroring the structure. Every module should be structured for library extraction from day one.
include/ # Public headers (categorical nesting)
├── core/ # Core functionality
│ ├── memory_allocator.h
│ ├── string_operations.h
│ └── error_codes.h
├── arch/ # Architecture-specific
│ ├── arm64.h
│ ├── x86_64.h
│ └── riscv32.h
├── os/ # OS-specific abstractions
│ ├── linux_syscalls.h
│ ├── darwin_syscalls.h
│ └── windows_syscalls.h
├── fmt/ # File format parsers
│ ├── elf_parser.h
│ ├── macho_parser.h
│ └── pe_parser.h
└── sys/ # System-level interfaces
└── thread_pool.h
src/ # Implementation files (mirror include/ structure)
├── core/
│ ├── memory_allocator.c
│ ├── string_operations.c
│ └── error_codes.c
├── arch/
│ ├── arm64.c
│ └── x86_64.c
└── os/
├── linux_syscalls.c
└── darwin_syscalls.c
tests/ # Test files (mirror src/ structure)
├── unit/
│ ├── test_memory_allocator.c
│ └── test_string_operations.c
└── integration/
└── test_full_pipeline.c
Key conventions:
include/andsrc/mirror each other's structure- Category directories by domain:
core/,arch/,os/,fmt/,sys/ - snake_case for all file names
- Header names describe what's inside:
memory_allocator.hnotmem.h - One header + one source per logical unit
- Headers define the full API contract (see Function Documentation section)
- Each category directory is a potential standalone library
For ESP32-C3 / RISC-V targets specifically. Tight RAM (~380 KB), no FPU, no PSRAM, and a single SPI bus shared between SD and display shape almost every implementation choice. See .skills/SKILL.md "The Resource Protocol" for the rules; this section covers techniques and habits.
Add -Wl,-Map=output.map to your linker flags. The map file shows exactly which symbol consumed which bytes. Read it after every meaningful change:
pio run -t size # PlatformIO summary
riscv32-esp-elf-nm --size-sort -t d firmware.elf | tail -50 # Largest symbols
riscv32-esp-elf-objdump -h firmware.elf # Section sizesLargest symbols are usually surprising and almost always actionable. Make this a weekly habit during active development.
For dynamic-but-bounded memory needs, prefer arenas over malloc/free. Allocate the backing buffer once (statically), bump-allocate from it, reset between major operations:
typedef struct {
uint8_t *base;
size_t size;
size_t used;
} arena_t;
void *arena_alloc(arena_t *a, size_t n) {
if (a->used + n > a->size) return NULL;
void *p = a->base + a->used;
a->used += n;
return p;
}
void arena_reset(arena_t *a) { a->used = 0; }Solves ~80% of dynamic-memory needs without fragmentation. Useful per-plugin or per-render-pass with a static backing buffer of a known maximum size.
Group flags and small enums into a single byte instead of letting each bool consume 4 bytes:
// 4 bytes
struct {
bool dirty;
bool valid;
bool needs_redraw;
uint8_t mode;
} state;
// 1 byte
struct {
uint8_t dirty : 1;
uint8_t valid : 1;
uint8_t needs_redraw : 1;
uint8_t mode : 2;
uint8_t reserved : 3;
} state;In-memory state only — bitfield ordering varies across compilers, so for wire/file formats use explicit bitwise ops (x |= (1 << 3)) instead.
Stack overflows on the C3 are silent (no MMU; the MPU helps but doesn't catch everything). Paint the stack with a known pattern at boot and check the high-water mark from a debug command:
void stack_paint(void) {
for (uint8_t *p = stack_start; p < stack_end; p++) *p = 0xA5;
}
size_t stack_used(void) {
uint8_t *p = stack_start;
while (*p == 0xA5 && p < stack_end) p++;
return stack_end - p;
}Print the result in development builds. Lets you size FreeRTOS task stacks based on data, not guesswork. For per-task stack inspection, FreeRTOS also exposes uxTaskGetStackHighWaterMark().
ESP-IDF defaults are reasonable but worth confirming in platformio.ini or sdkconfig:
-Os— optimize for size (often runs faster too because more fits in cache)-ffunction-sections -fdata-sections+-Wl,--gc-sections— strips unused code/data per function. 20-40% binary size reduction with zero source changes.-Werror=stack-usage=512— fails build if any function uses more than 512 bytes of stack. Forces discipline before crashes happen.-fstack-usage— generates a.sufile per source file showing per-function stack usage. Read these when chasing stack issues.
Routine habit: identify the biggest things in flash and RAM, decide whether they should be that big.
riscv32-esp-elf-nm --size-sort -t d firmware.elf | tail -50Common surprises: a const table that should be in flash but ended up in RAM (missing static const), printf format string proliferation, a library pulled in unexpectedly because of one feature flag, a struct member ordering that wasted half its space to padding.
Sources/
├── App/ # App entry, lifecycle
│ ├── AppMain.swift
│ └── AppDelegate.swift
├── Features/ # Feature modules
│ ├── Camera/
│ │ ├── CameraManager.swift
│ │ ├── CameraPreviewView.swift
│ │ └── CameraPermissions.swift
│ └── Recording/
│ ├── RecordingSession.swift
│ ├── RecordingOverlay.swift
│ └── RecordingStorage.swift
├── Services/ # Shared services
│ ├── FileStorageService.swift
│ └── NotificationService.swift
├── Models/ # Data models
│ ├── RecordingMetadata.swift
│ └── UserPreferences.swift
├── Views/ # Shared UI components
│ ├── PermissionRequestView.swift
│ └── StatusIndicator.swift
├── Extensions/ # Swift extensions
│ └── DateFormattingExtensions.swift
└── Utilities/ # Small helpers
└── DeviceCapabilities.swift
Key conventions:
- PascalCase for files (Swift convention), but still descriptive
- Feature-based grouping under
Features/ CameraPermissions.swiftnotPermissions.swift— qualify the name
When you encounter a new language, apply this structure:
src/ (or equivalent)
├── [entry point] # Minimal — just init and delegate
├── features/ or modules/ # Self-contained feature units
│ └── [feature_name]/
│ ├── [entry/index] # Re-exports or module definition
│ ├── [types/models] # Data structures for this feature
│ ├── [api/handlers] # External interface (API calls, commands)
│ └── [service/logic] # Business logic
├── services/ # Shared, cross-cutting services
├── models/ or types/ # Global data structures
├── platform/ # OS/environment-specific code
└── utils/ # Small, focused helpers
Organize imports in 3 groups, separated by blank lines:
// 1. Framework & language imports
import { ref, computed, watch, onMounted } from 'vue';
import { defineStore } from 'pinia';
// 2. Third-party libraries
import { format } from 'date-fns';
// 3. Local imports (project code)
import { useGlobalStore } from '@/stores/globalAppState';
import { request } from '@/services/apiClient';
import type { UserProfile } from './types';Always this order in single-file components:
<template>
<!-- Template first — the visual output -->
</template>
<script setup lang="ts">
// Script second — the logic
</script>
<style scoped lang="scss">
// Style last — scoped to this component
</style>Within <script setup>:
- Imports
- Store references
- Props & emits
- Reactive state (
ref,reactive) - Computed properties
- Methods / handlers
- Watchers
- Lifecycle hooks (
onMounted, etc.)
Always use composition API style:
export const useFeatureStore = defineStore('feature-name', () => {
// State
const items = ref<Item[]>([]);
const selectedItem = ref<Item | null>(null);
const isInitialized = ref(false);
// Init guard — prevent double initialization
async function initialize() {
if (isInitialized.value) return;
items.value = await featureApi.loadItems();
isInitialized.value = true;
}
// Actions
function selectItem(item: Item) {
selectedItem.value = item;
}
// Return everything that should be accessible
return { items, selectedItem, isInitialized, initialize, selectItem };
});Centralized request function + module-level API objects:
// services/apiClient.ts — single fetch wrapper, typed
export async function request<T>(path: string, options?: RequestInit): Promise<T> {
const response = await fetch(`${API_BASE}${path}`, {
headers: { 'Content-Type': 'application/json' },
...options
});
if (!response.ok) {
const error = await response.json().catch(() => ({ error: 'Request failed' }));
throw new Error(error.error || 'Request failed');
}
return response.json();
}
// modules/inventory/api.ts — module-specific API
export const inventoryApi = {
listProducts: (warehouseId: string): Promise<Product[]> =>
request(`/warehouses/${warehouseId}/products`),
updateStock: (productId: string, quantity: number): Promise<void> =>
request(`/products/${productId}/stock`, {
method: 'PATCH',
body: JSON.stringify({ quantity })
})
};- Strict mode always on
- No
any— useunknownwith type narrowing instead - Discriminated unions with type guard functions:
type EditorTab =
| { type: 'file'; path: string; content: string }
| { type: 'terminal'; sessionId: string }
| { type: 'diff'; leftPath: string; rightPath: string };
function isFileTab(tab: EditorTab): tab is EditorTab & { type: 'file' } {
return tab.type === 'file';
}- Interfaces for object shapes, types for unions and aliases
- PascalCase for types/interfaces, no
Iprefix (UsernotIUser)
- Scoped styles in components:
<style scoped lang="scss"> - CSS custom properties for theming:
var(--app-surface-color) - Dark theme by default
- kebab-case for CSS classes:
.file-tree-node,.panel-header - SCSS variables for design tokens (colors, spacing)
Semicolons: required
Quotes: single quotes
Trailing commas: none
Line width: 100 characters
Indentation: 2 spaces
constby default — only useletwhen reassignment is needed, nevervar- Arrow functions for most cases:
const handleClick = () => { ... } - Nullish coalescing:
value ?? fallbackovervalue || fallback - Optional chaining:
obj?.property?.method?.()over nested null checks - No magic numbers/strings — use named constants
- Colocate related code — don't scatter logic across files unnecessarily
- One blank line between logical chunks for readability — no more than one
| Thing | Convention | Example |
|---|---|---|
| Vue components | PascalCase | UserProfileCard.vue |
| TS/JS files | camelCase | userProfileService.ts |
| Rust files | snake_case | process_manager.rs |
| C files | snake_case | memory_allocator.c |
| Swift files | PascalCase | CameraManager.swift |
| Config files | kebab-case | quasar.config.js |
| Directories | camelCase (JS/TS), snake_case (Rust/C) | userAuth/, user_auth/ |
| Variables/functions | camelCase (JS/TS), snake_case (Rust/C), camelCase (Swift) | |
| Constants | UPPER_SNAKE_CASE | MAX_RETRY_ATTEMPTS |
| Types/Interfaces | PascalCase | UserSession, FileTreeNode |
| CSS classes | kebab-case | .sidebar-panel-header |
| Database tables | snake_case | user_sessions |
| Database columns | snake_case | created_at, session_token |