Commit graph

82 commits

Author SHA1 Message Date
483d6bfb93
docs(mod): detail evo port plan in TODO
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
2026-10-02 09:50:15 +02:00
e604a48c31
fix(mod): smaller module description plaque, tooltip auto-scale to GUI scale
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
2026-10-02 09:49:02 +02:00
0504be5b57
feat(mod): scripted HoldMyItems on LuaJ (hand/item/model scripts, addons, vanilla overrides, particles, camera)
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
2026-10-02 09:47:30 +02:00
44353e893c
feat: mod platform integration (showcase, cloud slots, cloud screen, server-side unlink)
Mod:
- %config slots/pull/publish/unpublish for the four cloud slots
- %showcase [new|popular] [page] | load | copy, with number references
- %cloud opens a CloudScreen (link/unlink, slots, showcase) on vanilla widgets
- %config load IDDQD works offline (everything off except ChinaHat)
- default backend URL is now the production gateway
- shared ConfigRemoteApplier and ClientThread replace per-class copies
- %link unlink revokes the link on the server, then clears the local token

Backend:
- POST /device/revoke (RFC 7009 style self-revoke, always 204), rate limited
  to 10/min per IP at the gateway

CloudScreen is compiled but has not been opened in a running client yet.
2026-10-02 09:23:44 +02:00
e00ea8eb11
feat: frontend perf and a11y pass, plus mod visuals, hero main menu, Simple Voice Chat and vanilla chat autocomplete
Frontend:
- replace the i18next stack with a small typed i18n module
- hero clouds removed, llms.txt, HUD widget ARIA fixes and tests

Mod:
- TargetESP constellation mode; tracker and colors extracted
- JumpCircles: 7 new shapes, shared animation and color pipeline
- KillEffect blood mode with Burst/Fountain/Ring/Spray styles
- main menu: menuLayout setting (orbs/hero) and hero screen
- Simple Voice Chat soft integration: VoiceChat module and HUD panel
- renderer/quick: static Draw facade with glass outline
- vanilla chat: Baritone-style autocomplete popup for the % command prefix
2026-10-01 21:29:54 +02:00
167405aed0
perf(frontend): replace react-markdown with a small safe renderer (DownloadPage 120 KB -> 6 KB) 2026-09-30 16:27:32 +02:00
d629ccf496
perf(frontend): drop the motion library for CSS keyframes and small FLIP/presence hooks 2026-09-30 16:26:36 +02:00
7172652ac3
perf(frontend): defer landing lower sections and voxel backdrop, skip guest refresh 401, fix hud a11y 2026-09-30 16:24:28 +02:00
b170227adf
perf(frontend): route-level code splitting, main chunk 538 -> 491 KB
Lighthouse (production, default mobile 4x-CPU profile) scored
Performance 33/100, dominated by one ~536-538 KB entry chunk that
bundled every route's code regardless of which page a visitor landed
on -- the "chunks are larger than 500 kB" Vite warning on every build
this session.

Converted every route except / and the 404 catch-all to React Router
8's native route.lazy (blocks navigation until the chunk is ready --
no separate Suspense fallback needed). AppShell/HomePage/NotFoundPage/
RequireAuth stay eager since they're needed on every load regardless.

Result: main chunk 490.84 KB, no >500kB warning, one small chunk per
route (LoginPage 1.4 KB, ThemesPage 15.5 KB, AccountPage 11 KB, etc.)
loaded only when actually visited.

Fixed a real race the lazy boundary exposed: /login and /register
share an "Почта" field label, and during the chunk swap between them
the old page can stay mounted a beat longer than before -- a test
grabbing the field by label could land on the about-to-unmount one.
Also bumped RTL's asyncUtilTimeout and vitest's testTimeout, since the
route-transition + findBy chain didn't always land inside the old
1000/5000ms defaults under full parallel test-suite load.

Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
2026-09-30 16:06:45 +02:00
6b0633034d
feat(frontend): SEO — sitemap, canonical link, structured data
- robots.txt now also points at a real sitemap.xml (public pages
  only: /, /download, /themes, /showcase) and disallows the
  auth-gated routes (/account, /configs, /link) that have nothing
  useful to a crawler
- canonical <link> on the root page
- SoftwareApplication JSON-LD so Google can render a rich result
  (name, free price, icon) instead of a plain blue link

Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
2026-09-30 15:48:44 +02:00
9744dc7f24
fix(frontend): add a real robots.txt, cache hashed assets forever
Lighthouse audit findings:
- No robots.txt existed, so nginx's SPA fallback (try_files -> index.html)
  served the React app's HTML at /robots.txt -- crawlers got 44 "syntax
  not understood" lines instead of directives.
- No Cache-Control on /assets/*, so every visit re-fetched a hashed,
  content-addressed bundle that can never actually change under that
  URL. Lighthouse estimated 936 KiB/visit wasted on this alone.

Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
2026-09-30 15:40:04 +02:00
54b47db7c1
docs(plan): restore full plan and integrate phase 11 design 2026-09-30 12:29:35 +02:00
fdc239db88
feat(mod): %policy command and clickgui lock 2026-09-30 12:24:43 +02:00
33cff90c82
feat(mod): apply server policy from the proxy 2026-09-30 12:24:42 +02:00
711fcafc3a
feat(policy): velocity plugin sending a lovisual:policy payload 2026-09-30 12:08:02 +02:00
af2b417289
docs(plan): design phase 11 server policy plugin 2026-09-30 12:08:02 +02:00
676dc4f441
docs(plan): record 8.5.3 top-4 split wave 2026-09-30 11:33:17 +02:00
71f779c8ee
refactor(hud): split BetterButtons to 197 lines
Settings values and visibility predicates move to ButtonStyleSettings,
icon drawing to ButtonIconRenderer; facade keeps the public static API
and getters. Drops three dead one-liners (getLastContext,
renderCenteredText, renderLeftAlignedText) and the unused
renderButtonInternal wrapper.
2026-09-30 11:32:17 +02:00
76bd8e9510
refactor(hud): split ScriptedTriangulatorHudPanel to 189 lines
Rendering moves to ScriptedTriangulatorRenderer, prop maps and cache
signatures to scripted/panel/; records and row helpers stay nested so
caller references (Panel, Palette, row, rows, idString) keep working.
2026-09-30 11:22:54 +02:00
3e57542bad
refactor(aim): split RotationManager to 199 lines
State machine, mutable state and server-rotation packet tracking move to
rotation/internal/; the singleton, event wiring and public API stay put.
2026-09-30 11:17:03 +02:00
7e8b6b6467
refactor(clickgui): split ConfigDiffPanelComponent to 167 lines
Header buttons, body text and scrollbar move to package-private helpers
under config/diff/; the public API, rendering order and math stay as-is.
2026-09-30 11:07:33 +02:00
6fa36f8b32
perf(frontend): memoize ColorField/GradientField with stable callbacks
React.memo alone does nothing here: PaletteTabs built a fresh
`(hex) => setColor(slot, hex)` closure per field on every render, so
memo's prop comparison always saw a "changed" onChange and re-rendered
anyway. Stabilized it properly:

- setColor/setGradient read draft/onChange from a ref (updated in a
  useLayoutEffect, not during render -- oxlint's react(refs) rule
  correctly flags a ref write in the render body) so they keep a
  stable identity (useCallback, empty deps) across edits
- per-slot wrapper closures are built once in a useMemo keyed off
  those now-stable functions, not off `draft`

This doesn't change the preset-switch case measured earlier (every
field's value genuinely changes then, so memo correctly re-renders
all of them -- that cost is what startTransition already covers). It
does stop sibling fields re-rendering on interactions that don't
touch them: undo/redo, tab switching, hovering unrelated panel
elements.

Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
2026-09-30 10:58:34 +02:00
786fe1cc1d
perf(frontend): defer the theme editor's heavy preset/import re-render
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
2026-09-30 10:51:13 +02:00
ab86078ca3
perf(frontend): stop theme switches from janking the whole page
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
2026-09-30 10:37:40 +02:00
756ebc6d86
feat(frontend): full redesign of login/register
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
2026-09-30 10:30:53 +02:00
c2c7e8fa91
feat(frontend): redesign the theme editor page
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
2026-09-30 10:28:59 +02:00
fb853a26f4
docs(todo): record the download task as shipped 2026-09-29 18:56:27 +02:00
70e33f290f
fix(download): keep the GitHub label visible under the ribbon 2026-09-29 18:53:52 +02:00
eb6e38e20c
chore(docker): keep the build context to the workspace crates 2026-09-29 18:46:46 +02:00
fc440f0c91
feat(download): two-button page with the jar served by our backend 2026-09-29 18:39:40 +02:00
54af71589a
feat(gateway): serve the mod jar under GET /downloads/* 2026-09-29 18:39:39 +02:00
192ef52eed
docs(todo): plan download page, server plugin phase and remaining giants split 2026-09-29 18:36:17 +02:00
5dfbf6c1e8
test(backend): replay the mod's gateway calls as a repeatable e2e check
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.
2026-09-29 18:12:49 +02:00
f51d3bf0f8
style(backend): run cargo fmt across the workspace 2026-09-29 17:48:36 +02:00
a109d7bb99
fix(backend): fill avatar_public_base_url in the accounts test config
The field added in 8a758754 was never propagated to the shared test
fixture, so every accounts-service test binary stopped compiling.
2026-09-29 17:48:35 +02:00
9d423456af
feat(frontend): landing redesign, mock/fake-data cleanup, Safari fix
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
2026-09-29 17:22:02 +02:00
3f679b83f6
feat(frontend): default site theme to Amber instead of Classic
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.
2026-09-29 10:55:18 +02:00
8a758754dd
fix(backend): cache-bust served avatars with an uploaded_at version
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.
2026-09-29 10:42:49 +02:00
eb854c32da
feat(frontend): switch the default site accent from cyan to emerald
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.
2026-09-29 10:14:18 +02:00
e8b77fb0a5
fix(frontend): avatar history strip fills in without a reload
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.
2026-09-29 10:14:18 +02:00
b496bde701
feat(backend): serve avatars through accounts-service behind the gateway
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.
2026-09-29 10:14:18 +02:00
fd2e29cede
feat(frontend): avatar upload QOL — feedback, re-crop, drop, progress, history
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.
2026-09-28 22:56:01 +02:00
e79b0aedd2
fix(download): serve lovisual.jar from our own origin, not GitHub
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
2026-09-28 22:32:21 +02:00
f97f1a7117
feat(frontend): add a proper 1200x630 og:image for link previews
Square favicon.png was a weak og:image. Renders the same pixel-heart
mark next to the wordmark and tagline on the site's dark background.

Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
2026-09-28 22:26:38 +02:00
e0531eda5e
fix(frontend): session cookie path, browser-language default, avatar crop, EN link previews, topbar logo
- 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
2026-09-28 22:23:28 +02:00
fc0e125e62
feat(brand): 3D pixel-heart icon and updated site tagline
- 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
2026-09-28 22:14:14 +02:00
0ecb4635d1
docs: add a real root README and rewrite the frontend one
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.
2026-09-28 17:28:41 +02:00
95186ad49c
fix(deploy): pin backend Dockerfiles' builder/runtime base to the same Debian release
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
2026-09-28 15:32:30 +02:00
1998cdae24
fix(deploy): use bun --frozen-lockfile instead of npm install
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
2026-09-28 15:30:42 +02:00
4c2486d7cd
fix(deploy): use npm install (no committed package-lock.json in frontend)
Claude-Session: https://claude.ai/code/session_01F1M1Jic1wTSn4igUENynmZ
2026-09-28 15:27:12 +02:00