# `categoryViewAutomat` > **Source File**: [`src/automats/categoryViewAutomat.js`](file:///home/seb/src/dashfront/src/automats/categoryViewAutomat.js) > **Registration Key**: `'category-views'` (`Automat.get('category-views')` / `window.__AUTOMATS__.get('category-views')`) > **Related**: [`categoryAutomat`](./categoryAutomat.md) — owns the category **tree** this module resolves against. --- ## 1. Overview & Purpose `categoryViewAutomat` owns the **per-category article views**: a `KeyedAutomat` (`categoryViews`) indexed by route **slug path** (`''` for root, `'getraenke'`, `'getraenke/softdrinks'`). Each entry holds one category's articles plus its child categories. This module was extracted from [`categoryAutomat.js`](../src/automats/categoryAutomat.js) so the tree and the views can evolve independently. The dependency stays **one-directional**: ```mermaid graph LR CA[categoryAutomat
tree + mutations] -->|attachCategoryTree| CV[categoryViewAutomat
KeyedAutomat views] CV -->|loadAllCategoryViews| API[/api/category/:id/articles] CV -->|collectLoadedCategoryViewData| AI[articleIndexAutomat
derived search index] ``` `categoryAutomat` injects itself via `attachCategoryTree(automat)`; `categoryViewAutomat` never imports the tree, which avoids a circular import. ### Core Responsibilities - **Slug → Category Resolution**: Resolves a slug path against the loaded tree to a `categoryId`. - **Per-Category Article Payloads**: `GET /api/category/:id/articles` for the resolved category. - **Bulk Warm-Up**: `loadAllCategoryViews()` loads **every** category in the tree (concurrency-bounded), triggered by `categoryAutomat.loadTree()` the moment the tree succeeds. - **Child Category Derivation**: Recomputes `childCategories` for all loaded views whenever the tree changes. - **Incremental Patches**: Appends created articles to, and removes deleted articles from, the affected views. --- ## 2. View Data Structure ```ts interface CategoryViewData { /** Cleaned slug path this view is keyed by. */ slug: string; /** Resolved category id. */ categoryId: number | string; /** Articles in that category. */ articles: Array; /** Direct child category nodes, sorted by `sort`. */ childCategories: Array; } ``` Each keyed entry wraps this as `ItemState`: `{ data, loading, loaded, error }`. --- ## 3. Loading Strategy ### `loadAllCategoryViews({ concurrency }): Promise<{ loaded, failed }>` Builds the slug path of **every** category node from its breadcrumb trail (`categoryViewSlugPath()`), de-duplicates them, then loads each with `categoryViews.load(slug)`. - **Already cached views resolve immediately** (`load()` short-circuits when `loaded && !error`), so the call is idempotent. - Concurrency defaults to **6**; failures are captured per category and returned in `failed` rather than rejecting the batch. - **Triggered by**: `categoryAutomat.actions.loadTree()` right after `treeStatus === 'succeeded'`, and available on demand via `categoryAutomat.actions.loadAllCategoryViews()`. ### `reloadLoadedCategoryViews(): Promise` Re-fetches **only the already cached** views (`setDirty(undefined, false, true)`), keeping the cached set small for SSE-driven refreshes. ### `invalidateAllCategoryViews(): Promise` Background-invalidates all loaded views. Called by `sseAutomat` on `'category'` and `'articles'` events. ### `disposeAllCategoryViewAutomats(): void` Clears every view and frees memory on company switch or logout. ### `waitForCategoryTree(): Promise` Resolves once the attached tree reports `treeStatus === 'succeeded'`. Rejects on tree failure or when `authAutomat` turns `'anon'`, so a pending view never hangs forever. --- ## 4. Exported API | Export | Description | |---|---| | `categoryViews` (default) | The `KeyedAutomat` keyed by slug path. | | `attachCategoryTree(automat)` | Injects the tree automat (called once by `categoryAutomat`). | | `categoryViewSlugPath(categories, rootCategoryId, categoryId)` | Breadcrumb-derived slug path usable as a key. | | `collectLoadedCategoryViewData()` | All loaded view payloads — the source for the derived article index. | | `loadAllCategoryViews({ concurrency })` | Bulk warm-up of every category in the tree. | | `reloadLoadedCategoryViews()` | Re-fetch only the cached views. | | `invalidateAllCategoryViews()` | Background-invalidate all loaded views. | | `disposeAllCategoryViewAutomats()` | Clear all views. | | `getOrCreateCategoryAutomat({ slugPath })` | Returns the `KeyHandle` for a slug path. | | `refreshCategoryViewChildren(categories)` | Re-derive `childCategories` for every loaded view. | | `appendArticlesToCategoryViews({ categoryId, articles })` | Append newly created articles. | | `removeArticleBySku(sku)` | Strip a deleted article from every view. | --- ## 5. Key Handle Usage ```js import categoryAutomat from '../automats/categoryAutomat'; const handle = categoryAutomat.key('getraenke/softdrinks'); handle.subscribe(this, (item) => ({ articles: item.data?.articles ?? [], childCategories: item.data?.childCategories ?? [], })); const data = await handle.load(); // deduplicated across concurrent mounts const data2 = handle.read(); // Suspense: throws the in-flight promise when unready handle.reload(); handle.setDirty(); ``` All four facades (`key`, `read`, `load`, `views`) are attached to `categoryAutomat` as thin delegates to `categoryViews`, so existing import sites keep working. --- ## 6. Cross-Automat Coordination | Automat | Interaction | |---|---| | `categoryAutomat` | `attachCategoryTree()`, `loadAllCategoryViews()` after the tree succeeds, `refreshCategoryViewChildren()` on every tree mutation, `disposeAllCategoryViewAutomats()` on reset. | | `authAutomat` | `waitForCategoryTree()` rejects pending views on `'anon'`. | | `sseAutomat` | `invalidateAllCategoryViews()` on `'category'` / `'articles'` events. | | `articleIndexAutomat` | Subscribes to `categoryViews` and re-derives the search index from `collectLoadedCategoryViewData()`. | --- ## 7. Gotchas - **Bulk warm-up cost**: `loadTree()` now issues one request per category. On very large trees this is many requests — `concurrency` (default 6) is the throttle to tune. Individual failures are non-fatal. - **`viewsLoading`**: While it is `true`, the tree is ready but the views are still warming. Use it to decide between "render a spinner" and "render an empty grid". - **Keys are slug paths, not ids**: A rename or move changes the slug, so the view key changes and the old entry should be evicted; `refreshCategoryViewChildren()` handles the child lists, but stale keys may linger until the next `disposeAllCategoryViewAutomats()`. - **Do not import `categoryAutomat` from this file** — that would create the circular dependency the `attachCategoryTree()` injection exists to avoid.