Files
dashfront/docs/categoryViewAutomat.md
2026-10-04 23:38:46 +02:00

6.8 KiB

categoryViewAutomat

Source File: src/automats/categoryViewAutomat.js
Registration Key: 'category-views' (Automat.get('category-views') / window.__AUTOMATS__.get('category-views'))
Related: categoryAutomat — 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 so the tree and the views can evolve independently. The dependency stays one-directional:

graph LR
  CA[categoryAutomat<br/>tree + mutations] -->|attachCategoryTree| CV[categoryViewAutomat<br/>KeyedAutomat views]
  CV -->|loadAllCategoryViews| API[/api/category/:id/articles]
  CV -->|collectLoadedCategoryViewData| AI[articleIndexAutomat<br/>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

interface CategoryViewData {
  /** Cleaned slug path this view is keyed by. */
  slug: string;
  /** Resolved category id. */
  categoryId: number | string;
  /** Articles in that category. */
  articles: Array<ArticleRow>;
  /** Direct child category nodes, sorted by `sort`. */
  childCategories: Array<CategoryNode>;
}

Each keyed entry wraps this as ItemState<CategoryViewData>: { 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<void>

Re-fetches only the already cached views (setDirty(undefined, false, true)), keeping the cached set small for SSE-driven refreshes.

invalidateAllCategoryViews(): Promise<unknown[]>

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<CategoryState>

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

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.