Pure CSS approach: @property --border-angle + conic-gradient on border-box.
No extra elements, no overflow issues, no JS animation.
Smooth rotation via CSS Houdini custom property animation.
Replace framer-motion gradient interpolation (buggy in TG WebView) with
pure CSS conic-gradient + transform:rotate() keyframe animation.
Transform animations are GPU-accelerated and reliable everywhere.
- Use accent CSS vars for gradient (matches app theme dynamically)
- Increase gradient spot size for visible animation
- Darker inner bg (dark-900) for better contrast
- Increase blur to 3px for glow effect
Dashboard is the default landing page for all authenticated users.
Loading it eagerly eliminates one extra network round-trip for the
JS chunk (+30KB / +7KB gzip to main bundle, but saves ~200-500ms
of chunk fetch + parse time on first load).
- Move Google Fonts from CSS @import (render-blocking) to <link> in
index.html with preconnect for earlier discovery
- Remove @import from globals.css — browser no longer waits for CSS
parse -> @import fetch -> font CSS parse before first paint
- Defer initLogoPreload() to requestIdleCallback so logo fetch
doesn't compete with critical resources during page load
- Add prefetchBackground() to registry — triggers dynamic import
without creating React component
- Call prefetch at module init in BackgroundRenderer using cached
config from localStorage, before React renders
- Background JS chunk starts downloading immediately instead of
waiting for Suspense to trigger lazy() import
- Use 300% oversized CSS grid container instead of fixed-size flex cells
- Grid with 1fr units auto-fills the container, skew+scale covers viewport
- Simplified to flat cell array, removed SVG crosses for cleaner look
- Use original Aceternity positioning: translate(-40%,-60%) with
-top-1/4 left-1/4 to cover viewport after skew transform
- Switch from CSS grid to flex layout matching original structure
- Add cross (+) SVG markers at grid intersections
- Use pastel color palette from original, animate opacity per cell
- Increase default rows to 20 for better coverage
- Delete google-gemini-effect.tsx and noise-background.tsx
- Remove from types, registry, and backend whitelist
- Rewrite aurora: use animate-aurora directly on element instead of
invisible pseudo-element with mix-blend-difference
- Aurora: remove invert filter (broken on dark bg), use dark gradient directly
- Beams: use animated linearGradient positions instead of pathLength animation
- Beams Collision: add collision detection, explosion particles, AnimatePresence
- Gemini Effect: rewrite as SVG motion.path with animated pathLength (was canvas)
- Noise: change mixBlendMode from overlay to screen (overlay invisible on dark bg)
- Create AdminRoleEdit.tsx for /admin/roles/create and /admin/roles/:id/edit
- Create AdminPolicyEdit.tsx for /admin/policies/create and /admin/policies/:id/edit
- Remove create/edit modals from AdminRoles.tsx and AdminPolicies.tsx
- Add new lazy routes in App.tsx with proper permission gates
- Keep delete confirmation as small inline modal (appropriate for destructive action)
- Read policy role_id from top-level field instead of conditions object
- Use policy.role_name for display, remove conditions.role_id lookup
- Remove duplicate role_id injection into conditions payload on submit
- Fix PermissionGate strings: policies:* -> roles:* to match backend
- Remove redundant client-side status filtering in audit log (already
filtered server-side)
- Move RBAC translations from banSystem to admin namespace in all 4 locales
- Fix AccessPolicy type: action (string) -> actions (string[])
- Fix AuditLogEntry type to match backend: add status, request_method, request_path, resource_type, user info
- Fix AuditLogFilters: resource -> resource_type, add status filter
- Handle null details safely in audit log expanded view
- Fix policy form submission and display to use actions array
- Add global settings section (CHANNEL_IS_REQUIRED_SUB, trial/paid toggles)
at top of AdminChannelSubscriptions page
- Add per-channel disable_trial_on_leave and disable_paid_on_leave toggles
in each channel card with inline Toggle controls
- Update RequiredChannel type and API request interfaces with new fields
- Add translations for globalSettings and perChannel in en/ru/fa/zh locales
- Admin panel: added Edit button on channel cards with inline edit form
(title, link, sort order) using existing PATCH API
- Subscription screen: filter out already-subscribed channels so users
only see what they still need to subscribe to
- Updated channelIdHint to remove @username reference (numeric only)
- Added i18n keys: edit, editing, form.save (en/ru/fa/zh)
- Channel subscription blocking screen with per-channel status display
- Safe URL handling with protocol validation (no open redirect)
- Race-condition-safe subscription check with useRef guard
- Admin channel management page with create, toggle, delete
- Error handlers on all admin mutations with haptic feedback
- Proper type guard for channel subscription error detection
- Accessibility: aria-labels on icon-only buttons
- Localization: en, ru, zh, fa translations
memory.used from systeminformation includes disk cache/buffers,
showing ~91% when actual usage is ~18%. Now uses (total - available)
which matches htop/free output. Also added PB/EB units to formatBytes.
The old clearStaleSessionIfNeeded stored initData in sessionStorage for
comparison, but sessionStorage is cleared on tab close — so switching
Telegram accounts between separate Mini App openings was never detected.
Fix: store the Telegram user ID in localStorage (survives tab close).
On every launch, compare the fresh initData user ID with the stored one.
If they differ, clear all auth tokens and cached user data to force
re-authentication as the correct account.
Replace all bare useAuthStore(), useBlockingStore(), and
useSuccessNotification() calls with individual field selectors.
This prevents components from re-rendering when unrelated store
fields change.
- 21 useAuthStore usages across 20 files: individual selectors
for 1-2 fields, useShallow for 3+ fields
- 5 useBlockingStore usages: individual selectors
- 2 useSuccessNotification usages: individual selectors
- Remove isFullscreen from useBranding useEffect deps to prevent
re-render loop (requestFullscreen changes isFullscreen which
re-triggers the effect); use ref guard instead
- Make AdminBroadcasts 5s polling conditional: only poll when there
are active broadcasts (queued/in_progress/cancelling)
- Add URL.revokeObjectURL on unmount and media removal in
AdminBroadcastCreate, AdminPinnedMessageCreate, Support
- Track blob URLs via refs to revoke before creating new ones
- Add clearCreateAttachment/clearReplyAttachment helpers in Support
to properly revoke blob URLs when clearing attachments
- Cap adminTraffic cache at 20 entries with FIFO eviction to prevent
unbounded memory growth from different filter combinations
Prevent MutationObserver re-initialization on every render by moving
the options object out of JSX (was creating a new object reference
on each render of AppWithNavigator).
- Remove mix-blend-mode: overlay from body::before noise texture
(was forcing fullscreen GPU compositing on every paint)
- Disable noise texture on mobile via display:none media query
- Replace backdrop-filter animation in backdropFadeIn/Out keyframes
with opacity-only animation (backdrop-filter transition is expensive)
- Replace toast progress bar width animation with scaleX (GPU-composited)
- Remove redundant will-change: transform from .glass and .wave-blob