fix(form): break drawer counter/dirty infinite-loop + add GlobeLoader + AppErrorBoundary
Maximum update depth exceeded showed на /dictionaries/$name когда RecordDrawer
открывался в create mode. Root cause:
1. SchemaDrivenForm useEffect deps включал onCounterChange/onDirtyChange.
2. Parent route передавал inline arrow (() => setDrawerCount({...})) —
новая identity каждый render.
3. setDrawerCount({visible, total}) создавал новый object literal даже
при тех же значениях → React re-render → effect fires → loop.
Fix:
- SchemaDrivenForm: ref-pattern для callback. useEffect subscribed только
на value deps (visibleFieldCount/totalFieldCount/dirtyCount), latest
callback хранится в ref.
- dataValues — useMemo с frozen EMPTY_DATA default чтобы не invalidate'ить
isFieldVisible memo каждый render.
Plus:
- GlobeLoader component — orbital globe spinner per Downloads/Globe loader
prototype. Pure SVG + CSS animations, без d3/topojson.
- LoadingBlock переключен с Loader2 на GlobeLoader (все usage обновляется
автоматически: RecordHistoryDrawer, dictionaries.$name editor loading,
search results, webhooks list, etc).
- AppErrorBoundary — friendly fallback вместо TanStack DefaultErrorComponent.
GlobeLoader (paused/dimmed) + 'Что-то пошло не так' + [Обновить][На главную].
Dev-only collapsible с raw stack trace.
This commit is contained in:
@@ -374,6 +374,45 @@ body {
|
||||
color: var(--color-mute);
|
||||
}
|
||||
|
||||
/* === GlobeLoader keyframes — orbital spinner (src/ui/components/globe-loader.tsx).
|
||||
* Класс .globe-loader на wrapper'е активирует вложенные .globe-* animations
|
||||
* через стандартные CSS animation rules. Transform-only — GPU-композитятся. */
|
||||
@keyframes ord-globe-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
@keyframes ord-globe-pulse {
|
||||
0%, 100% { opacity: 0.18; }
|
||||
50% { opacity: 0.42; }
|
||||
}
|
||||
.globe-loader .globe-whirl {
|
||||
transform-origin: 100px 100px;
|
||||
transform-box: fill-box;
|
||||
animation: ord-globe-spin 3.6s linear infinite;
|
||||
}
|
||||
.globe-loader .globe-whirl-rev {
|
||||
transform-origin: 100px 100px;
|
||||
transform-box: fill-box;
|
||||
animation: ord-globe-spin 5.4s linear infinite reverse;
|
||||
}
|
||||
.globe-loader .globe-pulse {
|
||||
transform-origin: 100px 100px;
|
||||
transform-box: fill-box;
|
||||
animation: ord-globe-pulse 2.4s ease-in-out infinite;
|
||||
}
|
||||
.globe-loader .globe-sphere {
|
||||
transform-origin: 100px 100px;
|
||||
transform-box: fill-box;
|
||||
animation: ord-globe-spin 14s linear infinite;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.globe-loader .globe-whirl,
|
||||
.globe-loader .globe-whirl-rev,
|
||||
.globe-loader .globe-pulse,
|
||||
.globe-loader .globe-sphere {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* === Legacy fallback: старые компоненты используют nstart token names
|
||||
* (--color-carbon, --color-ultramarain etc). Маппим их на новые pending
|
||||
* полная миграция. === */
|
||||
|
||||
Reference in New Issue
Block a user