Files
ArchiTools/docs/guides/CODING-STANDARDS.md
Marius Tarau 4c46e8bcdd Initial commit: ArchiTools modular dashboard platform
Complete Next.js 16 application with 13 fully implemented modules:
Email Signature, Word XML Generator, Registratura, Dashboard,
Tag Manager, IT Inventory, Address Book, Password Vault,
Mini Utilities, Prompt Generator, Digital Signatures,
Word Templates, and AI Chat.

Includes core platform systems (module registry, feature flags,
storage abstraction, i18n, theming, auth stub, tagging),
16 technical documentation files, Docker deployment config,
and legacy HTML tool reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:50:25 +02:00

685 lines
18 KiB
Markdown

# Coding Standards
Conventions and rules for all code in the ArchiTools repository.
---
## Language
TypeScript in strict mode. The `tsconfig.json` must include:
```json
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true
}
}
```
**No `any`** unless there is a documented, unavoidable reason (e.g., third-party library with missing types). When `any` is truly necessary, use `// eslint-disable-next-line @typescript-eslint/no-explicit-any` with a comment explaining why.
Prefer `unknown` over `any` when the type is genuinely unknown. Narrow with type guards.
---
## File Naming
| Entity | Convention | Example |
|---|---|---|
| Files (components) | kebab-case | `signature-preview.tsx` |
| Files (hooks) | kebab-case with `use-` prefix | `use-signature-builder.ts` |
| Files (utils/services) | kebab-case | `sanitize-xml-name.ts` |
| Files (types) | `types.ts` within module | `types.ts` |
| Files (tests) | `*.test.ts` / `*.test.tsx` | `sanitize-xml-name.test.ts` |
| Component names | PascalCase | `SignaturePreview` |
| Hook names | camelCase with `use` prefix | `useSignatureBuilder` |
| Utility functions | camelCase | `sanitizeXmlName` |
| Constants | UPPER_SNAKE_CASE | `DEFAULT_NAMESPACE` |
| Type/Interface names | PascalCase | `SignatureConfig` |
| Enum values | PascalCase | `FieldVariant.Upper` |
---
## Component Patterns
### Functional Components Only
No class components. All components are functions.
```tsx
// Correct
export function SignaturePreview({ config }: SignaturePreviewProps) {
return <div>...</div>;
}
// Wrong: arrow function export (less debuggable in stack traces)
export const SignaturePreview = ({ config }: SignaturePreviewProps) => { ... };
// Wrong: default export
export default function SignaturePreview() { ... }
```
Exception: arrow functions are acceptable for small inline components passed as props or used in `.map()`.
### Props Interface
Define the props interface directly above the component, in the same file. Suffix with `Props`.
```tsx
interface SignaturePreviewProps {
config: SignatureConfig;
className?: string;
onExport?: () => void;
}
export function SignaturePreview({ config, className, onExport }: SignaturePreviewProps) {
// ...
}
```
If the props interface is reused across multiple files, define it in the module's `types.ts` and import it.
### Named Exports
All exports are named. No default exports anywhere in the codebase.
Rationale: named exports enforce consistent import names, improve refactoring, and work better with tree-shaking.
```tsx
// Correct
export function SignaturePreview() { ... }
export function useSignatureBuilder() { ... }
export interface SignatureConfig { ... }
// Wrong
export default function SignaturePreview() { ... }
```
### Co-located Styles
All styling is done with Tailwind classes in JSX. No CSS modules, no styled-components, no separate `.css` files per component.
```tsx
// Correct
<div className="rounded-xl border bg-card p-6 shadow-sm">
// Wrong: external CSS file
import styles from './signature-preview.module.css';
<div className={styles.card}>
```
Global styles live only in `src/app/globals.css` (theme tokens, font imports, base resets).
### Conditional Classes
Use the `cn()` utility (from `src/shared/utils/cn.ts`, wrapping `clsx` + `tailwind-merge`):
```tsx
import { cn } from '@/shared/utils/cn';
<div className={cn(
'rounded-xl border p-6',
isActive && 'border-primary bg-primary/5',
className
)} />
```
---
## Hook Patterns
### Naming
All hooks start with `use`. File names match: `use-signature-builder.ts` exports `useSignatureBuilder`.
### Return Type
Return a typed object, not a tuple/array.
```tsx
// Correct
export function useSignatureBuilder(config: SignatureConfig): SignatureBuilderState {
// ...
return { html, previewData, isGenerating };
}
// Wrong: tuple return (positional args are fragile)
export function useSignatureBuilder(config: SignatureConfig) {
return [html, previewData, isGenerating];
}
```
Exception: simple two-value hooks that mirror `useState` semantics may return a tuple if the pattern is unambiguous: `const [value, setValue] = useSomeState()`.
### Single Responsibility
Each hook does one thing. If a hook grows beyond ~80 lines, consider splitting.
```
useSignatureConfig -- manages form state
useSignatureBuilder -- generates HTML from config
useSignatureExport -- handles file download
```
Not:
```
useSignature -- does everything
```
### Dependencies
Hooks that depend on external services (storage, API) receive them as parameters:
```tsx
export function useCategoryManager(storage: StorageAdapter): CategoryManagerState {
// ...
}
```
This enables testing with mock storage.
---
## Service Patterns
Services are modules that encapsulate business logic outside of React's component lifecycle.
### Pure Functions Where Possible
```tsx
// Correct: pure function, easy to test
export function sanitizeXmlName(name: string): string | null {
// ...
}
// Correct: pure function with explicit dependencies
export function generateXml(input: XmlGeneratorInput): XmlGeneratorResult {
// ...
}
```
### Accept Dependencies via Parameters
```tsx
// Correct: zip library passed in (or imported at module level, testable via jest.mock)
export async function createZipArchive(
files: Record<string, string>,
zipFactory: () => JSZip = () => new JSZip()
): Promise<Blob> {
const zip = zipFactory();
// ...
}
```
### No Direct DOM Access
Services must never call `document.*`, `window.localStorage`, or any browser API directly. All browser interactions are mediated through hooks or adapters that are injected.
```tsx
// Wrong: service directly accessing DOM
export function downloadFile(content: string) {
const a = document.createElement('a'); // NO
// ...
}
// Correct: service returns data, hook handles DOM interaction
export function prepareDownload(content: string, filename: string): DownloadPayload {
return { blob: new Blob([content]), filename };
}
```
---
## Import Ordering
Imports are grouped in this order, separated by blank lines:
```tsx
// 1. React
import { useState, useCallback } from 'react';
// 2. Next.js
import { useRouter } from 'next/navigation';
// 3. Third-party libraries
import JSZip from 'jszip';
import { z } from 'zod';
// 4. Core (@/core)
import { labels } from '@/core/i18n/labels';
import { useStorage } from '@/core/storage/use-storage';
// 5. Shared (@/shared)
import { Button } from '@/shared/components/ui/button';
import { cn } from '@/shared/utils/cn';
// 6. Module-level (@/modules)
import { useXmlGenerator } from '@/modules/xml-generator/hooks/use-xml-generator';
// 7. Relative imports (same module)
import { SignaturePreview } from './signature-preview';
import type { SignatureConfig } from '../types';
```
Enforce with ESLint `import/order` rule.
---
## Path Aliases
Configured in `tsconfig.json`:
```json
{
"compilerOptions": {
"paths": {
"@/core/*": ["./src/core/*"],
"@/shared/*": ["./src/shared/*"],
"@/modules/*": ["./src/modules/*"],
"@/config/*": ["./src/config/*"]
}
}
}
```
**Rules:**
- Always use path aliases for cross-boundary imports.
- Use relative imports only within the same module directory.
- Never use `../../../` chains that cross module boundaries.
```tsx
// Correct: cross-boundary via alias
import { labels } from '@/core/i18n/labels';
// Correct: within same module via relative
import { SignaturePreview } from './signature-preview';
// Wrong: relative path crossing module boundary
import { labels } from '../../../core/i18n/labels';
```
---
## No Barrel Re-exports from Module Boundaries
Do not create `index.ts` files that re-export everything from a module.
```
// WRONG: src/modules/xml-generator/index.ts
export * from './hooks/use-xml-generator';
export * from './hooks/use-category-manager';
export * from './utils/sanitize-xml-name';
export * from './types';
```
Rationale: barrel files break tree-shaking in Next.js, cause circular dependency issues, and make import costs invisible. Import directly from the specific file.
```tsx
// Correct: direct import
import { useXmlGenerator } from '@/modules/xml-generator/hooks/use-xml-generator';
// Wrong: barrel import
import { useXmlGenerator } from '@/modules/xml-generator';
```
Exception: `src/shared/components/ui/` may use barrel re-exports since shadcn/ui generates them and they are leaf components with no circular dependency risk.
---
## Type Conventions
### `interface` vs `type`
- Use `interface` for object shapes (props, configs, data models).
- Use `type` for unions, intersections, mapped types, and utility types.
```tsx
// Object shape: interface
interface SignatureConfig {
prefix: string;
name: string;
colors: ColorMap;
}
// Union: type
type FieldVariant = 'base' | 'short' | 'upper' | 'lower' | 'initials' | 'first';
// Intersection: type
type SignatureWithMeta = SignatureConfig & { id: string; createdAt: string };
// Mapped type: type
type PartialSignature = Partial<SignatureConfig>;
```
### Naming Suffixes
| Suffix | Usage | Example |
|---|---|---|
| `Props` | Component props | `SignaturePreviewProps` |
| `State` | Hook return types | `SignatureBuilderState` |
| `Result` | Service return types | `XmlGeneratorResult` |
| `Config` | Configuration objects | `SignatureConfig` |
| `Input` | Service/function input parameters | `XmlGeneratorInput` |
### Enums
Prefer union types over TypeScript enums for simple value sets:
```tsx
// Preferred
type GeneratorMode = 'simple' | 'advanced';
// Acceptable for larger sets with associated logic
enum FieldVariant {
Base = 'base',
Short = 'short',
Upper = 'upper',
Lower = 'lower',
Initials = 'initials',
First = 'first',
}
```
---
## Error Handling
### Result Pattern for Services
Service functions that can fail return a discriminated union instead of throwing:
```tsx
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
export function parseXmlConfig(raw: string): Result<XmlConfig> {
try {
const parsed = JSON.parse(raw);
// validate...
return { success: true, data: parsed };
} catch (e) {
return { success: false, error: new Error('Invalid configuration format') };
}
}
```
### try/catch at Boundaries
Use `try/catch` only at the boundary between services and UI (in hooks or event handlers):
```tsx
export function useXmlExport() {
const handleDownload = useCallback(async () => {
try {
const blob = await createZipArchive(files);
triggerDownload(blob, filename);
} catch (error) {
toast.error(labels.common.exportFailed);
}
}, [files, filename]);
return { handleDownload };
}
```
Do not scatter `try/catch` through utility functions. Let errors propagate to the boundary.
### Never Swallow Errors Silently
```tsx
// Wrong: silent failure
try { storage.set('key', data); } catch (e) {}
// Correct: at minimum, log
try {
storage.set('key', data);
} catch (error) {
console.error('Failed to persist data:', error);
}
```
---
## Comments
### When to Comment
- Non-obvious business logic (e.g., "POT = SuprafataConstruitaLaSol / SuprafataTeren per Romanian building code").
- Workarounds for known issues (link to issue/PR).
- Regex patterns (always explain what the regex matches).
- Magic numbers that cannot be replaced with named constants.
### When Not to Comment
- Self-explanatory code. If the function is `sanitizeXmlName`, do not add `/** Sanitizes an XML name */`.
- JSDoc on every function. Add JSDoc only on public API boundaries of shared utilities.
- TODO comments without an owner or issue reference.
### Comment Style
```tsx
// Single-line comments for inline explanations.
/**
* Multi-line JSDoc only for shared utility public API.
* Describe what the function does, not how.
*/
export function formatPhoneNumber(raw: string): PhoneFormatResult {
// Romanian mobile numbers: 07xx xxx xxx -> +40 7xx xxx xxx
// ...
}
```
---
## Romanian Text
All user-facing Romanian text lives exclusively in `src/core/i18n/labels.ts`.
**Components never contain inline Romanian strings:**
```tsx
// Wrong
<Button>Descarca HTML</Button>
// Correct
import { labels } from '@/core/i18n/labels';
<Button>{labels.signature.exportHtml}</Button>
```
**Exceptions:**
- Test files may contain Romanian strings as test fixtures.
- Data files (presets, defaults) may contain Romanian field names that are domain terms, not UI labels.
Romanian diacritics in label values: use them where correct (`Semnatura` vs `Semnatura` -- prefer with diacritics in label constants if the display context supports it). For technical identifiers (XML field names, CSS classes, file paths), never use diacritics.
---
## Git Conventions
### Commit Messages
Follow [Conventional Commits](https://www.conventionalcommits.org/):
```
<type>(<scope>): <description>
[optional body]
[optional footer]
```
**Types:**
| Type | Usage |
|---|---|
| `feat` | New feature |
| `fix` | Bug fix |
| `refactor` | Code restructuring without behavior change |
| `docs` | Documentation only |
| `style` | Formatting, whitespace (not CSS) |
| `test` | Adding or fixing tests |
| `chore` | Build, tooling, dependencies |
| `perf` | Performance improvement |
**Scope:** module name or area (`signature`, `xml-generator`, `core`, `ui`, `config`).
**Examples:**
```
feat(signature): add multi-company support to signature builder
fix(xml-generator): handle duplicate field names in category
refactor(core): extract storage abstraction from localStorage calls
docs(guides): add HTML tool integration plan
chore(deps): upgrade shadcn/ui to 2.1.0
```
### Branching
- `main` -- production-ready code. Protected. No direct pushes.
- `feature/<description>` -- feature branches, branched from `main`.
- `fix/<description>` -- bugfix branches.
- `chore/<description>` -- maintenance branches.
### Pull Requests
- One logical change per PR.
- PR title follows conventional commit format.
- PR description includes: what changed, why, how to test.
- All CI checks must pass before merge.
- Squash merge to `main` (clean linear history).
---
## Anti-Patterns
The following patterns are explicitly prohibited in this codebase. Each entry includes the reason and the correct alternative.
### 1. `any` as escape hatch
```tsx
// Wrong
const data: any = fetchSomething();
// Correct
const data: unknown = fetchSomething();
if (isValidConfig(data)) { /* narrow type */ }
```
### 2. Default exports
```tsx
// Wrong
export default function Page() { ... }
// Correct (Next.js pages are the sole exception -- App Router requires default exports for page.tsx/layout.tsx)
// For page.tsx and layout.tsx ONLY, default export is acceptable because Next.js requires it.
export default function Page() { ... }
// Everything else: named exports
export function SignatureForm() { ... }
```
### 3. Direct DOM manipulation in components
```tsx
// Wrong
useEffect(() => {
document.getElementById('preview')!.innerHTML = html;
}, [html]);
// Correct
return <div dangerouslySetInnerHTML={{ __html: html }} />;
// Or better: render structured JSX from data
```
### 4. Inline Romanian strings
```tsx
// Wrong
<Label>Nume si Prenume</Label>
// Correct
<Label>{labels.signature.fieldName}</Label>
```
### 5. God hooks
```tsx
// Wrong: hook that manages 15 pieces of state and 10 callbacks
export function useEverything() { ... }
// Correct: split by responsibility
export function useSignatureConfig() { ... }
export function useSignatureBuilder() { ... }
export function useSignatureExport() { ... }
```
### 6. Barrel re-exports at module boundary
```tsx
// Wrong: src/modules/xml-generator/index.ts
export * from './hooks/use-xml-generator';
// Correct: import directly from source file
import { useXmlGenerator } from '@/modules/xml-generator/hooks/use-xml-generator';
```
### 7. Business logic in components
```tsx
// Wrong: XML sanitization logic inline in JSX
function XmlForm() {
const sanitize = (name: string) => {
let n = name.replace(/\s+/g, '_');
// 20 more lines of logic...
};
}
// Correct: extract to utility, test separately
import { sanitizeXmlName } from '../utils/sanitize-xml-name';
```
### 8. Untyped hook returns
```tsx
// Wrong: caller has to guess the shape
export function useConfig() {
return [config, setConfig, isLoading, error];
}
// Correct: explicit interface
interface ConfigState {
config: Config;
setConfig: (c: Config) => void;
isLoading: boolean;
error: Error | null;
}
export function useConfig(): ConfigState { ... }
```
### 9. `useEffect` for derived state
```tsx
// Wrong: effect to compute a value from props
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(`${prefix} ${name}`);
}, [prefix, name]);
// Correct: derive during render
const fullName = `${prefix} ${name}`;
// Or useMemo if computation is expensive
const fullName = useMemo(() => expensiveFormat(prefix, name), [prefix, name]);
```
### 10. Committing secrets or environment files
Never commit `.env`, `.env.local`, credentials, API keys, or tokens. The `.gitignore` must include these patterns. If a secret is accidentally committed, rotate it immediately -- do not just delete the file.