| name | typescript-conventions |
| description | Use this skill when writing or reviewing TypeScript code in the frontend to follow project conventions. Covers naming standards (kebab-case files), import patterns, error handling, type safety (no any), and ESLint/Prettier configuration. Apply when authoring new TypeScript files, reviewing code style, or resolving linting issues.
|
TypeScript Conventions
Style & Formatting
- Follow
.editorconfig and ESLint + Prettier config strictly
- Run
npm run format before committing
- Minimize diffs: Change only what's necessary, preserve existing formatting and structure
- Match surrounding code style exactly
Project Frontend Rules
- Always use braces for all control flow statements (
if, for, while, etc.).
- Always use block bodies for arrow functions that return statements; avoid single-expression shorthand in project code.
- Do not use abbreviations in identifiers (
organization, not org; filter, not filt).
- Avoid inline single-line condition + return patterns; use multi-line blocks for readability.
File Naming
- Use kebab-case for files and directories
- Component files:
user-profile.svelte
- TypeScript files:
api-client.ts, user-service.ts
- Test files:
user-service.test.ts or user-service.spec.ts
Imports
Prefer Named Imports
import { UserService, type User } from "$lib/services/user-service";
import { formatDate, formatNumber } from "$lib/utils/formatters";
import * as utils from "$lib/utils";
- Named imports are preferred for most modules; namespace imports should be limited to approved patterns (e.g., shadcn composite imports).
Allowed Namespace Imports
import * as Dialog from "$comp/ui/dialog";
import * as DropdownMenu from "$comp/ui/dropdown-menu";
import * as Field from "$comp/ui/field";
Type Safety
Avoid any
function processData(data: any) { ... }
interface UserData {
id: string;
name: string;
email: string;
}
function processData(data: UserData) { ... }
function parseResponse(data: unknown): UserData {
if (isUserData(data)) {
return data;
}
throw new Error('Invalid data format');
}
Type Guards
function isUserData(data: unknown): data is UserData {
return (
typeof data === "object" &&
data !== null &&
"id" in data &&
"name" in data &&
"email" in data
);
}
type ApiResponse =
| { status: "success"; data: UserData }
| { status: "error"; error: string };
function handleResponse(response: ApiResponse) {
if (response.status === "success") {
return response.data;
}
throw new Error(response.error);
}
Promise Handling
Always Await
const user = await fetchUser(id);
const [users, projects] = await Promise.all([fetchUsers(), fetchProjects()]);
fetchUser(id);
Error Handling
async function loadUser(id: string): Promise<User | null> {
try {
const response = await api.get<User>(`/users/${id}`);
return response.data;
} catch (error) {
if (error instanceof ApiError) {
console.error("API Error:", error.message);
}
return null;
}
}
Control Statements
All single-line control statements need braces:
if (condition) {
doSomething();
}
for (const item of items) {
process(item);
}
if (condition) doSomething();
Interface Naming
Follow HTTP verb prefixes for API-related types:
interface PostOrganizationRequest {
name: string;
billing_email: string;
}
interface GetOrganizationParams {
id: string;
}
interface PatchUserRequest {
name?: string;
email?: string;
}
Export Patterns
export function createUser(data: CreateUserRequest): Promise<User> { ... }
export type { User, CreateUserRequest };
export { createUser, updateUser, deleteUser } from './api.svelte';
export type { User, CreateUserRequest } from './models';
Modern ES6+ Features
const message = `Hello, ${user.name}!`;
const { id, name, email } = user;
const [first, ...rest] = items;
const displayName = user.nickname ?? user.name ?? "Anonymous";
const city = user?.address?.city;
const data = { id, name, createdAt: new Date() };