Projekt srovnan se zasadami v D:\GitHubRepository\CLAUDE.md bez zmeny chovani.
Struktura: scripts/ (skripty konektoru) -> connectors/, src/scripts ->
src/runtime/scripts; src/index.ts jen startuje, novy src/app.ts s createApp();
routes/dashboard.ts a routes/settings.ts rozdeleny do slozek; openapi.ts
rozdelen na openapi/{index,helpers,components} a paths/* (98 cest overeno
shodnych); ticketStore, automationStore a services jsou fasady nad slozkami
data/tickets, data/automations a data/services/catalog. process.env se cte
jen v config.ts. Web: hooky v hooks/, sdilena ui/Table a ui/ServiceIcon,
surove inputy nahrazeny komponentami, sedm velkych souboru rozdeleno.
Nastroje: eslint (typescript-eslint, react-hooks v7), prettier, editorconfig,
nvmrc, .env.example, vitest; skripty lint, format, test. Lint je cisty bez
jedineho eslint-disable (nove hooky useLatest a useSyncFromSource, odvozeny
stav misto setState v effectu). noUncheckedIndexedAccess v obou tsconfig,
84 mist zuzeno bez non-null operatoru; odhalilo zalohu backoffu fronty pri
nule pokusu a Retry-After NaN pri max 0. Cely kod naformatovan prettierem.
Testy: 8 souboru, 105 testu (prava, viditelnost, podminky a opakovani
v executoru, redaktor tajemstvi, sitove guardy, migrace resitelu, tickety,
health a prihlaseni pres supertest). Testy odhalily dve chyby ve vyhodnoceni
podminek, obe opravene: chybejici castka se porovnavala jako nula a podminka
nad vystupem druheho kroku cetla hodnotu prvniho se stejnym nazvem.
Pojmenovane konstanty misto magickych hodnot, ctx.util.base64 pro skripty
konektoru, README a dokumentace aktualizovany vcetne znamych odchylek.
32 KiB
32 KiB
Rejstřík znovupoužitelných funkcí a komponent
K čemu je co, aby se za rok nepsalo znovu něco, co už existuje. Když píšeš druhou funkci, která dělá skoro totéž jako něco odsud, je to skoro vždycky chyba - buď se má použít ta původní, nebo se má rozšířit.
Podrobný popis je vždycky v komentáři u samotné funkce. Tady je jen jedna věta a kdy to použít.
Ukládání dat (server)
Rozhoduje se na jednom místě, viz 14-databaze.md. Volající nikdy nezjišťuje, jestli běží Postgres, soubor, nebo pamět.
| Co | Kde | K čemu |
|---|---|---|
defineStore<T>(kind) |
src/data/store/index.ts |
Založí úložiště pro nový druh záznamu. Jeden řádek na entitu. |
initStores({databaseReady}) |
src/data/store/index.ts |
Vybere režim pro všechna úložiště včetně konektorů. Volá se jednou při startu, nikde jinde. |
refreshCache(kind), refreshAllCaches() |
src/data/store/cached.ts |
Registr cache. Po zápisu obnovit jen tu entitu, ne všechny. |
listByTenant(tenantIds, sortBy) |
src/data/store/cached.ts |
Filtr na firmu plus řazení nad cache. Místo sedmi kopií filter + sort v modulech entit. |
nowIso, minutesAgo, highestNumber, writableOrWarn |
src/data/store/types.ts |
Drobnosti pro moduly úložišť: časová značka, čas před N minutami, nejvyšší číslo ID pro čítač, varování při zápisu do úložiště jen pro čtení. |
mergeValues(current, patch) |
src/data/connectors/types.ts |
Sloučení hodnot konektoru při PATCH: prázdný řetězec maže, chybějící klíč nechává. Jedna implementace pro soubor i Postgres. |
flushStores() |
src/data/store/index.ts |
Dopíše rozepsané zápisy. Jen při ukončení procesu. |
withCache(store) |
src/data/store/cached.ts |
Kopie v paměti pro konfigurační entity, které se čtou při každém requestu (uživatelé, role, firmy). Čte se synchronně, obnovuje se po zápisu. |
withMirror(store) |
src/data/store/mirror.ts |
Opačný směr než withCache: data se mění v paměti a po každé změně se celý záznam zapíše. Pro provozní data (tickety, automatizace, incidenty, rozložení). |
isVisible(entity, options) |
src/data/store/types.ts |
Vidí volající tenhle záznam? Prázdný seznam firem znamená "nic", ne "vše". |
nowIso() |
src/data/store/types.ts |
Časová značka. Ať se nepíše new Date().toISOString() na třiceti místech. |
memorySnapshot / fileSnapshot |
src/data/snapshot.ts |
Nižší vrstva pod createLocalStore: atomický zápis JSONu s debounce. Přímo se nepoužívá. |
db(), query, queryOne, transaction |
src/db/pool.ts |
Postgres. dbFor(tenantId) je připravený šev pro rozdělení na víc databází. |
seal, open, sealAll, openAll |
src/db/secretBox.ts |
Šifrování přístupových údajů konektorů (AES-256-GCM). Nic tajného se neukládá jinak. |
runMigrations() |
src/db/migrate.ts |
Migrace pod zámkem, jeden soubor = jedna transakce. |
Entity a práva (server)
| Co | Kde | K čemu |
|---|---|---|
crudRouter(options) |
src/routes/crud.ts |
Celý CRUD nad jednou entitou: seznam, detail, vytvoření, úprava, mazání, právo, audit. Nová entita v nastavení = jeden crudRouter, ne pět handlerů. |
readScope(req) |
src/routes/crud.ts |
Ze které firmy smí request číst. Povinný argument všech list volání. |
accessFor(user, tenantId?) |
src/data/access.ts |
Co uživatel smí: práva, záložky, výchozí firma. Klient si nic nedovozuje sám. |
permissionsOf(user, tenantId) |
src/data/permissions.ts |
Efektivní práva z rolí. Pětisekundová cache, invalidatePermissions() po zápisu. |
hasPermission(...) |
src/data/permissions.ts |
Jedna kontrola. Používá ji crudRouter i ruční handlery. |
navFor(...) |
src/data/tenantFeatures.ts |
Průnik toho, co firma má, a toho, na co má člověk právo. Navigace chodí ze serveru. |
recordAudit(input) |
src/data/audit.ts |
Zápis do auditu. Nevrací chybu a nečeká se - rozbitý audit nesmí rozbít aplikaci. |
enqueue(input) |
src/runtime/queue.ts |
Zařadí běh. Klíč proti dvojímu zařazení drží jeden běh na jednu událost. |
claimBatch(limit, active) |
src/runtime/queue.ts |
Vezme další práci, spravedlivě po firmách, přeskočí to, co už běží. Místo, kde nad Postgresem musí být SKIP LOCKED. |
touchClaim(item) |
src/runtime/queue.ts |
Tlukot běžícího běhu. Bez něj se dlouhý běh po 30 minutách považuje za zaseknutý a vykoná se podruhé. |
findOpenIncident(source, tenantIds) |
src/data/incidentStore.ts |
Otevřený incident téže příčiny v téže firmě. Stejná chyba nezakládá druhý. |
attachAccess, tenantOrDeny, optionalTenantOrDeny, scopeOrDeny |
src/middleware/tenant.ts |
Přístup jednou na request do req.access, firma requestu z něj. Route si to nepočítá sama. |
safeRouter(), wrap(handler) |
src/middleware/asyncHandler.ts |
Router, ve kterém odmítnutá promise skončí jako 500 s logem. Každá nová route vzniká tady. |
rateLimit({name, windowMs, max}) |
src/middleware/rateLimit.ts |
Limit requestů v paměti, 429 s Retry-After. Jen na veřejných endpointech, kde se dá hádat. |
validationError(res, zodError, message?) |
src/middleware/validation.ts |
Jeden tvar chyby validace: z chyby zodu udělá issues po polích. |
timingSafeEqualString(a, b) |
src/lib/secure.ts |
Porovnání tokenu v konstantním čase. Webhook, příjem, pozvánky. |
assertAllowedUrl, readBodyLimited, readJsonLimited, describeFetchError |
src/net/guard.ts |
Vše, co volá ven: zákaz vnitřní sítě, čtení těla proudem s limitem, čitelný popis chyby sítě. HTTP skriptů, MCP, SMTP i ARES. |
publishOutputs(...) |
src/runtime/executor.ts |
Zápis výstupů kroku do kontextu, jednou pro vestavěné kroky i skripty. Holé jméno nepřepíše, co už v kontextu je. |
lookupCompany, searchCompanies, listCompanyPersons, placeholderEmail, isPlaceholderEmail |
src/ares/client.ts |
Registr ARES: firma podle IČ nebo názvu, statutáři, zástupná adresa a její rozpoznání. |
onTicketEvent(kind, ticket) |
src/runtime/triggers.ts |
Změna ticketu zařadí navázané automatizace, včetně ochrany proti smyčce. |
withRun(marker, work) |
src/runtime/context.ts |
Označí, který běh práci způsobil. Bez toho automatizace spouští sama sebe. |
findBuiltinStep(...) |
src/runtime/builtinSteps.ts |
Kroky, které sahají do našeho úložiště, ne ven přes HTTP. |
personView(user, membership) |
src/data/people.ts |
Jediné místo, kde z účtu a jednoho členství vzniká pohled Person. Řešitel je členství, ID řešitele je ID účtu. |
listPeople(tenantIds), listAllPeople(tenantIds) |
src/data/people.ts |
Řešitelé vybraných firem, jeden záznam na členství; druhá i s vypnutými účty (správa týmu). |
findPerson(id, tenantId) |
src/data/people.ts |
Řešitel v dané firmě, firma je povinná. Kdo v ní není členem, je undefined, i když účet existuje. |
personName(id) |
src/data/people.ts |
Jméno účtu bez ohledu na firmu, pro popisky u záznamů, které už prošly filtrem na firmu. |
personIdFor(user, tenantId) |
src/data/people.ts |
ID řešitele, kterým je uživatel ve firmě: ID účtu při členství, jinak null. Neptat se user.id přímo. |
findPersonByExternalId(value, tenantIds) |
src/data/people.ts |
Řešitel podle ID z cizí aplikace, například voicebotId. Externí ID visí na členství. |
migratePeople() |
src/data/migratePeople.ts |
Jednorázový převod starých záznamů řešitelů (ppl_) na účty při startu. Přepisuje odkazy přes remapPersonIds v tickets/remap.ts a automations/remap.ts. |
notify(input) |
src/data/notifications.ts |
Upozorní člověka. Nečeká se a nevyhazuje chyby, stejně jako audit. |
runFlow(steps, context, options) |
src/runtime/executor.ts |
Vykoná strom kroků. Nikdy nevyhodí výjimku, chyba je výsledek. Používá to akce na ticketu i webhook, aby se strom choval všude stejně. |
widgetCatalog(tenantIds, userId) |
src/data/widgets.ts |
Jediná definice toho, co jde položit na dashboard. Používá ji nabídka i kontrola ukládaného rozložení. |
intakeEvent(input) |
src/data/tickets/intake.ts |
Přijme událost zvenku: podle externího ID buď založí ticket, nebo ji navěsí na existující. Jediná cesta, kterou se událost stává ticketem. Hodnoty z input.apply zapíše v obou případech, prázdné nemaže. |
getAgentStats(...) |
src/data/tickets/stats.ts |
Výkon řešitelů: odbavené, mediány časů, vrácené, fronta. Používá to widget i detail osoby, aby čísla seděla. |
findByExternalId(...) |
src/data/tickets/queries.ts |
Ticket firmy podle externího ID. Klíč je dvojice firma a ID. |
createApp() |
src/app.ts |
Sestavi Express aplikaci: middleware, routery, health, Swagger, statika, SPA. Bez listen a bez nacteni dat, takze jde postavit v testu (supertest). index.ts ji jen spusti. |
config.serviceBaseUrlOverride(variable) |
src/config.ts |
Jedine cteni process.env s dynamickym nazvem (<SLUZBA>_BASE_URL). Vraci normalizovanou adresu nebo null. Nikde jinde se process.env necte. |
buildOpenApiDocument() |
src/openapi/index.ts |
Sklada OpenAPI z components.ts a paths/*.ts. Novy endpoint se popisuje v souboru sveho routeru, crudPaths v helpers.ts popise CRUD petici jednim radkem. |
pageFrom(query), paginate(items, page) |
src/routes/dashboard/shared.ts |
Strankovani seznamu ticketu a fronty behu: limit, offset, strop MAX_PAGE_LIMIT. Nepsat vlastni slice v route. |
memberOf(user, tenantId) |
src/routes/settings/shared.ts |
Je ucet clenem firmy? Sdili sprava uctu a resitelu. |
Moduly dat po rozdeleni
Puvodni soubory zustavaji jako fasady (ticketStore.ts, automationStore.ts,
services.ts), importy se nemeni. Kdo hleda, kde co je:
| Modul | Co drzi |
|---|---|
tickets/index.ts |
verejne API slozky, seed a initTickets v poradi, ve kterem se maji volat |
tickets/model.ts |
StoredTicket, defaultStatuses, channelLabels, toTicket (doplneni vychozich hodnot) |
tickets/state.ts |
pole ticketu, indexy podle ID a externiho ID, log, udalosti, citace ID |
tickets/persist.ts |
persist, touch (updatedAt a zapis v jednom), initTickets |
tickets/queries.ts |
listTickets s TicketFilter, getTicket, findTicket, findByExternalId, ticketWithinVisibility |
tickets/store.ts |
zapisy: createTicket, updateTicketStatus, assignTicket, setTicketType, setTicketTags, assignTicketGroup, claimTicket, addComment |
tickets/intake.ts |
intakeEvent: udalost zvenku se stane ticketem nebo se navesi |
tickets/trace.ts |
appendTrace, flattenTrace, lastTraceId, describePayload: log prubehu |
tickets/stats.ts |
getWorkload, getAgentStats |
tickets/seed.ts, remap.ts |
ukazkova data (SEED_DEMO=1), preznaceni resitelu pri migraci |
automations/index.ts |
verejne API slozky, seed a initAutomations |
automations/model.ts |
StoredAutomation, rulesOf, matchOf (cteni podminky ve stare i nove podobe) |
automations/state.ts |
Map automatizaci, nextId, findWritable |
automations/persist.ts |
save, initAutomations, mirror |
automations/store.ts |
listAutomations, getAutomation, createAutomation, updateAutomation, regenerateWebhookToken, findByWebhookToken, recordRun, deleteAutomation |
automations/validation.ts |
countSteps, collectFlowIssues, deriveKind, withDerived: ciste funkce nad stromem |
automations/webhook.ts |
generateWebhookToken, withWebhookToken, recordWebhookCall, recentWebhookCalls, bodyForCall |
automations/runs.ts |
historie behu po dnech, KEEP_DAYS, statsOf |
automations/seed.ts, seedDemo.ts, remap.ts |
skutecne automatizace (vzdy), ukazkove (SEED_DEMO=1), preznaceni resitelu |
services/index.ts |
katalog za behu: findService, findOperation, actionsFor, serviceCatalog, setScriptActions, setMcpOperations, withRuntimeOptions, visibleServices |
services/catalog/index.ts |
services a serviceCategories slozene ze skupin; poradi tady je poradi v nabidce |
services/catalog/<skupina>.ts |
staticky zapis sluzeb jedne skupiny: triggers, incident, ticket, crm, finance, logistics, email, messaging, social, office, analytics, ai, mcp, tools, polstryn |
findByIntakeToken(token) |
src/data/tenants.ts |
refreshCaches(), refreshEntity(kind) |
src/data/bootstrap.ts |
bootstrapData({databaseReady}) |
src/data/bootstrap.ts |
Skripty a konektory (server)
| Co | Kde | K čemu |
|---|---|---|
runScript(id, inputs, ctx) |
src/runtime/scripts/runner.ts |
Spustí skript. Nikdy nevyhodí výjimku, chybu vrací jako výsledek s celým hlášením. |
validateValues(...) |
src/runtime/scripts/values.ts |
Jedna kontrola pro vstupy i výstupy skriptu podle manifestu. |
scriptUtil |
src/runtime/scripts/util.ts |
Nádobíčko pro skripty: pick, first, num, date, day, list, addresses, quote, need, get, applyRules, fillJson. Skript nemá sahat na nic jiného. |
pick, pickText, jwtExpiry, parseBool, parseNumber |
src/runtime/scripts/util.ts |
Totéž pro server: pole bez ohledu na velikost písmen, exp z JWT, převody. Než napíšeš Number(x) s kontrolou NaN, je to tady. |
DETAIL_BYTES, truncate(value) |
src/runtime/scripts/util.ts |
Jeden limit na zkracování detailu chyby pro všechny vrstvy. Žádné vlastní slice(0, 600). |
createRedactor(...) |
src/runtime/scripts/util.ts |
Vyškrtá tajemství z textu před logováním, i v URL-encoded a JSON-escaped tvaru. Používá se u všeho, co jde do logu. |
scriptConfig(target) |
src/runtime/scripts/connections.ts |
Nastavení napojení bez tajných polí. Jediné, co skript dostane jako ctx.config. |
applyRules, fillJson |
src/runtime/scripts/mapping.ts |
Transformace dat: pole na pole s převody, nebo objekt na objekt. Viz 13-transformace-dat.md. |
getPath(obj, path) |
src/runtime/scripts/mapping.ts |
Čtení zakaznik.adresa.mesto z neznámého objektu. |
resolveTarget(...) |
src/runtime/scripts/connections.ts |
Z konektoru poskládá adresu a hlavičky. Přístupové údaje nikam jinam nevedou. |
serviceBaseUrl(service) |
src/runtime/scripts/connections.ts |
Adresa služby: naše aplikace ze SERVICES_BASE_URL, cizí (OpenAI) z jejího baseUrl. Přebít jde přes <SLUZBA>_BASE_URL. |
targetSecrets(target) |
src/runtime/scripts/connections.ts |
Co se musí vyškrtat z logu. Vrací i holý klíč bez předpony Bearer , protože v něm ho cizí služby vracejí v chybách. |
createHttp(...) |
src/runtime/scripts/http.ts |
HTTP se timeoutem, limitem odpovědi a rozlišením "zkusit znovu" a "marné". |
isPrivateHost(host) |
src/runtime/scripts/http.ts |
Míří jméno do vnitřní sítě? Jedno pravidlo pro HTTP i pro SMTP server z konektoru. |
sendMail(target, message) |
src/mail/smtp.ts |
Odešle e-mail přes SMTP z konektoru. Nikdy nevyhodí výjimku, vrací i to, jestli má smysl zkusit znovu. |
verifySmtp(target) |
src/mail/smtp.ts |
Přihlásí se na server bez odeslání zprávy. Tím se ověřuje konektor e-mailu. |
escapeHtml(value) |
src/data/templates.ts |
Escapuje dosazenou hodnotu v HTML šabloně. Značky autora šablony zůstávají, ostré závorky od zákazníka ne. |
ctx.http.postForm(...) |
src/runtime/scripts/http.ts |
Odeslání souboru (multipart/form-data). Obsah přichází jako Base64, hranici dopisuje runtime. |
scriptIdFor(serviceId, operationId) |
src/runtime/scripts/lookup.ts |
Který skript obsluhuje operaci z katalogu. |
Klient
| Co | Kde | K čemu |
|---|---|---|
EntityAdmin |
components/dashboard/EntityAdmin.tsx |
Celá správa jedné entity: tabulka, modál, validace, mazání. Nová záložka nastavení = popis sloupců a polí, ne nová stránka. |
parseJsonField |
components/dashboard/EntityAdmin.tsx |
Textové pole s JSONem na hodnotu, s hlášením, kde je chyba. |
TicketActions |
components/dashboard/TicketActions.tsx |
CTA akcí na ticketu plus typ, tagy a vlastní pole. Seznam akcí chodí ze serveru už vyfiltrovaný. |
ErrorDetail |
components/dashboard/ErrorDetail.tsx |
Rozbalovací celé chybové hlášení s kopírováním. Chyba se nikdy nezkracuje. |
CustomWidgetCard |
components/dashboard/widgets/CustomWidget.tsx |
Vykreslí widget, jehož data počítá server: číslo, pruhy, tabulka výkonu, časová řada, seznam, data z konektoru. |
TicketTable |
components/dashboard/TicketTable.tsx |
Tabulka ticketů pro všechna místa. Na mobilu se místo posouvání do strany kreslí karty. |
TicketEvents |
components/dashboard/TicketEvents.tsx |
Příchozí události ticketu včetně celého přijatého JSONu. |
ViewSwitch |
components/dashboard/ViewSwitch.tsx |
Přepínač tabulka nebo dlaždice. Používají ho všechny seznamy. |
FlowCanvas s start |
components/dashboard/flow/FlowCanvas.tsx |
Tentýž strom kroků i bez spouštěče - pro tělo akce, které spouští člověk. |
MappingEditor |
components/dashboard/flow/MappingEditor.tsx |
Editor transformací v obou režimech (pole na pole, JSON). |
DataState |
components/dashboard/DataState.tsx |
Načítání, chyba, prázdno. Ať to každá stránka nekreslí po svém. |
apiFetch<T> |
lib/api.ts |
Jediná cesta na API: base path, token, ApiError s celým hlášením ze serveru. |
useApiQuery<T> |
hooks/useApiQuery.ts |
Načtení dat do stránky včetně reload, refreshing a total. S body pošle POST, s enabled: false se neptá, patchOn opraví data z události bez dotazu. |
useCollection(key), useAccess(), useCollectionSelector |
lib/collections.tsx |
Číselníky za firmu (lidé, skupiny, typy, služby, konektory, přístup) ze sdíleného skladu, opravované z událostí. Ne apiFetch na číselník ze stránky. |
patchTicketList(...) |
lib/ticketEvents.ts |
Oprava seznamu ticketů z payload.ticket v události. Použít jako patchOn. |
apiFetchWithMeta<T> |
lib/api.ts |
Jako apiFetch, ale vrací i X-Total-Count. Pro stránkované seznamy. |
useSubmit(fn) |
hooks/useSubmit.ts |
Odeslání formuláře: saving, chyba, reset. Dvanáct řádků, které si dřív psal každý formulář zvlášť. |
useUnsavedChanges(dirty) |
hooks/useUnsavedChanges.ts |
Varování při odchodu z rozepsaného formuláře nebo stromu. |
priorities, priorityLabel |
lib/options.ts |
Pevné číselníky. Stavy a kanály se berou ze serveru (/widget-data/options). |
plural(count, forms) |
lib/format.ts |
Skloňování počtu (1 ticket, 2 tickety, 5 ticketů). |
Field, Input, Select, Textarea |
components/ui/form/ |
Formulářové prvky s jednou sadou tříd (controlClass). Vlastní inputClass ve stránce je chyba. |
Chip |
components/ui/Chip.tsx |
Štítek. |
Table, TableHead, Th, TableRow, Td |
components/ui/Table.tsx |
Tabulka seznamu v portalu (hlavicka verzalkami, radky s linkou). Ctyri stranky ji kreslily kazda jinak. Huste tabulky ticketu a vykonu zustavaji zvlast, jsou to jine tabulky. |
ServiceIcon |
components/ui/ServiceIcon.tsx |
Ikona sluzby podle klice z katalogu. Misto const Icon = serviceIcon(key) v JSX, ktere lint hlasi jako komponentu vytvorenou pri vykresleni. |
EntityForm |
components/dashboard/EntityForm.tsx |
Formular jedne entity v modalu, pouziva ho EntityAdmin. Pole z popisu sloupcu, hodnoty a chyby v propsech. |
useLatest(value) |
hooks/useLatest.ts |
Ref s posledni hodnotou pro callbacky, ktere nemaji byt v zavislostech effectu. Zapis v layout effectu, aby vykresleni zustalo ciste. |
useSyncFromSource(source, apply) |
hooks/useSyncFromSource.ts |
Prevzeti dat ze zdroje do rozepsaneho stavu uz pri vykresleni, ne v useEffect. Stara kopie neproblikne a stranka se nekresli dvakrat. |
TicketCard |
components/dashboard/TicketCard.tsx |
Karta ticketu pro dlaždice a mobil, varianta compact pro widgety. |
useMediaQuery(query), MD_UP |
hooks/useMediaQuery.ts |
Tabulka nebo karty podle šířky pres useSyncExternalStore, prohlizec je zdroj pravdy. TicketTable podle toho kreslí obojí, druhá komponenta není. |
cn(...) |
lib/cn.ts |
Skládání tříd. Podmíněné třídy nikdy ručně přes šablonu. |
format* |
lib/format.ts |
Čísla, procenta, datum, relativní čas, trvání. Formátování se nepíše v komponentě. |
serviceIcon(key) |
lib/serviceIcons.ts |
Klíč ikony ze serveru na komponentu. Server neposílá komponenty. |
usePageMeta |
hooks/usePageMeta.ts |
Titulek stránky. |
Badge, Button, Modal, Card, ... |
components/ui/ |
Základní prvky. Nový vzhled tlačítka patří sem, ne do stránky. |
Pravidla, která z toho plynou
- Nová entita v nastavení:
defineStorev modulu entity, řádek vbootstrap.ts,crudRoutervsrc/routes/settings/<entita>.tsa mount vsettings/index.ts, popis vSettings.tsx. Nic jiného se psát nemusí. - Data, která se mění za provozu, jdou přes
withMirror. Data, která se čtou při každém requestu a mění zřídka, přeswithCache. Obojí nikdy. - Chybu se nesmí zkracovat. Server vrací celé hlášení, klient ho umí
zobrazit (
ErrorDetail). - Klient nepočítá práva. Co smí, říká
accessFor.