> For the complete documentation index, see [llms.txt](https://engineering.umh.app/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://engineering.umh.app/engineering/development-process/how-to-build/coding-standards/languages/svelte.md).

# Svelte

How the Svelte code in the Management Console renders the design system and how it is tested.

Owner: Robert

The UI in the [Management Console](https://docs.umh.app/management-console) comes from the design system. Its logic lives in plain TypeScript.

## Rules

* The design system is the single source of truth for the UI in the Management Console. Its rules and its owners are in Storybook (UMH only).
* The Svelte code only renders the design system.
* Logic lives in TypeScript, in a `utils/` folder next to the component. It is never inline in a `.svelte` file. Once a second place uses it, it moves to the shared folder for its domain.
* The TypeScript in the `utils/` folders gets unit tests.
* The Svelte code gets no unit tests. Each component exposes Storybook stories instead. Chromatic compares a screenshot of every story and runs an accessibility check on each pull request.
* Test-driven development works through the stories. Write the story before the component.
* Coding agents use the [Svelte AI skills](https://svelte.dev/docs/ai/skills). They hold Svelte's own best practices and a checker for Svelte code.
* A `.svelte` file contains no `try`, `catch` or `finally`.
* The rules below add to these.

## States in Svelte

### Derived state

Keep each value in one `$state` and derive the rest with `$derived`. An `$effect` that copies one state into another is out of sync whenever either side changes somewhere else.

Discouraged:

```svelte
let badgeOpen = $state(false);
let disclosure = $state(initialDisclosure);
$effect(() => { disclosure = { ...disclosure, open: badgeOpen }; });
```

Preferred:

```svelte
let disclosure = $state(initialDisclosure);
const badgeOpen = $derived(disclosure.open);
```

### One object per lifecycle

Model a lifecycle as one nullable object. `null` means closed, and the object holds what is true while it is open. Work that runs once per open runs where the object is created, so no flag has to be reset.

Discouraged:

```svelte
<script lang="ts">
  let open = $state(false);
  let frozenThisOpen = $state(false);
  let notifiedThisOpen = $state(false);

  function handleHover() {
    open = true;
    if (!notifiedThisOpen) {
      analytics.track('badge_opened');
      notifiedThisOpen = true;
    }
  }

  function handleClick() {
    if (!open) {
      return;
    }
    frozenThisOpen = true;
  }

  function handleLeave() {
    if (frozenThisOpen) {
      return;
    }
    open = false;
    notifiedThisOpen = false;   // a forgotten reset here skips the next notification
  }
</script>
```

Preferred:

```svelte
<script lang="ts">
  interface OpenBadge { frozen: boolean }

  let openBadge = $state<OpenBadge | null>(null);

  function handleHover() {
    if (openBadge !== null) {
      return;
    }
    openBadge = { frozen: false };
    analytics.track('badge_opened');   // once per open, by construction
  }

  function handleClick() {
    if (openBadge === null) {
      return;
    }
    openBadge.frozen = true;
  }

  function handleLeave() {
    if (openBadge === null || openBadge.frozen) {
      return;
    }
    openBadge = null;
  }
</script>
```

### Helper callbacks

Pass callbacks into a helper. A helper that returns command names for the caller to interpret puts the same decision in two places.

Discouraged:

```ts
function hoverActions(badge: OpenBadge | null): Array<'freeze' | 'expand'> {
  if (badge === null || badge.frozen) {
    return [];
  }
  return ['freeze', 'expand'];
}

const actions = hoverActions(openBadge);
if (actions.includes('freeze')) {
  freeze();
}
if (actions.includes('expand')) {
  expand();
}
```

Preferred:

```ts
function handleHover(badge: OpenBadge | null, freeze: () => void, expand: () => void) {
  if (badge === null || badge.frozen) {
    return;
  }
  freeze();
  expand();
}

handleHover(openBadge, freeze, expand);
```

### Mount-constant values

A value that doesn't change while the component is mounted is a plain `const`, or a prop when the caller already has it. `$derived` is for values that change.

Discouraged:

```svelte
<script lang="ts">
  let { instanceId }: { instanceId: string } = $props();
  const detailsUrl = $derived(`/instances/${instanceId}`);   // instanceId is fixed for this mount
</script>
```

Preferred:

```svelte
<script lang="ts">
  let { instanceId }: { instanceId: string } = $props();
  const detailsUrl = `/instances/${instanceId}`;
</script>
```

### Component structure

A `.svelte` file contains more markup than TypeScript. Logic goes into a colocated `utils` folder that returns a result the component renders. Inside `{#each}`, render per-item variations through named [snippets](https://svelte.dev/docs/svelte/snippet). When a part needs its own TypeScript or its own render logic, make it a subcomponent in a colocated `components` folder. Enumerate the component's states in one object and render from the passed state.

```
instance-health/
  InstanceHealthCard.svelte     # markup; renders the result from utils
  components/
    HealthBadge.svelte          # subcomponent with its own render logic
    HealthTooltip.svelte
  utils/
    load-health.ts              # returns a result the card renders
    badge-state.ts
  types.ts
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://engineering.umh.app/engineering/development-process/how-to-build/coding-standards/languages/svelte.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
