Skip to content

Commit feaa2f7

Browse files
acking-youclaude
andauthored
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

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ perf.data.old
1818

1919
# IDE 配置
2020
.vscode/
21+
22+
# 本机预览/调试配置(含机器专属路径,不入库)
23+
.claude/launch.json
2124
.idea/
2225
*.swp
2326
*.swo

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/frontend/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ workspace = true
1111
# 使用 workspace 级别的依赖版本
1212
yew = { workspace = true }
1313
yew-router = { workspace = true }
14+
gloo-events = "0.2"
1415
gloo-net = { workspace = true }
1516
gloo-timers = "0.3"
17+
gloo-utils = "0.2"
1618
serde = { workspace = true }
1719
serde_json = { workspace = true }
1820
wasm-bindgen = { workspace = true }
@@ -25,7 +27,8 @@ web-sys = { workspace = true, features = [
2527
"DomTokenList", "DomRect", "Navigator", "HtmlTextAreaElement", "Selection", "Range", "Node", "HtmlButtonElement",
2628
"Event", "MessageEvent", "EventSource", "HtmlAudioElement", "MediaError", "HtmlInputElement",
2729
"ClipboardEvent", "DataTransfer",
28-
"TimeRanges", "File", "Blob", "FileList", "FormData"
30+
"TimeRanges", "File", "Blob", "FileList", "FormData",
31+
"IntersectionObserver", "IntersectionObserverEntry", "IntersectionObserverInit"
2932
] }
3033
js-sys = { workspace = true }
3134
pulldown-cmark = { workspace = true }

crates/frontend/index.html

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -265,15 +265,6 @@
265265
</head>
266266

267267
<body>
268-
<svg width="0" height="0" style="position:absolute;">
269-
<defs>
270-
<filter id="wave-filter">
271-
<feTurbulence type="fractalNoise" baseFrequency="0.01" numOctaves="1" result="noise" />
272-
<feDisplacementMap in="SourceGraphic" in2="noise" scale="5" xChannelSelector="R" yChannelSelector="G" />
273-
</filter>
274-
</defs>
275-
</svg>
276-
<div class="spotlight" id="global-spotlight" aria-hidden="true"></div>
277268
<!-- WASM app will render here -->
278269
<noscript>本应用需要启用 JavaScript。</noscript>
279270

@@ -810,11 +801,24 @@
810801
pre.parentNode.insertBefore(wrapper, pre);
811802
wrapper.appendChild(pre);
812803

804+
// Language label (from highlight.js `language-xxx` class)
805+
const codeEl = pre.querySelector('code');
806+
const langClass = codeEl && [...codeEl.classList].find((c) => c.startsWith('language-'));
807+
const lang = langClass ? langClass.slice('language-'.length) : '';
808+
if (lang && lang !== 'undefined' && lang !== 'plaintext') {
809+
const langLabel = document.createElement('span');
810+
langLabel.className = 'code-lang-label';
811+
langLabel.textContent = lang;
812+
langLabel.setAttribute('aria-hidden', 'true');
813+
wrapper.appendChild(langLabel);
814+
}
815+
813816
// Create copy button
814817
const copyBtn = document.createElement('button');
815818
copyBtn.className = 'copy-button';
816819
copyBtn.innerHTML = '<i class="far fa-copy"></i>';
817820
copyBtn.title = '复制代码';
821+
copyBtn.setAttribute('aria-label', '复制代码');
818822

819823
copyBtn.addEventListener('click', async () => {
820824
const code = pre.querySelector('code');

0 commit comments

Comments
 (0)