Commit feaa2f7
feat(frontend): restrained refinement — visual & interaction overhaul (#39)
* fix(frontend): repair bit-rotted mock feature so local preview compiles
`trunk serve --features mock` — the documented local-preview path in
Trunk.toml — no longer compiled: CI never builds the mock feature, so
mock initializers fell behind struct evolution.
- Add the missing `latest_codex_check` / `latest_kiro_check` fields to
both AdminUpstreamProxyConfigView mock payloads.
- Add the missing `codex_fast_enabled` / `kiro_candidate_credit_summary`
fields to both AdminLlmGatewayKeyView mock payloads.
- Gate build_admin_kiro_usage_event_detail_url with the same
`cfg(any(not(feature = "mock"), test))` as its sibling helpers; its
only non-test caller already sits behind cfg(not(mock)).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* frontend: token foundation — motion, focus ring, type scale, contrast
Phase 1 of the restrained-refinement pass. Pure CSS, no class deletions.
- New `@theme static` token block (static so Tailwind v4 does not prune
the not-yet-referenced variables): --motion-fast/base durations, a
unified --focus-ring, fluid clamp() heading scale
(--font-size-display/h1..h6), and skeleton surface tokens with dark
overrides in the data-theme block.
- Fix the long-dead reduced-motion kill switch: the global block used
`prefers-reduced-motion:reduced` (invalid value, never matched);
output CSS now has 10/10 valid `reduce` queries.
- Raise light-theme --muted #6c7382 -> #5b6270: ~3.8:1 on --bg failed
WCAG AA; new value measures ~4.8:1 on --bg and ~6.1:1 on --surface.
- Add a global :focus-visible outline plus --focus-ring treatment for
the btn-terminal/btn-fluent families (no rules existed before).
- Dedupe 7 keyframes defined twice (fadeInCard three times): identical
bodies lost the later copy, differing bodies kept the cascade-winning
later body, verified by body diff before deletion.
Verified in mock preview: tokens resolve in both themes at runtime,
--muted/--skeleton-base flip with data-theme, built CSS contains both
:focus-visible rules.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* frontend: interaction infrastructure — toast, modal, skeleton, form kit
Phase 2 of the restrained-refinement pass: the reusable feedback and
form layer, each wired to one pilot consumer.
- components/toast.rs: ToastProvider + use_toast(). The context carries
only the reducer dispatcher (stable identity), so pushing a toast
re-renders the viewport alone. Stack capped at 4; success/info
auto-dismiss in 4s, errors in 6s; aria-live viewport on an acrylic
surface (the sanctioned glass language for overlays).
- components/modal.rs: Modal (portal to body, focus trap with Tab
cycling, Escape + backdrop-click close, body scroll lock, focus
restore) and ConfirmModal (danger/busy states) to replace
window.confirm for destructive actions.
- components/skeleton.rs: SkeletonBlock/Text/Card/Article — layout-
reserving shimmer placeholders; shimmer pauses under reduced motion.
- components/form.rs: FormField/TextInput/SelectInput/TextArea with one
.form-input class replacing the 9-utility string duplicated across
admin pages; aria-invalid + aria-describedby wired to field errors.
- Pilots: music library wish form (toast success/error + inline
required-field validation), latest-articles loading state (skeleton
card grid replaces the full-page spinner), admin published-comment
delete (ConfirmModal + success toast).
- Deps: gloo-events/gloo-utils 0.2 (already in the lock graph);
IntersectionObserver web-sys features staged for the scroll-spy phase.
Verified in mock preview: empty submit shows both field errors with
aria-invalid; successful submit pushes a success toast that auto-
dismisses and clears the form; toast viewport mounts app-wide.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* frontend: one glass language, calm cards, animation noise removal
Phase 3 of the restrained-refinement pass — the big deletion phase.
Net -1093 lines.
Surfaces:
- Acrylic is now the only glass language, reserved for floating
overlays; new .acrylic-surface class adopted by the mini player.
- All 25 liquid-glass call sites across 12 files drop the class; cards
fall back to their existing surface + border + shadow styling.
- Cards calm down: .article-card/.stats-card hover is a 2px lift +
shadow elevation + border accent on motion tokens — the perspective
tilt, magnetic tracking, click ripple, border-radius morphing, glow
pulse, and specular sweep machinery is gone (stats_card.rs sheds its
mousemove/click JS entirely).
- body returns to a flat var(--bg): the three-layer animated mesh
gradient ran on the GPU for the lifetime of every page.
Deleted CSS (grep-verified zero references before each removal):
liquid-glass family + far/mid/near layers, glass-iridescent(-animated),
glass-refraction, glass-realistic, glass-specular, glass-texture-bg,
shimmer-hover, wave-distort (+ its index.html SVG filter), fluid-edges,
liquid-card, liquid-pulse, card-3d-container, spotlight (component,
router mount, index.html node, CSS), stats/article ripple spans,
hero avatar spin, year-label perpetual shimmer, badge-pulse,
rotate-rainbow, button shimmer streak + click ripple, and keyframes
mesh-float / glow-pulse / fluid-flow / liquid-{shimmer,ripple,pulse} /
search-hero-{rotate,shimmer,glow,glow-hover,glow-dark,glow-dark-hover}
/ heroAvatarSpin / button-ripple / shimmer + the --search-hero-angle
@Property.
Kept identity: the search-hero conic rainbow border stays as the home
page signature, now static (135deg) with a static hover glow; the
terminal brand cursor blink remains the one infinite animation.
Verified in mock preview: home/posts render with calm cards in both
themes; infinite animations on /posts dropped from 6 named families to
exactly ["blink"]; body computed background-image is none; styles.css
236.9KB -> 225.6KB.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* frontend: fluid editorial type scale and anchor-safe headings
Phase 4 of the restrained-refinement pass.
- Article headings consume the Phase 1 clamp() tokens
(--font-size-h1..h6) instead of fixed rem sizes, so they track the
viewport down to 320px without overflow; desktop sizes are unchanged
by construction (clamp maxima equal the previous fixed values).
- .page-title becomes fluid the same way (2.25rem desktop -> 1.7rem
phones).
- Article headings gain scroll-margin-top under the sticky header,
pre-wiring clean anchor jumps for the TOC scroll-spy phase.
Audited, deliberately unchanged: body measure (850px) and the em-based
spacing ladder with its 768px overrides are already well tuned;
font-mono outside terminal/admin contexts is limited to timecodes and
date pickers (correct tabular usage); Fraunces appears on headlines
only. No churn for its own sake.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): allow not-yet-consumed kit APIs under -D dead-code
clippy on wasm (-D warnings) rejects the interaction-kit items whose
consumers land in later phases: SelectInput's props/option structs
(admin migration phase), the standalone SkeletonBlock props (feedback
adoption phase), and the Info toast kind/method (feedback sweep).
Annotate them with the repo's allow(dead_code, reason) pattern; the
allows come off as the consumers arrive.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* frontend: article reading experience — progress bar, code labels, touch lightbox
Phase 5 of the restrained-refinement pass.
- New ReadingProgress component (article detail): a 2px primary line
under the sticky header tracking position through the article body.
The scroll handler writes the bar width straight through a NodeRef so
scrolling never re-renders; articles that fit the viewport show no
bar at all.
- Code blocks (index.html enhancer + CSS): inject a Space Mono language
label from the highlight.js language-* class; on touch devices the
copy button is always visible with a finger-sized hit area instead of
hover-revealed; copy button gains an aria-label.
- Lightbox touch gestures (article_detail.rs): two-finger pinch drives
the existing zoom state (0.5x-3x), double-tap toggles 1x <-> 2x, and
`touch-action: pan-x pan-y` keeps one-finger panning native while
suppressing the browser's own pinch. Keyboard support and the wheel
path are unchanged.
Found already implemented (no work needed, contrary to the initial
audit): TOC scroll-spy with active-lock highlighting and smooth anchor
scrolling, and lightbox keyboard handling (Escape/+/-/0) — both live in
the index.html TOC/lightbox scripts.
Verified in mock preview: progress bar mounts on article detail and
reports 0% for short articles / fills on scroll events; new wasm + CSS
deployed through the dev server.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* frontend: feedback adoption — toasts for hidden confirmations, skeletons for lists
Phase 6 of the restrained-refinement pass.
Toasts (6a):
- Selection-comment success on the article page now confirms via toast:
the selection modal closes on success, so the previous inline
feedback was never visible. Errors stay inline in the modal where the
user can correct and retry.
- Article-request submission success moves to a toast for a consistent
confirmation pattern; errors stay next to the form.
- Deliberate non-change: home stats fetch failures keep degrading
silently to zeroes — background loads, not user actions.
Skeletons (6b):
- tags / categories / tag_detail / category_detail swap the centered
full-height spinner for a 6-card skeleton grid matching the loaded
layout (no layout shift on arrival).
- Article detail's main loading view becomes SkeletonArticle sized to
the 850px reading column.
- Kept: interactive_article's spinner (redirect interstitial) and the
small inline spinners for busy states.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* frontend: header search suggestions with full keyboard navigation
Phase 7 of the restrained-refinement pass.
- New SearchSuggest component replaces the bare desktop header input:
parent-controlled value (the header's search/clear buttons keep
working off the same state), 300ms debounced top-5 keyword
suggestions with a request-sequence guard against out-of-order
responses, ArrowUp/Down cycling, Enter to open the highlighted
article or submit free text, Escape and outside-click dismissal,
combobox/listbox/aria-activedescendant semantics, acrylic dropdown
surface.
- The header's now-superseded desktop Enter-key handler is removed;
the mobile overlay search keeps its simple input (a dropdown inside
the slide-over menu would fight the overlay).
Deliberately dropped from this phase: converting the image/music grid
pagination to infinite scroll. The append-mode rework would entangle
URL/page state and back-button behavior for a pattern upgrade the
existing pagination doesn't need — restraint over feature-chasing.
Verified in mock preview end-to-end: typing opens suggestions
(aria-expanded), arrows move the highlight with aria-activedescendant
tracking, Enter navigates to the highlighted article route.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* frontend: admin console deletes move from window.confirm to ConfirmModal
Phase 8 of the restrained-refinement pass (admin alignment).
All four destructive flows on the admin console — published comment,
comment task, music wish, and article request deletion — now share one
pattern: the action dispatcher parks the id in a pending state and the
focus-trapped ConfirmModal (Escape / backdrop / focus-return semantics)
confirms before the original execution path runs. The former dispatch
callbacks become execute_* with their interior logic untouched
(inflight guards, optimistic updates, error handling), so the wrappers
only intercept the Delete arm. confirm_destructive() has no remaining
callers in this page.
Scoped follow-up (kit is ready, swaps are mechanical): the four
confirm_destructive sites in admin_gpt2api_rs, and the gateway pages'
flash-callback -> use_toast and utility-string -> TextInput/FormField
migrations.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* frontend: final audit — dead token sweep, themed selection tint
Phase 9 of the restrained-refinement pass.
- Remove the eleven glass-* tokens orphaned by the Phase 3 surface
unification (--glass-{blur,alpha}-{far,mid,near}, --glass-opacity,
--glass-noise-{light,dark,svg}, --glass-saturate) from both theme
blocks — zero var() references remained. The acrylic token family
stays (header, toasts, modals, dropdowns, mini player).
- Drop the unreferenced .empty-hint rule and the stale
.year-label::after entry in the tag-detail reduced-motion list.
- ::selection now tints from var(--primary-rgb) in both themes instead
of two hardcoded off-palette blues (the dark override collapses).
- Audited, no change needed: the pagination ellipsis is a decorative
aria-hidden span (correct semantics); the remaining infinite
animations on home are the deliberate brand set (terminal cursor
blinks, terminal dots, hero swipe hint, GitHub Wrapped border).
Final styles.css: 226.8KB vs 236.9KB baseline (-4.3%) with the toast/
modal/skeleton/form/search-suggest/reading-progress kits added on top.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* frontend: restore dark-mode spotlight, fix dark: variant wiring, polish search & forms
Course-correct the restrained-refinement pass per review feedback:
- Restore the dark-mode cursor-following spotlight (components/spotlight.rs),
now demand-driven (rAF runs only while dark + pointer moving, parks once
settled) and tinted with the theme's interactive color.
- Wire Tailwind's `dark:` variant to the app's `data-theme` switch via
`@custom-variant`; the 260+ `dark:` utilities previously followed only the
OS `prefers-color-scheme` and ignored the in-app toggle, so dark:-styled
islands stayed light. Convert the lone `[.dark_&]` header usage to `dark:`.
- Rebuild the header search dropdown as a floating acrylic panel that never
reflows the header, with a loading skeleton, entrance animation, empty
state, category chips, and an Enter-to-search footer.
- Refine form inputs (recessed fill, hover/focus, placeholder) and give the
music wish form an accent-barred gradient card.
- Theme-aware wish status chips (were fixed -600 shades that muddied on dark).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* frontend: white spotlight, visible iridescent search borders, avatar hover spin
Address review feedback on the flair-restoration pass:
- Spotlight is a white light focus again (was tinted with the interactive cyan).
- Make the static iridescent border actually render on the hero search button
and the music search box: the old ::before + mask ring sat at inset:-1px and
was clipped by overflow:hidden to a ~0.5px sliver. Switch both (and their dark
variants) to the robust padding-box/border-box dual-background technique so the
colorful edge always shows crisply.
- Restore the homepage avatar's hover spin (reduced-motion guarded).
- Add the music search box focus glow (the `.focused` class was wired up in the
page but had no visual) with a smooth box-shadow transition.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* build(frontend): add one-click portable build/publish script
Local builds had relied on hand-editing crates/frontend/Trunk.toml to point the
Tailwind pre_build hook at a machine-specific binary path -- non-portable and
easy to commit by accident (it would break CI and other machines, which use the
`npx @tailwindcss/cli` hook backed by package.json). Add a script that removes
the need for any such edit by resolving the toolchain itself:
- scripts/build_frontend.ps1: ensures the wasm32 target, Trunk, and the Tailwind
CLI (one-time `npm install`; afterwards npx resolves it locally with no
per-build download -- the original source of friction), then runs Trunk.
Modes: build (prod, default) / dev (mock server :8080) / selfhosted (/api).
build & selfhosted copy 404.html + standalone/ into dist/ so the output is
deploy-ready, matching .github/workflows/deploy.yml.
- .gitignore: ignore the machine-specific .claude/launch.json preview config.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* frontend: motion-harmony, dark-mode & a11y polish (review pass 1)
High-confidence findings from a multi-dimensional, adversarially-verified
polish review:
motion (the disharmony the user dislikes):
- Fluent buttons: hover background/color now ease with the scale+shadow instead
of snapping (one shared --motion-fast/--ease-snap transition).
- .btn-terminal press settles down from the hover lift (translateY(0) scale.97)
and uses motion tokens instead of `all .15s ease`.
- Admin tabs: active underline grows in with a spring (always-present ::after,
scaleX 0->1) instead of popping into existence.
- system-panel-compact stat tiles join the token motion vocabulary (2px lift,
snap/spring) shared by the article/stats cards on the same screen.
- Pagination pills gain a tactile lift/press and the --motion-fast duration.
dark mode:
- Global a:hover was a hardcoded dark blue (#0b5f93) that went low-contrast in
dark mode -> var(--primary).
- Pagination disabled-hover dropped its near-black hardcoded glyph color that
vanished on dark surfaces.
- Fixed a dangling text-[var(--ink)] (undefined token) on the prompt modal.
consistency:
- Harmonized two off-palette blueviolet glows to the brand iris (#8b5cf6).
- Stat-card radius unified to --radius-lg (was a one-off 10px).
- posts.rs tag chips match the canonical rounded-full pills.
- Music wish card gains the restrained hover its sibling cards have.
a11y / touch:
- Decorative Icon SVGs marked aria-hidden / focusable=false.
- Mobile header search/clear buttons get aria-labels (desktop already had them).
- Music hero search button hits 44px on mobile; icon/pagination controls relax
to the --hit-size token on touch (hover:none) devices.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* frontend: polish review pass 2 — motion, states, a11y across the app
Implements the remaining verified findings from the multi-dimensional polish
review (shared/component files done directly; the per-page state work fanned
out across 8 parallel agents).
motion:
- Scroll-to-top FAB stays mounted and fades + scales in/out instead of
hard-cutting at the scroll threshold.
- Theme toggle cross-fades + rotates a stacked sun/moon instead of hard-swapping
the glyph (on-token --motion-base/--ease-spring).
- Toasts now play a spring exit (two-phase StartLeave -> animate -> Dismiss)
to mirror their entrance instead of vanishing instantly.
- Editorial article cards join the shared token lift (2px, snap/spring) used by
the stats/system cards on the same screen.
states & feedback:
- The six core list views (posts, categories, tags, category/tag detail, the
main latest grid) gain a real error state with Retry — a failed fetch no
longer masquerades as an empty collection.
- Music Library loading uses skeleton song cards (SkeletonSongCard) instead of a
bare spinner, removing the layout pop.
- Music search surfaces backend errors instead of a phantom "no results".
- The article-request form is now toast-only (matching the wish form), dropping
the dead inline success path.
a11y:
- Icon-only buttons get accessible names (IconButton/TooltipIconButton thread an
aria_label, defaulting from the tooltip), fixing the scroll-top / TOC / close
FABs.
- Admin tab bar moves real DOM focus on Arrow/Home/End (roving tabindex).
- Desktop nav icon links get sr-only names; the glyphs are aria-hidden.
dark mode & responsive:
- Interactive-alert banner gets a dark variant (was a cream light-island).
- Terminal hero relaxes its padding and indent at phone widths (was cramped at
320px).
- C3: posts cards use the canonical text-base summary.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* frontend: mobile menu polish — sticky close + live search suggestions
Final two findings from the polish review:
- R5: the mobile menu close button is now a sticky acrylic strip that stays
reachable while the menu scrolls (it used to scroll away on short / landscape
phones); the 4.5rem dead-zone top padding is gone.
- R3: the mobile menu search now uses the shared SearchSuggest component (live
results, skeleton, keyboard nav) instead of a bare input. Its panel drops into
normal flow on mobile (.mobile-search-suggest) so the menu's own scroll area
reveals it rather than clipping an absolutely-positioned dropdown. The
now-redundant mobile search/keypress/submit callbacks and their imports are
removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>1 parent c85d203 commit feaa2f7
41 files changed
Lines changed: 3440 additions & 1727 deletions
File tree
- crates/frontend
- src
- components
- i18n
- pages
- scripts
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
21 | 24 | | |
22 | 25 | | |
23 | 26 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
| 17 | + | |
16 | 18 | | |
17 | 19 | | |
18 | 20 | | |
| |||
25 | 27 | | |
26 | 28 | | |
27 | 29 | | |
28 | | - | |
| 30 | + | |
| 31 | + | |
29 | 32 | | |
30 | 33 | | |
31 | 34 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
265 | 265 | | |
266 | 266 | | |
267 | 267 | | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
277 | 268 | | |
278 | 269 | | |
279 | 270 | | |
| |||
810 | 801 | | |
811 | 802 | | |
812 | 803 | | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
813 | 816 | | |
814 | 817 | | |
815 | 818 | | |
816 | 819 | | |
817 | 820 | | |
| 821 | + | |
818 | 822 | | |
819 | 823 | | |
820 | 824 | | |
| |||
0 commit comments