# Code Style

This document describes how code is written in the SurrealDB UI Kit. It is
derived from analysing the existing source under `src/`, which is the
**leading** authority: when in doubt, match the surrounding code. The goal is
that anyone can write new code indistinguishable from what is already there.

Formatting (tabs, 100-column width, one-attribute-per-line JSX, import ordering)
is enforced by oxfmt. This document covers the conventions the formatter and
linter do **not** enforce: structure, naming, comments, and idiom.

## Files and extensions

- **`.tsx` everywhere in `/src`** — including files
  that contain no JSX (for example `api/helpers.tsx`, `api/keys.tsx`,
  `api/publisher.tsx`). The framework packages use `.tsx` as the default source
  extension.
- **Component folders** are `PascalCase` and contain an `index.tsx` plus, when
  needed, a `style.module.scss`. Sub-parts of a component live beside it as
  lowercase files (e.g. `section.tsx`, `view.tsx`, `list.tsx`) or in nested
  `PascalCase` folders.
- **Hook and utility files** are `kebab-case`: `use-stable.tsx`,
  `use-registry.tsx`, `make-panel-type.tsx`, `clone.ts`.
- **SCSS modules** are always named `style.module.scss` and imported as
  `import classes from "./style.module.scss"`.

## Components

**Always define components as typed `const` arrow functions, never as `function`
declarations.** This is the dominant convention across `core`.

```tsx
export interface EntryButtonProps
  extends ButtonProps, ElementProps<"button">, RefAttributes<HTMLButtonElement> {}

export const EntryButton: FC<...> = ({ children, className, ...other }) => {
  return (
    <Button
      {...other}
      className={clsx(classes.root, className)}
    >
      {children}
    </Button>
  );
};
```

Conventions:

- Type the component with `FC` (or `FC<Props>`) imported from `react`, and
  destructure props in the parameter list.
- Name the props interface `<Component>Props`. Extend Mantine prop types
  (`ButtonProps`, `BoxProps`, `ElementProps<"...">`), `PropsWithChildren`, and
  `RefAttributes<...>` where relevant, rather than re-declaring common props.
- **Named exports** are the norm; avoid default exports.
- When a component needs an inner implementation (for example to sit behind a
  `<Suspense>` or memo boundary), the exported component is a thin wrapper and the
  implementation is a private `const XxxInner: FC<...>` in the same file:

```tsx
export const WorkspaceMixin: FC<WorkspaceMixinProps> = memo((props) => (
	<Suspense>
		<WorkspaceMixinInner {...props} />
	</Suspense>
));

const WorkspaceMixinInner: FC<WorkspaceMixinProps> = ({ depotKey, name, weight }) => {
	// ...
};
```

- Boolean props read as flags: `with*` (`withBackground`, `withDebug`), `is*`
  (`isActive`, `isFocused`), or `show*` (`showResults`, `showSidebar`).
- JSX attributes are placed one-per-line (oxfmt `singleAttributePerLine: true`);
  do not fight the formatter by collapsing them.

## Event handlers

Interactive handlers are named `handle<Event>` and wrapped in `useStable` so they
keep a stable identity across renders:

```tsx
const handleSidebarToggle = useStable(() => {
	setShowSidebar(!showSidebar);
});
```

`useStable` (from `src/hooks`) is preferred over `useCallback` for
callbacks whose dependencies would otherwise churn; it is registered with Biome
as a stable-result hook, so it does not need a dependency array.

## Hooks

- Custom hooks are `use<Thing>` and live in `src/hooks/` (core-wide) or beside the
  feature they serve.
- **Context hooks** read a context with `use(SomeContext)` and throw a typed
  error when used outside their provider, rather than returning `null`:

```tsx
export function useTabs(): TabsContext {
	const ctx = use(TabsContext);

	if (!ctx) {
		throw new SurrealistError("Component must be wrapped in a <Workspace> component");
	}

	return ctx;
}
```

- A context, its `use*` accessor, and its `Provider` component typically share a
  file. The context value interface and the context object often share the same
  name (`interface TabsContext` and `const TabsContext = createContext(...)`).
- Utility hooks are exceptions to the arrow-component rule and are written as
  `export function` (e.g. `useStable`, `useRegistry`, `useTabs`). Components use
  `const` + `FC`; standalone functions and hooks use `function`.

## Functions and utilities

- Standalone, non-component functions use `export function foo(...)` with explicit
  return types where they are part of a public surface.
- Pure helpers live in utility modules and are small and
  single-purpose (`generateId`, `delegate`, `dedent`, `extractError`, `bail`,
  `sanitizeId`).
- Prefer early returns and guard clauses over deep nesting.

## Types and interfaces

- Use `interface` for object shapes, component props, and context values; use
  `type` for unions, aliases, mapped/conditional types, and function-type
  aliases.
- Union-of-literals is the norm for enumerations
  (`type Platform = "darwin" | "windows" | "linux"`,
  `type UpdateType = "metadata" | "state"`).
- Discriminated unions use a `kind` field, with `is*` type guards:

```ts
export type ProviderEntry = FolderEntry | ModelEntry;

export const isFolder = (item: ProviderEntry): item is FolderEntry => item.kind === "folder";
export const isModel = (item: ProviderEntry): item is ModelEntry => item.kind === "model";
```

- Generic type parameters are single uppercase letters (`T`, `K`, `A`, `S`, `U`),
  matching the panel/registry/depot generics.
- Deliberate exceptions to a lint rule use an inline
  `// oxlint-disable-next-line <rule>: <reason>` comment, always with a trailing
  reason. The Biome-owned `useExhaustiveDependencies` rule is the exception — it
  still reads `// biome-ignore lint/correctness/useExhaustiveDependencies` comments.

## Naming

- **Variables, functions, props, methods:** `camelCase`.
- **Components, classes, interfaces, types:** `PascalCase`.
- **Module-level constants:** `SCREAMING_SNAKE_CASE` — e.g. `LOG_TAG`,
  `CONFIG_CACHE`, `DEFAULT_PANELS`, `WORKSPACE_PROVIDER`, `THEME_DATA_RETRIEVAL`,
  `OPACITY_STRONG`, `ENTITY_TYPES`, `FullSync`. (A small number of local
  singletons like `FullSync` use `PascalCase`; module-wide exported constants use
  `SCREAMING_SNAKE_CASE`.)
- **Refs:** suffix with `Ref` (`onChangeInner`, `modelEntryCacheRef`,
  `collectRef`).
- **Design tokens:** colours are `THEME_*` (`THEME_DATA_RETRIEVAL`,
  `THEME_SCHEMA`, `THEME_ADMIN`, ...); opacities are `OPACITY_*`
  (`OPACITY_SUBTLE`, `OPACITY_MEDIUM`, `OPACITY_STRONG`).
- **Icons and pictos:** `icon<Name>` (`iconQuery`, `iconCog`, `iconNamespace`)
  and `picto<Name>` (`pictoSurrealist`), imported from the core UI layer.
- **Event names** use a namespaced, double-colon convention on the IPC/electron
  boundary (`window::maximize`, `window::toggle-fullscreen`).
- **SCSS class names** are `kebab-case` in the stylesheet and accessed as
  `camelCase` via the `classes` object (`.save-box` → `classes.saveBox`).

## Comments

Two distinct comment styles are used, and both matter:

1. **Multi-line JSDoc block comments (`/** ... */`)** document exported functions, classes,
   interfaces, class members, and interface members. They describe intent, and
   use `@param`/`@returns`/`@example` where helpful. Nearly every public method
   on `Depot`, `Panel`, `ModelProvider`, and the hooks carries one:

```ts
/**
 * Get a value from the storage backend.
 *
 * @param key - The key of the value to get.
 * @returns A promise that resolves with the value.
 */
async get<K extends keyof T>(key: K): Promise<T[K] | null> { ... }
```

2. **Short single-line comments (`// ...`)** explain _why_ a block of logic exists
   or mark the steps of a longer procedure. They sit directly above the code they
   describe and read as terse imperative phrases:

```ts
// Focus or replace the model if it is already open
for (const key of currentKeys) {
  // Focus the existing model
  if (models.includes(uriString)) { ... }

  // Replace an existing model
  if (replaceUriString && models.includes(replaceUriString)) { ... }
}

// Create a new region
const newKey = generateId();
```

**Do not** restate the code; explain the reason behind logic or explain the use case to a public facing audience.
Longer procedural functions (like tab open/close logic) are essentially outlined by these step-comments.

**Do not** make any references to the planning phases or phrase comments from a first person perspective.

## Whitespace and vertical rhythm

Blank lines are used deliberately to separate **logical units**, not
arbitrarily. The consistent pattern is:

- One blank line between the imports block and the first declaration.
- One blank line separating each logical step inside a function — typically a
  step introduced by a `// ...` comment is preceded by a blank line:

```ts
const newSchema = { ...schema };

// Synchronize root schema
if (options.root) {
  ...
}

// Synchronize namespace schema
if (surreal.namespace && options.namespace) {
  ...
}
```

- One blank line between members of a class/interface, and between grouped
  declarations.
- Related declarations are grouped together with no blank lines between them,
  then separated from the next group by a single blank line. For example, a block
  of `useLocalDepotState` calls is grouped, then a blank line precedes the refs:

```tsx
const [regionKeys, setRegionKeys] = useLocalDepotState(depot, "regionKeys", [], true);
const [regionModels, setRegionModels] = useLocalDepotState(depot, "regionModels", {}, true);
const [regionActives, setRegionActives] = useLocalDepotState(depot, "regionActives", {});

const modelEntryCacheRef = useRef(new Map<string, ModelEntry>());
const providers = modelProviders.mapping();
```

Never use more than one consecutive blank line.

## Imports and exports

- oxfmt organises imports automatically (`sortImports`); do not hand-sort them.
  Imports are grouped — external packages, then internal, then local/relative —
  with a blank line between groups, and sorted within each group.
- Use **named exports**. Barrel `index.tsx` files re-export a package's public
  surface with `export *` and named re-exports, grouped by area with a short
  comment header per group (see `core/src/index.tsx`, whose sections are labelled
  `// API exports`, `// Foundation exports`, `// Hooks exports`, etc.).
- Import type-only symbols with `import type { ... }` (`verbatimModuleSyntax` is
  on, so this is required for types).
