General coding standards and best practices. These rules apply to all code in the GROWI monorepo.
ALWAYS create new objects, NEVER mutate:
// ❌ WRONG: Mutation
function updateUser(user, name) {
user.name = name // MUTATION!
return user
}
// ✅ CORRECT: Immutability
function updateUser(user, name) {
return {
...user,
name
}
}
// ✅ CORRECT: Array immutable update
const updatedPages = pages.map(p => p.id === id ? { ...p, title: newTitle } : p);
// ❌ WRONG: Array mutation
pages[index].title = newTitle;
MANY SMALL FILES > FEW LARGE FILES:
UPPER_SNAKE_CASE for constants
const pageId = '123';
const MAX_PAGE_SIZE = 1000;
function getPageById(id: string) { }
class PageService { }
interface PageData { }
type PageStatus = 'draft' | 'published';
Button.tsx, PageTree.tsxpage-utils.tsfeatures/page-tree/, utils/Prefer named exports over default exports:
// ✅ Good: Named exports
export const MyComponent = () => { };
export function myFunction() { }
export class MyClass { }
// ❌ Avoid: Default exports
export default MyComponent;
Why?
Exception: Next.js pages require default exports.
Always provide explicit types for function parameters and return values:
// ✅ Good: Explicit types
function createPage(path: string, body: string): Promise<Page> {
// ...
}
// ❌ Avoid: Implicit any
function createPage(path, body) {
// ...
}
Use import type for type-only imports:
import type { PageData } from '~/interfaces/page';
ALWAYS handle errors comprehensively:
try {
const result = await riskyOperation();
return result;
} catch (error) {
logger.error('Operation failed:', { error, context });
throw new Error('Detailed user-friendly message');
}
Prefer async/await over Promise chains:
// ✅ Good: async/await
async function loadPages() {
const pages = await fetchPages();
const enriched = await enrichPageData(pages);
return enriched;
}
// ❌ Avoid: Promise chains
function loadPages() {
return fetchPages()
.then(pages => enrichPageData(pages))
.then(enriched => enriched);
}
Write comments in English (even for Japanese developers):
// ✅ Good: English comment
// Calculate the total number of pages in the workspace
// ❌ Avoid: Japanese comment
// ワークスペース内のページ総数を計算
When to comment:
When NOT to comment:
Co-locate tests with source files in the same directory:
src/utils/
├── helper.ts
└── helper.spec.ts # Test next to source
src/components/Button/
├── Button.tsx
└── Button.spec.tsx # Test next to component
*.spec.{ts,js}*.integ.ts*.spec.{tsx,jsx}Follow conventional commit format:
<type>(<scope>): <subject>
<body>
Types: feat, fix, refactor, test, docs, chore
Example:
feat(page-tree): add virtualization for large trees
Implemented react-window for virtualizing page tree
to improve performance with 10k+ pages.
GROWI must work on Windows, macOS, and Linux. Never use platform-specific shell commands in npm scripts.
// ❌ WRONG: Unix-only commands in npm scripts
"clean": "rm -rf dist",
"copy": "cp src/foo.ts dist/foo.ts",
"move": "mv src dist"
// ✅ CORRECT: Cross-platform tools
"clean": "rimraf dist",
"copy": "node -e \"require('fs').cpSync('src/foo.ts','dist/foo.ts')\"",
"move": "node -e \"require('fs').renameSync('src','dist')\""
Rules:
rimraf instead of rm -rfcpy-cli, cpx2) instead of cp, mv, echo, lsBefore marking work complete: