mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Format entire codebase with Prettier (mechanical, no behavior change)
This commit is contained in:
@@ -15,7 +15,7 @@ not components" architecture, and it carries no build-time magic. The principles
|
||||
are the durable part — they would survive a change of library.
|
||||
|
||||
**Lineage (why React + Zustand).** The UI began on Preact + `@preact/signals` and migrated to
|
||||
**React + Zustand** at M0, before feature work. The driver was *React-ecosystem friction* —
|
||||
**React + Zustand** at M0, before feature work. The driver was _React-ecosystem friction_ —
|
||||
real-React-only libraries not cooperating with `preact/compat` — **not** the signals model.
|
||||
Switching framework while nothing was implemented yet was also the one cheap moment to pick
|
||||
the lowest-migration-risk state library, so signals gave way to Zustand. A bonus: borrowing
|
||||
@@ -58,7 +58,7 @@ Three ways to touch a store:
|
||||
- **`set(partial)`** — update state (shallow-merges). Inside actions, the only place
|
||||
that mutates state.
|
||||
- **`get()`** — read current state inside actions without subscribing.
|
||||
- **the hook `useAppStore(selector)`** — read state *in a React component*, subscribing
|
||||
- **the hook `useAppStore(selector)`** — read state _in a React component_, subscribing
|
||||
to exactly what the selector returns.
|
||||
|
||||
### Reading in components — always select narrowly
|
||||
@@ -93,9 +93,11 @@ Services, orchestration, infrastructure, and tests use the store object directly
|
||||
no React involved. This is the property that lets our logic live outside components:
|
||||
|
||||
```ts
|
||||
openModal('settings'); // via the modal coordinator (doc 03)
|
||||
const theme = useAppStore.getState().uiTheme; // snapshot read
|
||||
const unsub = useAppStore.subscribe((s, prev) => { /* react to changes */ });
|
||||
openModal('settings'); // via the modal coordinator (doc 03)
|
||||
const theme = useAppStore.getState().uiTheme; // snapshot read
|
||||
const unsub = useAppStore.subscribe((s, prev) => {
|
||||
/* react to changes */
|
||||
});
|
||||
```
|
||||
|
||||
> Rule: in components, **select narrowly** (and `useShallow` for object/array
|
||||
@@ -106,7 +108,7 @@ const unsub = useAppStore.subscribe((s, prev) => { /* react to changes */ });
|
||||
|
||||
## 2. One Source of Truth per Fact — Derive, Don't Duplicate
|
||||
|
||||
Every fact lives in exactly one state field. Anything that can be *calculated*
|
||||
Every fact lives in exactly one state field. Anything that can be _calculated_
|
||||
from other state is computed **in a selector at read time**, never stored as a
|
||||
second field you keep in sync by hand.
|
||||
|
||||
@@ -121,8 +123,8 @@ read, drift is structurally impossible.
|
||||
// activeSnippetId: string | null
|
||||
|
||||
// Derive in the component's selector — not a stored field:
|
||||
const activeSnippet = useSnippetStore((s) =>
|
||||
s.snippets.find((x) => x.id === s.activeSnippetId) ?? null,
|
||||
const activeSnippet = useSnippetStore(
|
||||
(s) => s.snippets.find((x) => x.id === s.activeSnippetId) ?? null,
|
||||
);
|
||||
const snippetCount = useSnippetStore((s) => s.snippets.length);
|
||||
```
|
||||
@@ -141,13 +143,13 @@ const active = useSnippetStore(selectActiveSnippet);
|
||||
```
|
||||
|
||||
> Rule: if you can compute it, do not store it. Add a new state field only for a
|
||||
> value that is *input* the app receives, not output it derives.
|
||||
> value that is _input_ the app receives, not output it derives.
|
||||
|
||||
---
|
||||
|
||||
## 3. Where State Lives: Central vs. Per-Feature Stores
|
||||
|
||||
Each store is its own `create()` module. We split by *concern*, not by component
|
||||
Each store is its own `create()` module. We split by _concern_, not by component
|
||||
tree.
|
||||
|
||||
### Per-feature stores
|
||||
@@ -162,16 +164,16 @@ Each cohesive feature owns a store holding its durable domain state.
|
||||
|
||||
### The central `useAppStore`
|
||||
|
||||
`useAppStore` holds only *cross-cutting, ephemeral UI state* that no single
|
||||
`useAppStore` holds only _cross-cutting, ephemeral UI state_ that no single
|
||||
feature owns — which modal is open, the runtime theme, transient render flags.
|
||||
|
||||
### How to decide
|
||||
|
||||
| Put it in a **feature store** when… | Put it in **`useAppStore`** when… |
|
||||
| --------------------------------------------- | --------------------------------------------- |
|
||||
| It's domain data (snippets, datasets, specs) | It's transient UI chrome (open modal, theme) |
|
||||
| It outlives a single interaction | It belongs to no single feature |
|
||||
| It gets persisted | Multiple unrelated features read/write it |
|
||||
| Put it in a **feature store** when… | Put it in **`useAppStore`** when… |
|
||||
| -------------------------------------------- | -------------------------------------------- |
|
||||
| It's domain data (snippets, datasets, specs) | It's transient UI chrome (open modal, theme) |
|
||||
| It outlives a single interaction | It belongs to no single feature |
|
||||
| It gets persisted | Multiple unrelated features read/write it |
|
||||
|
||||
> Rule: keep `useAppStore` small. When a chunk of it only ever serves one feature,
|
||||
> that's the signal to extract a feature store. A bloated central store is the
|
||||
@@ -253,7 +255,14 @@ export function SnippetList() {
|
||||
{snippets.map((s) => (
|
||||
<li key={s.id} aria-current={s.id === activeSnippetId} onClick={() => select(s.id)}>
|
||||
{s.name}
|
||||
<button onClick={(e) => { e.stopPropagation(); remove(s.id); }}>✕</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
remove(s.id);
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -310,7 +319,9 @@ Subscribers read state and write to `src/app/infrastructure/` adapters (IndexedD
|
||||
|
||||
```ts
|
||||
// src/main.tsx
|
||||
const applyTheme = (t: string) => { document.documentElement.dataset.theme = t; };
|
||||
const applyTheme = (t: string) => {
|
||||
document.documentElement.dataset.theme = t;
|
||||
};
|
||||
applyTheme(useAppStore.getState().uiTheme);
|
||||
useAppStore.subscribe((s, prev) => {
|
||||
if (s.uiTheme !== prev.uiTheme) applyTheme(s.uiTheme);
|
||||
|
||||
Reference in New Issue
Block a user