From 28652738af94662f592fd056c7f8fee038e9f5e0 Mon Sep 17 00:00:00 2001 From: Miodec Date: Sat, 24 Jan 2026 16:02:36 +0100 Subject: [PATCH 1/3] chore: disable not recommended rule --- frontend/.oxlintrc-plugin.json | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/.oxlintrc-plugin.json b/frontend/.oxlintrc-plugin.json index b98ecdbae835..51398c3d461d 100644 --- a/frontend/.oxlintrc-plugin.json +++ b/frontend/.oxlintrc-plugin.json @@ -19,7 +19,6 @@ "solid/no-array-handlers": "error", "solid/no-destructure": "error", "solid/no-innerhtml": "error", - "solid/no-proxy-apis": "error", "solid/no-react-deps": "error", "solid/no-react-specific-props": "error", "solid/no-unknown-namespaces": "error", From 0bf1c739bc836b6616ace6f3c75706a3ce11acd9 Mon Sep 17 00:00:00 2001 From: Miodec Date: Sat, 24 Jan 2026 16:02:58 +0100 Subject: [PATCH 2/3] chore: add plugin support to oxlint checker --- frontend/vite-plugins/oxlint-checker.ts | 54 ++++++++++++++++++++----- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/frontend/vite-plugins/oxlint-checker.ts b/frontend/vite-plugins/oxlint-checker.ts index 20702b819414..877f4a70f647 100644 --- a/frontend/vite-plugins/oxlint-checker.ts +++ b/frontend/vite-plugins/oxlint-checker.ts @@ -7,6 +7,8 @@ export type OxlintCheckerOptions = { debounceDelay?: number; /** Run type-aware checks (slower but more thorough). @default true */ typeAware?: boolean; + /** Run plugin checks with custom config. @default true */ + plugin?: boolean; /** Show browser overlay with lint status. @default true */ overlay?: boolean; /** File extensions to watch for changes. @default ['.ts', '.tsx', '.js', '.jsx'] */ @@ -27,6 +29,7 @@ export function oxlintChecker(options: OxlintCheckerOptions = {}): Plugin { const { debounceDelay = 125, typeAware = true, + plugin = true, overlay = true, extensions = [".ts", ".tsx", ".js", ".jsx"], } = options; @@ -185,7 +188,35 @@ export function oxlintChecker(options: OxlintCheckerOptions = {}): Plugin { } } - // First pass clean - run type-aware check if enabled + // First pass clean - run plugin check if enabled + if (plugin) { + console.log("\x1b[36mRunning plugin checks...\x1b[0m"); + sendLintResult({ running: true }); + const pluginResult = await runLintProcess([ + "-c", + ".oxlintrc-plugin.json", + ]); + + // Check if we were superseded by a newer run + if (runId !== currentRunId) { + return; + } + + if (pluginResult.output) { + console.log(pluginResult.output); + } + + // If plugin check had errors, send them and return (fail fast) + if (pluginResult.code !== 0) { + const counts = parseLintOutput(pluginResult.output); + if (counts.errorCount > 0 || counts.warningCount > 0) { + sendLintResult({ ...counts, running: false }); + return; + } + } + } + + // Run type-aware check if enabled if (!typeAware) { sendLintResult({ errorCount: 0, warningCount: 0, running: false }); return; @@ -275,14 +306,19 @@ export function oxlintChecker(options: OxlintCheckerOptions = {}): Plugin { console.log("\n\x1b[1mRunning oxlint...\x1b[0m"); try { - const output = execSync( - "npx oxlint . && npx oxlint . --type-aware --type-check", - { - cwd: process.cwd(), - encoding: "utf-8", - env: { ...process.env, FORCE_COLOR: "3" }, - }, - ); + const commands = ["npx oxlint ."]; + if (plugin) { + commands.push("npx oxlint . -c .oxlintrc-plugin.json"); + } + if (typeAware) { + commands.push("npx oxlint . --type-aware --type-check"); + } + + const output = execSync(commands.join(" && "), { + cwd: process.cwd(), + encoding: "utf-8", + env: { ...process.env, FORCE_COLOR: "3" }, + }); if (output) { console.log(output); From b4e795da7d39bc65559064a1deea4c94f1d8be48 Mon Sep 17 00:00:00 2001 From: Jack Date: Sat, 24 Jan 2026 16:20:15 +0100 Subject: [PATCH 3/3] refactor: add fa component with typesafety (@miodec) (#7439) --- frontend/__tests__/components/Button.spec.tsx | 23 +- .../__tests__/components/ScrollToTop.spec.tsx | 2 +- .../src/ts/components/common/AsyncContent.tsx | 5 +- frontend/src/ts/components/common/Button.tsx | 17 +- frontend/src/ts/components/common/Fa.tsx | 28 + .../ts/components/layout/footer/Footer.tsx | 50 +- .../components/layout/footer/ScrollToTop.tsx | 3 +- .../layout/footer/ThemeIndicator.tsx | 7 +- .../layout/footer/VersionButton.tsx | 3 +- .../ts/components/layout/overlays/Banners.tsx | 3 +- .../components/layout/overlays/Overlays.tsx | 3 +- .../src/ts/components/modals/ContactModal.tsx | 36 +- .../src/ts/components/modals/SupportModal.tsx | 36 +- .../src/ts/components/pages/AboutPage.tsx | 51 +- frontend/src/ts/types/font-awesome.d.ts | 1638 +++++++++++++++++ pnpm-lock.yaml | 85 +- 16 files changed, 1834 insertions(+), 156 deletions(-) create mode 100644 frontend/src/ts/components/common/Fa.tsx create mode 100644 frontend/src/ts/types/font-awesome.d.ts diff --git a/frontend/__tests__/components/Button.spec.tsx b/frontend/__tests__/components/Button.spec.tsx index 7f18597dfbee..ba13e6ee0581 100644 --- a/frontend/__tests__/components/Button.spec.tsx +++ b/frontend/__tests__/components/Button.spec.tsx @@ -51,14 +51,16 @@ describe("Button component", () => { onClick={() => { // }} - icon="fa-test" + fa={{ + icon: "keyboard", + }} /> )); const icon = container.querySelector("i"); expect(icon).toBeTruthy(); - expect(icon?.className).toContain("icon"); - expect(icon?.className).toContain("fa-test"); + expect(icon?.className).toContain("fas"); + expect(icon?.className).toContain("fa-keyboard"); }); it("applies fa-fw class when text is missing", () => { @@ -67,7 +69,10 @@ describe("Button component", () => { onClick={() => { // }} - icon="fa-test" + fa={{ + icon: "keyboard", + fixedWidth: true, + }} /> )); @@ -81,9 +86,11 @@ describe("Button component", () => { onClick={() => { // }} - icon="fa-test" + fa={{ + fixedWidth: true, + icon: "keyboard", + }} text="Hello" - fixedWidthIcon /> )); @@ -97,7 +104,9 @@ describe("Button component", () => { onClick={() => { // }} - icon="fa-test" + fa={{ + icon: "keyboard", + }} text="Hello" /> )); diff --git a/frontend/__tests__/components/ScrollToTop.spec.tsx b/frontend/__tests__/components/ScrollToTop.spec.tsx index 27a46015f451..36021bfcc7ae 100644 --- a/frontend/__tests__/components/ScrollToTop.spec.tsx +++ b/frontend/__tests__/components/ScrollToTop.spec.tsx @@ -31,7 +31,7 @@ describe("ScrollToTop", () => { expect(container).toHaveClass("content-grid", "ScrollToTop"); expect(button).toHaveClass("breakout"); - expect(button).toContainHTML(``); + expect(button.querySelector("i")).toHaveClass("fas", "fa-angle-double-up"); }); it("renders invisible when scrollY is 0", () => { diff --git a/frontend/src/ts/components/common/AsyncContent.tsx b/frontend/src/ts/components/common/AsyncContent.tsx index e570a519ad4f..a36f82caa3fb 100644 --- a/frontend/src/ts/components/common/AsyncContent.tsx +++ b/frontend/src/ts/components/common/AsyncContent.tsx @@ -4,6 +4,7 @@ import * as Notifications from "../../elements/notifications"; import { createErrorMessage } from "../../utils/misc"; import { Conditional } from "./Conditional"; +import { Fa } from "./Fa"; export default function AsyncContent( props: { @@ -51,7 +52,7 @@ export default function AsyncContent( <>
- +
{p.children(value())} @@ -65,7 +66,7 @@ export default function AsyncContent( - + } > diff --git a/frontend/src/ts/components/common/Button.tsx b/frontend/src/ts/components/common/Button.tsx index 28c277170e3f..acd421c94a66 100644 --- a/frontend/src/ts/components/common/Button.tsx +++ b/frontend/src/ts/components/common/Button.tsx @@ -1,12 +1,11 @@ import { JSXElement, Show } from "solid-js"; import { Conditional } from "./Conditional"; +import { Fa, FaProps } from "./Fa"; type BaseProps = { text?: string; - icon?: string; - iconScale?: number; - fixedWidthIcon?: boolean; + fa?: FaProps; class?: string; type?: "text" | "button"; children?: JSXElement; @@ -29,16 +28,8 @@ export function Button(props: ButtonProps | AnchorProps): JSXElement { const content = ( <> - - + + {props.text} {props.children} diff --git a/frontend/src/ts/components/common/Fa.tsx b/frontend/src/ts/components/common/Fa.tsx new file mode 100644 index 000000000000..aa994ac26dfd --- /dev/null +++ b/frontend/src/ts/components/common/Fa.tsx @@ -0,0 +1,28 @@ +import { JSXElement } from "solid-js"; + +import { FaObject } from "../../types/font-awesome"; + +export type FaProps = { + fixedWidth?: boolean; + spin?: boolean; + size?: number; +} & FaObject; + +export function Fa(props: FaProps): JSXElement { + const variant = (): string => props.variant ?? "solid"; + return ( + + ); +} diff --git a/frontend/src/ts/components/layout/footer/Footer.tsx b/frontend/src/ts/components/layout/footer/Footer.tsx index d154f29ed578..fd6dbe21c6ba 100644 --- a/frontend/src/ts/components/layout/footer/Footer.tsx +++ b/frontend/src/ts/components/layout/footer/Footer.tsx @@ -28,58 +28,76 @@ export function Footer(): JSXElement { ); diff --git a/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx b/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx index 9294925e0764..ed4d13ff6098 100644 --- a/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx +++ b/frontend/src/ts/components/layout/footer/ThemeIndicator.tsx @@ -6,6 +6,7 @@ import * as DB from "../../../db"; import * as Notifications from "../../../elements/notifications"; import { isAuthenticated } from "../../../firebase"; import { getThemeIndicator } from "../../../signals/core"; +import { Fa } from "../../common/Fa"; export function ThemeIndicator(): JSXElement { const handleClick = (e: MouseEvent): void => { @@ -41,9 +42,11 @@ export function ThemeIndicator(): JSXElement { >
- +
+ +
- +
{getThemeIndicator().text}
diff --git a/frontend/src/ts/components/layout/footer/VersionButton.tsx b/frontend/src/ts/components/layout/footer/VersionButton.tsx index 46917c0ff35c..a335878452e0 100644 --- a/frontend/src/ts/components/layout/footer/VersionButton.tsx +++ b/frontend/src/ts/components/layout/footer/VersionButton.tsx @@ -6,6 +6,7 @@ import { lastSeenServerCompatibility } from "../../../ape/adapters/ts-rest-adapt import { getVersion } from "../../../signals/core"; import { showModal } from "../../../stores/modals"; import { isDevEnvironment } from "../../../utils/misc"; +import { Fa } from "../../common/Fa"; export function VersionButton(): JSXElement { const [indicatorVisible, setIndicatorVisible] = createSignal(true); @@ -45,7 +46,7 @@ export function VersionButton(): JSXElement { return ( } /> diff --git a/frontend/src/ts/components/layout/overlays/Overlays.tsx b/frontend/src/ts/components/layout/overlays/Overlays.tsx index f57b236c0c3f..0c66dd4f836b 100644 --- a/frontend/src/ts/components/layout/overlays/Overlays.tsx +++ b/frontend/src/ts/components/layout/overlays/Overlays.tsx @@ -3,6 +3,7 @@ import { JSXElement } from "solid-js"; import { getIsScreenshotting } from "../../../signals/core"; import { showModal } from "../../../stores/modals"; import { cn } from "../../../utils/cn"; +import { Fa } from "../../common/Fa"; import { ScrollToTop } from "../footer/ScrollToTop"; import { Banners } from "./Banners"; @@ -28,7 +29,7 @@ export function Overlays(): JSXElement { }} tabIndex="-1" > - + diff --git a/frontend/src/ts/components/modals/ContactModal.tsx b/frontend/src/ts/components/modals/ContactModal.tsx index 52601e075e10..6df442935430 100644 --- a/frontend/src/ts/components/modals/ContactModal.tsx +++ b/frontend/src/ts/components/modals/ContactModal.tsx @@ -22,48 +22,60 @@ export function ContactModal(): JSXElement {