leaflet_svelte/docs/CONVENTIONS.md
2026-04-22 01:27:38 +09:00

132 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Conventions
## TypeScript
- Every file is `.ts` or `<script lang="ts">`. No plain JS outside build config.
- Types live next to the code they describe. Cross-feature types go in
`src/lib/domain/`.
- Prefer `interface` for object shapes, `type` for unions/aliases.
- Avoid `any`. `unknown` + a narrowing cast is preferred.
## File & folder naming
- **Components**: `PascalCase.svelte`. Exception: pseudo-routes `+page.svelte`
etc. per SvelteKit.
- **Plain modules**: `kebab-or-camel.ts`.
- **Feature directories**: lowercase (`workspaces`, `prediction`).
- Each feature folder has an `index.ts` that re-exports its public surface;
outside code imports from the index, not from individual files.
## Component naming inside `<script>`
- `$state`/`$derived` variables — camelCase, no prefix.
```ts
let isCollapsed = $state(false);
let currentPoint = $derived($pointsStore.find(p => p.id === id));
```
- Component instance refs — camelCase + `Ref`.
```ts
let pointEditorRef: PointEditor | null = $state(null);
```
- Event handlers — `handleXxx`.
```ts
function handleSave() { … }
```
- Props that are event callbacks — `onXxx`.
```svelte
let { onSelect = () => {} }: Props = $props();
```
- HTML `id` attributes — kebab-case, prefixed with a 23 letter component code
(`cp-start-time` for ControlPanel). This keeps IDs globally unique.
- Stores — PascalCase + `Store` (for top-level domain stores) OR camelCase
(for feature-scoped stores). Both are fine; be consistent within a module.
## Props contract
Use the Svelte 5 `Props` interface pattern:
```ts
interface Props {
title?: string;
collapsed?: boolean;
onToggle?: () => void;
children?: Snippet;
}
let {
title = '',
collapsed = $bindable(false),
onToggle = () => {},
children,
}: Props = $props();
```
Do not use `export let` in new components.
## State
- Persisted state (survives reload, syncs across tabs): `persisted(key, initial)`
from `$state`.
- Ephemeral UI state (modal open, which tab is active): plain `$state`.
- Cross-component state that isn't user-facing persistence: create a small
factory module under the feature folder (`store.ts`) — never a bare
`writable()` in a `.svelte` file.
## Imports
Use path aliases (`$api`, `$auth`, `$domain`, `$map`, `$state`, `$ui`, `$i18n`,
`$features`). Example:
```ts
import { api } from '$api';
import { authStore, requireAuthenticated } from '$auth';
import type { Prediction } from '$domain';
import { Map, plotPrediction } from '$map';
import { CollapsibleCard, addToast } from '$ui';
import { t } from '$i18n';
import { workspacesStore } from '$features/workspaces';
```
Avoid importing from `src/lib/components/...` (it no longer exists).
## Strings
No hardcoded Russian/English text in new code. Add the key to both
`src/lib/i18n/locales/ru.json` and `en.json` and read it with `$t('...')`.
Placeholders use `{name}`:
```ts
$t('workspaces.deleteConfirm', { name: w.name })
```
## Comments
Default is no comment. Add one only when the *why* is non-obvious (hidden
invariants, workarounds, API quirks). Dont restate what the code does.
Module-level docstrings are welcome: put a short paragraph at the top of a
file that describes the role of the module, mentioning any non-obvious design
choices. See `src/lib/state/persisted.ts` for an example.
## CSS
- Global chrome lives in `src/app.css`.
- Component-scoped styles go inside the `.svelte` file.
- Use Bootstrap utility classes when possible; only add custom CSS when the
design requires it.
- Never use `!important` except to override third-party libs (MapLibre,
Bootstrap).
## Testing
Not set up yet. When tests arrive: feature modules should be testable without
Svelte — that's why `domain/` and `state/` are pure.
## Don't
- Dont reach into `maplibre-gl` directly from a feature component — extend
`IMap` or add a helper in `$map` instead.
- Dont mutate localStorage from a component. Use a persisted store.
- Dont perform fetches from a component. Call a `$api` module.
- Dont redirect the user on auth failures from individual pages. Use
`requireAuthenticated()` at the top of `onMount`.