Measured with Playwright (rAF frame deltas around a preset click, real
Chromium, production): even with every CSS transition disabled, a
single frame still spiked to ~133ms on preset switch -- proving the
worst offender wasn't CSS at all, but the synchronous React re-render
(reset() swaps the whole draft, re-rendering every ColorField/
GradientField and the ClickGui panel at once).
Wrapped the three call sites that replace the whole draft (preset
pick, reset-to-default, file import) in startTransition so React can
deprioritize that render instead of blocking the frame the click
lands on.
Result (same measurement, after fix): peak frame 133ms -> 83ms,
frames over 100ms: 1 -> 0. Not a full fix -- frames over 16ms actually
rose slightly (18 -> 21), so total render cost is roughly the same,
just spread out instead of landing in one blocking spike. A real fix
for the remaining cost needs memoizing ColorField/GradientField so a
preset switch doesn't re-render every field instance; noted as a
follow-up, not done here.
panel.test.tsx: import's name-box assertion now waits instead of
checking synchronously, since the state update is deferred.
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
Two inherited-custom-property transitions were forcing a full style
recalculation on every animation frame during a theme switch:
- html transitioned --color-ice/--page-accent-glow (inherits: true),
both read by dozens of selectors site-wide (buttons, links, glows,
color-mix() gradients) -- animating them recomputed style for every
element that references them, across the whole page, not just the
swatch that was clicked.
- .lv-gui (the ClickGui replica used in ThemeStrip, HudPlayground, the
login backdrop and the theme editor panel) transitioned 21 inherited
custom properties at once, including per-row/per-tab ones
(--gui-row-*, --gui-tab-*, --gui-menu-*) -- with a long module list
that's the same cost multiplied by every row/tab in the tree.
Both now snap instantly instead of transitioning; .lv-gui keeps a
300ms cross-fade but only on the ~8 properties that read as "the
theme" (window/header/stroke/text/accent/panel), not per-row state.
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
Owner's complaint: the two-pane layout's decorative right-side panel
("Твои настройки LoVisual на любом компьютере...") made no sense next
to a login form. Dropped the whole two-pane concept, not just its
visuals.
Login/register now render as the mod's own in-game account screen:
night-lake backdrop (the same Backdrop the landing hero uses), a
clock + Вход/Регистрация tab switcher styled like the mod's own
ClickGui tabs, a single glass panel on the mod's own category
colours, and a real mod-style hint row ("X - Y") instead of marketing
copy. What an account actually gets you is stated as plain hints
(4 cloud slots, share a slot's code, the mod signs itself in by code,
no account required to just use the mod) instead of a sales pitch.
- AuthSplit.tsx removed; AuthScreen.tsx + auth.css replace it
- LoginForm/RegisterForm: dropped the redundant login<->register text
link now that the tab switcher does that job; RegisterForm now
honours the original `from` redirect (previously always went to
/configs even when registration started from /link)
- Rewrote auth i18n copy to match the site's established voice
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
The editor's control panel is now a second ClickGui window skinned
live with the theme being edited, so every change shows in the tool
itself, not just the preview -- same "the section is a ClickGui
window" move the landing's ThemeStrip already uses, applied to a
working tool this time.
- Removed the three uppercase eyebrow-style section labels (the same
generic-tell pattern already fixed on the landing page); colour
slots are grouped into named fieldsets (Window/Modules/Text) instead
- Panel split into panel/{PanelHeader,PresetPicker,PaletteTabs,
ExportActions}.tsx to stay under the file-size limit
- Preset chips got a real swatch (accent dot over a mini card
gradient) -- previously same-hued presets like Classic/Blue/Azure
all read as identical blue dots
- Copy/share buttons confirm inline ("Скопировано"/"Ссылка
скопирована"); import/clipboard failures now surface a message
instead of failing silently
- Rewrote themes copy to match the landing's voice
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
dev-stack.sh boots accounts/configs/gateway on localhost; e2e.sh then
replays the exact request sequences the Java client sends (device link,
%config save/load, /me) plus the site-side register/login/confirm and
asserts every field the mod reads back. 31 checks, all green.
Landing:
- Rewrote all landing copy (ru/en): removed the tracked-caps eyebrow
chrome pattern everywhere, active-voice player-facing text
- ThemeStrip: full-bleed live-recolored ClickGui window instead of a
boxed swatch panel; HudPlayground: full-size real HUD screen
- ShowcaseTeaser: dropped the fake "example" fallback cards, shows an
honest empty state when there aren't 3 real configs yet
- Fixed VoxelScene parallax leaking outside the hero into the HUD
section
Data accuracy: the module list/counts advertised InvisESP, BlockESP
and FakeLag — all three were removed from the mod earlier this
session as cheat modules. Corrected the catalogue and the 88/52
module-count claims to the real 86/50.
Design QOL pass: removed the same eyebrow-label chrome from the site
footer; replaced plausible-looking fake share codes (howItWorks demo
slots, the chat command typing demo) with an explicit placeholder
string so they read as illustration, not fabricated real data.
Perf: promoted .lv-lower-bg onto its own compositor layer
(will-change/translateZ) — a full-height, multi-gradient, masked,
continuously-animated background layer was repainting on every
scroll tick in Safari.
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
New visitors (no saved lv_theme) now get the Amber accent; reconciled the
static CSS accent, demo scene colors and console greeting to match so there
is no blue/emerald flash before the theme provider applies. Updated the
default-theme assertions in the theme tests.
Avatar objects live at a deterministic key (avatars/<id>.png) served with a
300s cache, so after a re-upload the <img> kept pointing at the same URL and
the browser showed the stale crop until reload. Append ?v=<uploaded_at epoch>
to every avatar_url (me, public profile, and showcase/grpc batch) so the URL
changes whenever the avatar changes.
Recolor the site-level --color-ice default and glow from #5cc8e7 to #34d399
and align the decorative VoxelScene/ParticleField accent and the DevTools
greeting. ClickGui/mod theme accents (classic preset) are left untouched; the
avatar success notice now reads as positive green via tone=success.
addAvatarHistory was fired off without awaiting, then the history tick was
bumped immediately, so the strip re-read IndexedDB before the write landed and
only populated after a remount. Await the write before bumping the tick.
accounts-service returned avatar_url built from the internal S3_ENDPOINT
(http://minio:9000/<bucket>), which browsers cannot resolve, so avatars never
rendered and the re-crop fetch failed. MinIO is intentionally not exposed.
Add a public GET /media/{key} read route in accounts-service (behind the
gateway internal-key guard, no identity required so anonymous profile pages
work) that streams the object out of private MinIO. Introduce
AVATAR_PUBLIC_BASE_URL so the browser-facing prefix is decoupled from the
internal endpoint; prod sets it to the same-origin /api/media, gateway routes
the media segment to accounts.
Task 12 batch around the existing crop/preview flow:
- Success Notice (auto-dismiss ~2.5s) after an avatar upload.
- "Reposition" re-crops the current avatar without re-picking a file.
- Drag & drop an image straight onto the profile avatar circle.
- Real upload progress via a new XHR-backed api.upload (fetch has none);
refreshOnce now also stores the renewed token (reload-restore fix).
- Local avatar history (last 5, IndexedDB, this-device-only) strip that
re-uploads a stored crop. Stored as ArrayBuffer (Blob is not cloneable
under structuredClone); label makes the per-device scope explicit.
- Added a "success" tone to Notice.
Infra (same session): registered @shared/@features/@pages/@app/@test path
aliases in vite.config + tsconfigs and rewrote deep ../../../ imports;
moved avatar components into components/avatar/ to keep folders at <=4 files.
Tests: avatarHistory cap/order, recrop visibility, drop-opens-cropper,
client upload/progress/401-retry. bun test 103 pass, lint 0 errors, build ok.
The download button pointed at a github.com releases redirect and the
changelog at the GitHub API — both unreachable if GitHub itself is
blocked for a visitor. The button now links to /downloads/lovisual.jar,
served directly from the site's own nginx. Release notes still read
the GitHub API (already degrades gracefully to an empty-state message
if that fails) since that's changelog content, not the actual download.
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
- nginx: rewrite the refresh cookie's Path=/auth to /api/auth on the
production proxy — without this the browser never sent the cookie
back on POST /api/auth/refresh and the session was lost on reload
- i18n: fallbackLng ru -> en, so only an actual ru browser locale
defaults to Russian, everything else defaults to English
- index.html: explicit English og:*/twitter:* tags so link previews
in Discord/etc. don't fall back to the (Russian) meta description
- Avatar upload: pick -> circular crop/reposition/zoom preview ->
confirm -> upload, instead of uploading the raw file blind
- Topbar: logo pill background removed, pixel-heart mark added next
to the LoVisual wordmark
- Fixed two tests left stale by the earlier tagline copy change
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
- Site favicon/mod icon: aligned voxel heart mark with lightning-bolt
side notches, crisp pixel edges, centered bounding box
- Landing hero + meta description updated to "visuals you'll fall in
love with" (RU/EN), no em dashes
- fabric.mod.json now points at the new mod icon asset
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
The repository had no root README at all, and frontend/README.md was
still the stock Vite template. Add a bilingual (EN/RU) overview covering
the mod, site, and backend plus how they connect (%link, %config
save/load), replace the frontend README with the actual stack, layout,
and scripts, and fix the mod README's broken DEVELOPMENT.md link to
point at DEV_GUIDE.md.
accounts-service crash-looped in production: GLIBC_2.38 not found. The
builder stage used the floating rust:1-slim tag (now Debian trixie,
glibc 2.38+) while the runtime stage used debian:bookworm-slim (glibc
2.36) — a base mismatch. Pin both stages to trixie so the runtime glibc
is always at least as new as what the binary was linked against.
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
npm install with no committed package-lock.json re-resolved semver
ranges fresh on every deploy instead of pinning to bun.lock (the
project's actual lockfile) — a supply-chain integrity gap flagged by
automated commit review. Use bun, the frontend's real package manager,
with --frozen-lockfile so deploys are reproducible.
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
minio/minio and minio/mc are no longer pullable from Docker Hub; use
quay.io/minio/minio for both the server and the one-shot bucket-init step
(mc ships bundled inside the minio image).
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
Multi-stage Dockerfiles for accounts-service/configs-service/gateway, a
docker-compose.prod.yml wiring fresh Postgres+MinIO+the three services on a
private network, nginx site templates for visual.loki-code.dev (static SPA +
/api proxy) and bekend.loki-code.dev (full gateway proxy for the mod), and
idempotent setup.sh/deploy.sh scripts for the VDS.
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
Root .gitignore's bare 'gradlew'/'gradlew.bat' patterns excluded the
wrapper scripts, so CI had no ./gradlew to run and the release failed in
seconds. Un-ignore the mod wrapper and commit gradlew (exec bit) +
gradlew.bat; the wrapper jar/properties were already tracked.
The exec-bit step chmod'd ./gradlew from the repo root where the wrapper
does not exist, failing the release within seconds. Point it at mod/ like
the build step already does.
Add PlatformHttpClient.put() and ConfigCloudUploader so %config save
<slot> snapshots the current modules and PUTs them to /configs/{slot},
reporting the returned share_code for a friend's %config load <code>.
Plain %config save still writes locally. Update command usage/i18n and
add a buildBody unit test.
Implements the last unbuilt easter egg (PLAN Task 11 #5, TODO «Пасхалки» #5): the
404 used to show static ASCII art; now a grass block drops into a dusk world,
cracks over three clicks, then bursts into square debris and respawns. Keyboard
accessible (a real button), and under prefers-reduced-motion the fall and burst
are skipped while the crack stages still work. Replaces the NOT_FOUND_ART pane;
i18n adds notFound.blockAria/blockHint (ru+en).
checkAvatarFile returned hard-coded Russian strings, so an English user picking a
bad file got "Подойдёт PNG, JPG или WebP." with no translation. Route both
messages through the account namespace (validation.avatarType / avatarTooBig) the
same way the auth validators take a t function, keeping ru/en key parity enforced
by the satisfies clause.
Two defects in this QA pass were pure CSS drift with no test surface at all:
100vw full-bleed sections panning the document by a scrollbar's width, and
controls sitting on three different corner radii. Assert both against the
sources, so the next person who hard-codes a radius or drops an overflow-x
guard fails CI instead of failing a screenshot review.
Tests now read files off disk, so they live in their own tsc project
(tsconfig.test.json, node types) excluded from the browser build.
Two directories had grown past the plan's ≤4 files per folder constraint:
shared/ui (5 primitives, and the kit keeps growing) and pages/public (5 pages,
counting auth/ as a subfolder).
Split shared/ui by concept — text-field/ and share-code/ each with their own
tests/ — and move PageTitle to shared/layout, where the rest of the page chrome
lives. Flat UI tests become per-component tests; the PageTitle test follows the
component. Move LoginPage and RegisterPage into the existing pages/public/auth/
beside the AuthSplit layout they both use.
No behaviour change: 83 tests, clean tsc build, 1 known oxlint warning.
Buttons and inputs in the kit used rounded-md (6px) while the landing CTAs
hard-coded 0.6rem (9.6px), so a form row could mix three different corners.
Add --radius-ctl (10px, matching --radius-panel), point every interactive
control at it via rounded-ctl and make the CTAs consume the same variable.
Drop the unused ButtonAnchor helper.
- add PageTitle and apply it on /download, /themes, /configs, /showcase, /link, 404
- reset range-slider thumbs in the theme editor; fold color picker into the swatch
- hide horizontal scrollbars in the ClickGui mock; clip html to kill 100vw pan
- pin the footer under short pages via flex AppShell; split 404 art from caption
- one landing CTA geometry (lv-cta-lg / quiet); CLOUD eyebrow; auth caption scrim
- showcase error hides sort/pager; query retry limited to 1
- chore(backend): sync Cargo.lock with gateway dev-dependency
- feat(frontend): ClickGui replica matching the in-game GUI — category panels, Modules/Settings switch, theme cards
- feat(frontend): floating glass top bar in the mod's UI language
- fix(frontend): soft night backdrop for the ClickGui, editor that fits one screen
- chore(frontend): replace em dashes in UI copy with lighter separators
- refactor(frontend): split landing sections and theme editor into subfolders (4-files-per-folder rule)
- feat(frontend): login and registration in the mod's glass panel style
- feat(frontend): link the game with a device code
- feat(frontend): account page with avatar upload and linked games
- feat(frontend): my configs with share codes and publishing
- feat(frontend): public showcase listing and detail pages
- chore(frontend): drop em dashes from visible copy
- feat(frontend): atmospheric backdrop, glass download block and ClickGui-style FAQ on the landing
- feat(frontend): Konami code, DevTools console greeting and ASCII 404 block
- feat: public player profiles at /u/:id and author link in the showcase
- fix(frontend): stop the theme editor overflowing horizontally at 375px
- feat(accounts): persist device links with opaque hashed tokens, list and revoke endpoints
- feat(frontend): app shell, routing and landing page with the chat-command hero
- feat(frontend): Cyrillic-first fonts (Unbounded, Onest, JetBrains Mono); add i18next and motion
- docs: free mod, bilingual site, one-click download, theme editor, public profiles, rich landing in plans
- feat(accounts): internal gRPC AuthenticateDevice guarded by internal key
- feat(frontend): ru/en i18n with typed per-feature dictionaries and language switch
- feat(accounts): GET /me profile endpoint
- feat(gateway): scaffold crate with config validation and health check
- feat(gateway): reverse proxy to accounts and configs services
- feat(gateway): resolve identity once from access JWT or device token via gRPC
- feat(gateway): per-route and global rate limits with Retry-After
- feat(gateway): CORS for the site origin; docs for gateway and internal contract
- feat(configs): scaffold service with schema, config validation and health check
- feat(configs): four config slots per account with list, get and save
- feat(configs): permanent share codes with regenerate and public load-by-code
- feat(accounts): GetPublicProfiles gRPC for showcase author info
- style(accounts,common): apply rustfmt to existing sources
- feat(configs): public showcase with publish, browse, detail and copy-to-slot
- feat(backend): public profile endpoint and showcase author filter
- fix(gateway): silence clippy collapsible-if and needless-ref warnings
- docs(backend): configs-service implemented; Подсистема 1 backend complete
- feat(mod): add Optimize module skeleton with OptimizeState holder
- feat(mod): gate glass blur behind Optimize no_glass knob
- feat(mod): cut MotionBlur and DoF sample counts behind lite_post knob
- feat(mod): trim procedural sky noise behind lite_sky knob
- feat(mod): drop fade gradients and digit rolls behind lean_hud knob
- docs(todo): mark Optimize module phase 9.2 complete
- refactor(mod): drop dead Renderer2D compatibility shims
- refactor(mod): prune unreachable Renderer2D overload towers
- refactor(mod): remove unused Renderer2D overloads and imports
- docs(todo): mark Renderer2D giant-splitting done (2179 to 1597)
- refactor(mod): extract shader id constants from LoVisualRenderPipelines
- docs(todo): record registry wave 2026-09-25 (Renderer2D, pipelines)
- refactor(mod): move Renderer2D instance state into base class
- refactor(mod): extract Renderer2DRounded drawing family
- refactor(mod): extract Renderer2DPath connector and chamfer family
- refactor(mod): extract Renderer2DShapes circle line and texture primitives
- refactor(mod): extract Renderer2DGlass and Renderer2DItem families
- refactor(mod): prune Renderer2D imports after facade split
- docs(todo): record Renderer2D facade inheritance split (1597 to 475)
- docs: easter eggs — .env honeypot, konami troll mode, devtools banner, IDDQD config, breakable 404 block, 418 teapot
- feat(mod): introduce surface style system core (SurfaceStyle, StyleSpec, StyleConfig, SurfaceRenderer)
- refactor(mod): delegate HudRenderUtil liquid glass draws to SurfaceRenderer (dedupe glass constants)
- refactor(mod): route bespoke glass call sites through SurfaceRenderer.plateSpec
- feat(mod): add Auto option to HUD bg effects via shared HudBgStyles resolution
- feat(mod): flat fallback for no-glass optimize mode and persist global HUD config
- feat(mod): default HUD bg effects to Auto so the global surface style drives widgets
- feat(mod): add global cycle-style hotkey with surface style notification
- feat(mod): add surface style swatch strip under the global style picker
- feat(gateway): reject ambiguous paths and answer .env probes with a honeypot
- fix(gateway): charge failed credentials against the rate limit, allow stale ones on /auth
- feat(frontend): ClickGui theme pipeline generated from the mod, live site theming
- feat(frontend): landing v2 hero — voxel/particle backdrop, live ClickGui, theme strip
- docs(todo): drop the FPS A/B measurement from phase 9.3, close phase 9
- feat(gateway): answer /coffee with a 418 teapot
- feat(frontend): land the rest of landing v2 — HUD, module wall, showcase, FAQ, footer
- feat(frontend): one-click download from GitHub releases, changelog page, release CI
- feat(frontend): theme editor with live ClickGui preview, mod-compatible export and share links
- fix(frontend): landing HUD playground now shows real mod widgets (fps, coordinates, module list, keybinds, ping)
- style(frontend): apply ClickGui glass effect to landing HUD playground widgets
- fix(frontend): prevent color field row overflow in theme editor grid
- fix(frontend): never attach stale bearer token to /auth/* requests
- fix(configs): unpublish/publish can no longer bypass moderation
- refactor(accounts): shrink auth/handlers.rs under the 250-line cap
- fix(accounts): tolerate concurrent refresh without killing every session
- fix(gateway): minor hardening from the backend review
- feat(configs): IDDQD easter egg config
- feat(balancer): новый модуль Balancer + команда %tier — тиры с mctiers.com
- docs(todo): отмечен Balancer как выполненный
- feat(trinket): новый модуль Trinket — прыгающий шарик-компаньон у ног игрока
- docs(todo): отмечен Trinket как выполненный
- feat(chams): добавлен режим Invert руки (HandsShader) — закрывает Фазу 7
- docs(todo): Фаза 7 полностью закрыта
- fix(stability): FireworkParticles NPE, mesh trim, tab-list warn spam, TargetHud mob icons
- fix(trollface_mask): маска не рендерилась на себе, всех/друзей игнорировал всех кроме прицела
- docs(todo): план Фазы 10 — заход по референсам из Downloads (trollface/rain/hiteffect/hmi)
- docs(todo): вывод по сравнению Rain — уже паритет с обоими референсами, mokriyzhir отдельная идея на будущее
- fix(wet_surface): кривая нормаль/мёртвый код ряби + новый модуль WetWorld (SSR top из 3 референсов)
- docs(todo): отмечен WetWorld + фикс wet_surface шейдера как выполненные
- feat(holdmyitems): grip-хват для мечей/инструментов (портировано из Delta HMI)
- docs(todo): отмечен HoldMyItems grip как выполненный
- feat(hiteffect): режим Impact Ring — кольцевая волна + вертикальное свечение при ударе
- docs(todo): отмечена Фаза 10 полностью выполненной
- feat(gps): новый модуль Gps — компас-стрелка + маркер к точке (идея Soup)
- feat(hitbubbles): новый модуль HitBubbles — отдельно от HitEffect.BUBBLES
- feat(ambientparticles): новый модуль — атмосферные мошки / светлячки со шлейфом
- docs(todo): отмечены Gps/HitBubbles/AmbientParticles как выполненные (Фаза 7)
- feat(endcrystal): новый модуль — тонировка модели + осколки при разрушении
- docs(todo): отмечен EndCrystal как выполненный