feat: polish
This commit is contained in:
parent
2e6177fe74
commit
4bd927bb4e
137 changed files with 6357 additions and 137560 deletions
132
docs/CONVENTIONS.md
Normal file
132
docs/CONVENTIONS.md
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# 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 2–3 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). Don’t 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
|
||||
|
||||
- Don’t reach into `maplibre-gl` directly from a feature component — extend
|
||||
`IMap` or add a helper in `$map` instead.
|
||||
- Don’t mutate localStorage from a component. Use a persisted store.
|
||||
- Don’t perform fetches from a component. Call a `$api` module.
|
||||
- Don’t redirect the user on auth failures from individual pages. Use
|
||||
`requireAuthenticated()` at the top of `onMount`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue