This document describes the coding conventions used in the Twick monorepo. Follow these patterns so new code stays consistent with the existing codebase.
- Use PascalCase for class names.
- Use PascalCase for file names when the file exports a single main class (e.g.
track.tsforTrack,element-serializer.tsforElementSerializer).
export class Track { ... }
export class ElementController { ... }
export abstract class TrackElement { ... }- Use PascalCase for interfaces and type aliases.
- Prefer
interfacefor object shapes; usetypefor unions, aliases, and composite props.
export interface Position {
x: number;
y: number;
}
export interface ElementJSON { ... }
export type TrackType = (typeof TRACK_TYPES)[keyof typeof TRACK_TYPES];
export type TextPanelProps = TextPanelState & TextPanelActions;- Use UPPER_SNAKE_CASE for exported constant objects and primitive constants.
- Use
as constwhen you need literal types.
export const TRACK_TYPES = {
VIDEO: "video",
ELEMENT: "element",
} as const;
export const WORDS_PER_PHRASE = 4;- Use camelCase for variables, function names, and method names.
const elementController = new ElementController();
const minDist = thresholdSec;
getCurrentElements(tracks);
convertToVideoPosition(x, y, metadata, size);- Use PascalCase for component names.
- Props types: PascalCase with a
Propssuffix (e.g.TextPanelProps).
export type TextPanelProps = TextPanelState & TextPanelActions;
export function TextPanel({ textContent, fontSize, ... }: TextPanelProps) {
return ( ... );
}- Use camelCase with a
useprefix (e.g.useTextPanel,useTwickCanvas).
export const useTextPanel = ({ selectedElement, addElement, updateElement }) => { ... };
export const useTwickCanvas = ({ width, height, ... }) => { ... };- Use kebab-case for file and folder names.
- Use descriptive suffixes when helpful:
.element.ts,.controller.ts,.util.ts,use-*.ts.
Examples:
use-text-panel.tselement.controller.tscanvas.util.tselement-serializer.tstext-panel.tsx
- Prefer
constarrow functions for most functions (utils, hooks, handlers, helpers).
export const snapTime = (
time: number,
targets: number[],
thresholdSec: number = 0.1
): SnapResult => { ... };
export const useTextPanel = ({ ... }): TextPanelState & TextPanelActions => { ... };
export const convertToVideoPosition = (
x: number,
y: number,
canvasMetadata: CanvasMetadata,
videoSize: Size
): Position => { ... };functiondeclarations are acceptable for:- Top-level exported pure functions (e.g. in small utility modules).
- React function components, when you prefer
function ComponentName()overconst ComponentName = () =>.
export function snapTime(time: number, targets: number[], thresholdSec = 0.1): SnapResult {
...
}
export function TextPanel(props: TextPanelProps) {
...
}- Use arrow functions for inline callbacks and non-exported helpers to keep style consistent.
- Use
import typewhen importing only types or interfaces.
import type { CanvasElementHandler } from "../types";
import type { ElementVisitor } from "../visitor/element-visitor";
import { Track } from "../core/track/track";- Prefer named exports for public API. Use default export only for single primary exports (e.g. a singleton or the main hook/component of a file).
export { useTwickCanvas } from "./hooks/use-twick-canvas";
export { default as elementController } from "./controllers/element.controller";- Avoid
anywhere possible; useunknownor proper types. - Use optional chaining and nullish coalescing for safe defaults:
props?.x ?? 0. - Export types that are part of the public API from package
index.ts(e.g.export type { CanvasProps, ... } from "./types").
- Indent: 2 spaces.
- Strings: Double quotes
"for consistency with the majority of the codebase. - Semicolons: Use semicolons at the end of statements.
- Line length: Prefer readable line length; break long lines rather than exceeding ~100–120 characters when it hurts readability.
- Add JSDoc for public APIs: exported functions, classes, hooks, and non-obvious types.
- Include
@param,@returns, and@examplewhere they help (especially for utilities and shared hooks).
/**
* Snaps a time value to the nearest target within the threshold.
*
* @param time - The candidate time in seconds
* @param targets - Array of snap target times in seconds
* @param thresholdSec - Maximum distance (in seconds) to snap. Default 0.1.
* @returns SnapResult with time, didSnap, and optional snapTarget
*
* @example
* ```ts
* snapTime(5.07, [0, 5, 10], 0.1) // { time: 5, didSnap: true, snapTarget: 5 }
* ```
*/
export const snapTime = ( ... ) => { ... };- React components can document props in JSDoc (see
text-panel.tsxfor an example).
src/– Source code.src/index.ts– Public API: re-export types, constants, hooks, and main APIs. Keep implementation in other files.- Group by concern:
hooks/,helpers/,components/,controllers/,elements/,utils/,context/as needed.
- Put imports first (group: external, then internal;
import typecan be with other imports or grouped separately). - Then constants and types, then main implementation, then exports (or use
exportinline).
- Canvas/visualizer element handlers are often objects keyed by type with methods (e.g.
add,updateFromFabricObject), exported asconst XxxElement: CanvasElementHandler = { ... }. - Controllers that act as registries (e.g.
ElementController) are implemented as classes and may be exported as a singleton default plus the class.
- Use function components (either
function Nameorconst Name = () =>). - Keep components focused; extract logic into custom hooks (e.g.
useTextPanel,useTwickCanvas). - Props: define with interfaces or types in PascalCase (e.g.
TextPanelProps) and use them in the component signature. - Prefer named exports for components and hooks; use default export only when a file has a single primary export (e.g. a page or app root).
- Classes: PascalCase
- Interfaces/types: PascalCase;
interfacefor objects,typefor unions/aliases - Constants: UPPER_SNAKE_CASE;
as constwhere needed - Variables, functions, methods: camelCase
- React components: PascalCase; props type with
Propssuffix - Hooks: camelCase with
useprefix - Files/folders: kebab-case
- Functions: Prefer
const fn = (): ReturnType =>;functionallowed for components and some pure utils - Imports:
import typefor type-only imports - Strings: Double quotes; semicolons; 2-space indent
- Public API: JSDoc with
@param,@returns,@examplewhere helpful
For the contribution workflow (issues, PRs, commits), see CONTRIBUTING.md.