Conversation
… adjust Logo layout
There was a problem hiding this comment.
Pull request overview
Refactors the frontend header from legacy HTML/DOM-driven code to SolidJS components, introducing new signals/hooks to drive header UI state (account spinner, notifications, XP bar) from application logic.
Changes:
- Replace
<load src="html/header.html" />with a Solid-mounted<Header />and add new header/layout components. - Introduce signal-based state for header elements (notification bubble, account spinner, XP bar events) and wire parts of test/config/db flows into them.
- Add common UI primitives (e.g.,
Bar, new anime helpers) used by the new header and profile UI.
Reviewed changes
Copilot reviewed 51 out of 52 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Lockfile update for new frontend dependency (motion). |
| frontend/package.json | Adds motion dependency. |
| packages/oxlint-config/rules/jsx.jsonc | Allows router-link JSX attribute in lint config. |
| frontend/src/index.html | Mounts Solid header instead of loading legacy header HTML. |
| frontend/src/html/header.html | Partially comments out legacy nav links (legacy header content). |
| frontend/src/styles/nav.scss | Comments out legacy header/nav SCSS (migration away from old DOM structure). |
| frontend/src/styles/media-queries-purple.scss | Comments out legacy header/nav responsive overrides. |
| frontend/src/styles/media-queries-green.scss | Comments out legacy header/nav responsive overrides. |
| frontend/src/styles/media-queries-gray.scss | Comments out legacy header/nav responsive overrides. |
| frontend/src/styles/media-queries-brown.scss | Comments out legacy header/nav responsive overrides. |
| frontend/src/styles/media-queries-blue.scss | Comments out legacy header/nav responsive overrides. |
| frontend/src/ts/utils/levels.ts | Extends XP detail calculations with float/progress percent fields. |
| frontend/src/ts/test/test-ui.ts | Adds signal-based XP breakdown skipping (but retains legacy call). |
| frontend/src/ts/test/test-logic.ts | Shifts account spinner to header signal; stops using legacy XPBar update path. |
| frontend/src/ts/test/focus.ts | Removes header from focus-class toggling selector list. |
| frontend/src/ts/stores/snapshot.ts | Exports MiniSnapshot; unwraps Solid stores before cloning/reconciling. |
| frontend/src/ts/signals/xp-bar.ts | New XP bar data + skip event signals. |
| frontend/src/ts/signals/user.ts | New Solid store for authenticated user state. |
| frontend/src/ts/signals/loader-bar.ts | Refactors loader bar signal using createSignalWithSetters. |
| frontend/src/ts/signals/header.ts | New header signals (notification bubble + account spinner). |
| frontend/src/ts/signals/animated-level.ts | New signal for animated level display. |
| frontend/src/ts/hooks/useSwapAnimation.ts | New hook for swapping visibility with animations. |
| frontend/src/ts/hooks/useRefWithUtils.ts | Refactors ref hook to use a signal instead of a mutable local. |
| frontend/src/ts/hooks/createSignalWithSetters.ts | New helper to create signals with mapped setter functions. |
| frontend/src/ts/hooks/createEvent.ts | New “event signal” helper (counter-based). |
| frontend/src/ts/elements/alerts.ts | Uses header notification bubble signal; renames show to showAlerts. |
| frontend/src/ts/elements/account-button.ts | No-ops legacy account button DOM manipulation (migration). |
| frontend/src/ts/db.ts | Emits XP bar signal data from snapshot updates / XP changes. |
| frontend/src/ts/config.ts | Drives account spinner signal during config saves. |
| frontend/src/ts/components/utils/Transition.tsx | Adds placeholder Transition component. |
| frontend/src/ts/components/pages/profile/UserDetails.tsx | Uses new Bar component for level progress UI. |
| frontend/src/ts/components/pages/AboutPage.tsx | Comments out hover prefetch wiring (legacy DOM behavior). |
| frontend/src/ts/components/mount.tsx | Registers new header mount target. |
| frontend/src/ts/components/modals/DevOptionsModal.tsx | Updates XP bar dev test to use db.addXp signal path. |
| frontend/src/ts/components/layout/header/Header.tsx | New Solid header wrapper. |
| frontend/src/ts/components/layout/header/Logo.tsx | New Solid logo component with router-link integration. |
| frontend/src/ts/components/layout/header/Nav.tsx | New Solid nav (alerts, account menu, XP bar). |
| frontend/src/ts/components/layout/header/AccountMenu.tsx | New Solid account dropdown. |
| frontend/src/ts/components/layout/header/AccountXpBar.tsx | New Solid XP bar animation + breakdown UI. |
| frontend/src/ts/components/common/anime/index.ts | Updates anime exports to new helper components. |
| frontend/src/ts/components/common/anime/AnimeShow.tsx | Renames + tweaks default duration and adds class passthrough. |
| frontend/src/ts/components/common/anime/AnimeSwitch.tsx | Adds animated Switch wrapper with shared defaults. |
| frontend/src/ts/components/common/anime/AnimeMatch.tsx | Adds Match wrapper that consumes AnimeSwitch defaults. |
| frontend/src/ts/components/common/anime/AnimeConditional.tsx | Adds convenience animated if/then/else wrapper. |
| frontend/src/ts/components/common/anime/AnimeGroupTest.tsx | Expands doc comment for test/demo component. |
| frontend/src/ts/components/common/anime/Anime.tsx | Adds ref passthrough to underlying DOM element. |
| frontend/src/ts/components/common/UserFlags.tsx | Adds optional class passthrough for flag icons/badges. |
| frontend/src/ts/components/common/User.tsx | Refactors user display; adds animated level + spinner/avatar swap. |
| frontend/src/ts/components/common/NotificationBubble.tsx | New notification bubble component. |
| frontend/src/ts/components/common/DiscordAvatar.tsx | Adds configurable fallback icon rendering. |
| frontend/src/ts/components/common/Button.tsx | Adds mouse enter/leave props; relaxes onClick typing (anchor/button). |
| frontend/src/ts/components/common/Bar.tsx | New bar primitive used by profile + header XP UI. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (2)
frontend/src/ts/components/common/Button.tsx:104
BasePropsnow allowsonClickeven when rendering an anchor (hrefpresent), but the<a>branch never attachesonClick. This breaks callers like the new header nav (start-test button passeshref+onClickto restart). Either wireprops.onClickonto the<a>element or reintroduce the type restriction soonClickcan’t be provided withhref.
type BaseProps = {
text?: string;
fa?: FaProps;
class?: string;
classList?: JSX.HTMLAttributes<HTMLButtonElement>["classList"];
type?: "text" | "button";
children?: JSXElement;
ariaLabel?:
| string
| { text: string; position: "up" | "down" | "left" | "right" };
"router-link"?: true;
onClick?: () => void;
onMouseEnter?: () => void;
onMouseLeave?: () => void;
};
type ButtonProps = BaseProps & {
href?: never;
sameTarget?: true;
active?: boolean;
disabled?: boolean;
};
type AnchorProps = BaseProps & {
href: string;
// onClick?: never;
disabled?: never;
};
export function Button(props: ButtonProps | AnchorProps): JSXElement {
const isAnchor = "href" in props;
const isActive = (): boolean => (!isAnchor && props.active) ?? false;
const content = (
<>
<Show when={props.fa !== undefined}>
<Fa {...(props.fa as FaProps)} />
</Show>
<Show when={props.text !== undefined}>{props.text}</Show>
{props.children}
</>
);
const ariaLabel = (): object => {
if (props.ariaLabel === undefined) return {};
if (typeof props.ariaLabel === "string") {
return { "aria-label": props.ariaLabel, "data-balloon-pos": "up" };
}
return {
"aria-label": props.ariaLabel.text,
"data-balloon-pos": props.ariaLabel.position,
};
};
const getClasses = (): string => {
return cn(
"inline-flex h-min cursor-pointer appearance-none items-center justify-center gap-[0.5em] rounded border-0 p-[0.5em] text-center leading-[1.25] text-text transition-[color,background,opacity] duration-125 ease-in-out select-none",
"focus-visible:shadow-[0_0_0_0.1rem_var(--bg-color),_0_0_0_0.2rem_var(--text-color)] focus-visible:outline-none",
{
"bg-sub-alt hover:bg-text hover:text-bg": props.type !== "text",
"bg-transparent text-sub hover:text-text": props.type === "text",
[props.class ?? ""]: props.class !== undefined,
"bg-main text-bg hover:bg-text": isActive(),
...props.classList,
},
{
"opacity-[0.33]": props.disabled,
"bg-text text-bg": isActive() && props.disabled,
},
);
};
return (
<Conditional
if={isAnchor}
then={
<a
class={getClasses()}
href={props.href}
target={
props["router-link"] || props.href?.startsWith("#")
? undefined
: "_blank"
}
rel={
props["router-link"] || props.href?.startsWith("#")
? undefined
: "noreferrer noopener"
}
{...ariaLabel()}
{...(props["router-link"] ? { "router-link": "" } : {})}
onMouseEnter={() => props.onMouseEnter?.()}
onMouseLeave={() => props.onMouseLeave?.()}
>
{content}
</a>
}
frontend/src/ts/components/common/anime/AnimeShow.tsx:15
- Doc mismatch: the comment says
durationdefault is 250ms, but the implementation defaults to 125ms. Update the doc to match the new default (or revert the default) so consumers aren’t misled.
|
Continuous integration check(s) failed. Please review the failing check's logs and make the necessary changes. |
|
Continuous integration check(s) failed. Please review the failing check's logs and make the necessary changes. |
| <a | ||
| class={getClasses()} | ||
| href={props.href} | ||
| target={ | ||
| props["router-link"] || props.href?.startsWith("#") | ||
| ? undefined | ||
| : "_blank" | ||
| } | ||
| rel={ | ||
| props["router-link"] || props.href?.startsWith("#") | ||
| ? undefined | ||
| : "noreferrer noopener" | ||
| } | ||
| {...ariaLabel()} | ||
| {...(props["router-link"] ? { "router-link": "" } : {})} | ||
| onClick={() => props.onClick?.()} | ||
| onMouseEnter={() => props.onMouseEnter?.()} | ||
| onMouseLeave={() => props.onMouseLeave?.()} | ||
| {...props.dataset} | ||
| > | ||
| {content} |
There was a problem hiding this comment.
Button now allows onClick in BaseProps, and callers (e.g. nav start button) pass onClick while also providing href (anchor mode). The anchor branch never binds onClick, so the handler is silently ignored (restart-on-click etc. breaks). Either wire onClick onto the <a> element too, or disallow onClick when href is present (restore the onClick?: never typing + remove from BaseProps).
| setAccountButtonSpinner(true); | ||
|
|
||
| const response = await Ape.results.add({ body: { result } }); | ||
|
|
||
| AccountButton.loading(false); | ||
| setAccountButtonSpinner(false); |
There was a problem hiding this comment.
setAccountButtonSpinner(false) runs only after await Ape.results.add(...) resolves. If the request throws/rejects (network error, etc.), the spinner will stay stuck on. Wrap the await in try { ... } finally { setAccountButtonSpinner(false) } (and consider handling thrown errors similarly to non-200 responses).
| @@ -11,17 +11,17 @@ import { | |||
| import { getConfig } from "../../signals/config"; | |||
| import { getActivePage } from "../../signals/core"; | |||
| import { showModal } from "../../stores/modals"; | |||
| import { qsr } from "../../utils/dom"; | |||
| // import { qsr } from "../../utils/dom"; | |||
| import { getNumberWithMagnitude } from "../../utils/numbers"; | |||
| import AsyncContent from "../common/AsyncContent"; | |||
| import { Button } from "../common/Button"; | |||
| import { ChartJs } from "../common/ChartJs"; | |||
| import { Fa } from "../common/Fa"; | |||
| import { H2, H3 } from "../common/Headers"; | |||
|
|
|||
| qsr("nav .view-about").on("mouseenter", () => { | |||
| prefetch(); | |||
| }); | |||
| // qsr("nav .view-about").on("mouseenter", () => { | |||
| // prefetch(); | |||
| // }); | |||
There was a problem hiding this comment.
This file now contains commented-out imports and a fully commented prefetch handler/function. If prefetch-on-hover is intentionally removed, delete the dead code; if it's still desired, re-enable it in a Solid-friendly way (e.g. attach onMouseEnter in the header/nav component and call queryClient.prefetchQuery(...)).
| it("saves to localstorage if nosave=false", async () => { | ||
| //GIVEN | ||
| replaceConfig({ numbers: false }); | ||
|
|
||
| //WHEN | ||
| Config.setConfig("numbers", true); | ||
|
|
||
| //THEN | ||
| //wait for debounce | ||
| await vi.advanceTimersByTimeAsync(2500); | ||
|
|
||
| //show loading | ||
| expect(accountButtonLoadingMock).toHaveBeenNthCalledWith(1, true); | ||
|
|
||
| //save | ||
| expect(dbSaveConfigMock).toHaveBeenCalledWith({ numbers: true }); | ||
|
|
||
| //hide loading | ||
| expect(accountButtonLoadingMock).toHaveBeenNthCalledWith(2, false); | ||
| }); |
There was a problem hiding this comment.
The config tests previously asserted the account spinner/loading toggling during debounced saves. That coverage was removed when migrating off AccountButton.loading(...). Consider replacing it with an assertion on the new mechanism (e.g. spy on setAccountButtonSpinner from signals/header) so regressions in spinner behavior are still caught.
No description provided.