From 467a218d00fad34bd10d28f94a81fc2397bbb9e2 Mon Sep 17 00:00:00 2001 From: Roman Timashev Date: Sat, 25 Apr 2026 03:25:15 +0300 Subject: [PATCH] move to biome (#297) * switch to biome * writer format * migrate format * packages format * hudozka format * writer fix * biome config updated * migrate fix * packages fix * hudozka fix * claude updated --- .github/workflows/test-app.yml | 2 +- CLAUDE.md | 6 +- apps/hudozka-writer/src/App.tsx | 36 +- .../src/components/BlockActions.tsx | 25 +- .../src/components/BlockInsert.tsx | 14 +- .../src/components/BlockMenu.tsx | 36 +- .../src/components/DraftBanner.tsx | 2 +- apps/hudozka-writer/src/components/Editor.tsx | 40 +- .../src/components/FilePicker.tsx | 81 +- .../src/components/ImagePicker.tsx | 81 +- apps/hudozka-writer/src/components/Login.tsx | 6 +- .../hudozka-writer/src/components/Toolbar.tsx | 20 +- .../src/extensions/SlashCommands.tsx | 51 +- apps/hudozka-writer/src/lib/markdown.ts | 7 +- apps/hudozka-writer/src/lib/serialize.ts | 134 +- apps/hudozka-writer/src/main.tsx | 5 +- .../src/nodes/CardGridBlock.tsx | 26 +- .../src/nodes/DocumentBlock.tsx | 49 +- apps/hudozka-writer/src/nodes/EmbedBlock.tsx | 4 +- apps/hudozka-writer/src/nodes/ImageBlock.tsx | 61 +- apps/hudozka-writer/src/nodes/TextBlock.tsx | 4 +- apps/hudozka-writer/src/nodes/index.ts | 8 +- apps/hudozka-writer/src/types.ts | 7 +- apps/hudozka/package.json | 4 +- apps/hudozka/src/app/[...slug]/page.tsx | 6 +- apps/hudozka/src/app/feed.json/route.ts | 2 +- apps/hudozka/src/app/layout.tsx | 9 +- apps/hudozka/src/app/page.tsx | 7 +- apps/hudozka/src/app/sitemap.ts | 2 +- .../components/AccessibilityButton/index.tsx | 14 +- .../components/AccessibilityPanel/index.tsx | 207 +- .../AccessibilityPanel/panel.module.css | 33 +- .../components/AccessibilityPanel/voice.tsx | 14 +- apps/hudozka/src/components/App/index.tsx | 62 +- apps/hudozka/src/components/Author/index.tsx | 6 +- apps/hudozka/src/components/Card/index.tsx | 33 +- .../hudozka/src/components/Contacts/index.tsx | 14 +- apps/hudozka/src/components/Content/index.tsx | 2 +- .../src/components/Copyright/index.tsx | 4 +- apps/hudozka/src/components/File/index.tsx | 12 +- .../src/components/FileCard/Signature.tsx | 8 +- .../hudozka/src/components/FileCard/index.tsx | 25 +- apps/hudozka/src/components/Footer/index.tsx | 5 +- .../src/components/Gosuslugi/index.tsx | 11 +- .../src/components/HomeContent/index.tsx | 2 +- .../src/components/HudozkaLogo/index.tsx | 10 +- .../src/components/HudozkaTitle/index.tsx | 9 +- .../src/components/Menu/ActiveLink.tsx | 8 +- apps/hudozka/src/components/Menu/index.tsx | 49 +- .../Navigation/DesktopNavigation.tsx | 12 +- .../Navigation/MobileNavigation.tsx | 22 +- .../src/components/Navigation/index.tsx | 2 +- .../hudozka/src/components/NotFound/index.tsx | 8 +- apps/hudozka/src/components/Page/index.tsx | 4 +- .../src/components/PageContent/index.tsx | 97 +- apps/hudozka/src/components/PageGrid/Date.tsx | 18 +- .../hudozka/src/components/PageGrid/index.tsx | 51 +- .../hudozka/src/components/PageMeta/index.tsx | 33 +- apps/hudozka/src/components/Spacer/index.tsx | 11 +- apps/hudozka/src/components/TagList/index.tsx | 16 +- apps/hudozka/src/components/Wrapper/index.tsx | 14 +- apps/hudozka/src/components/YMetrika.test.ts | 2 +- apps/hudozka/src/components/Youtube/index.tsx | 8 +- apps/hudozka/src/hooks/useAccessibility.ts | 2 +- apps/hudozka/src/lib/meta.test.ts | 15 +- apps/hudozka/src/play.ts | 5 +- apps/hudozka/src/remote/api.test.ts | 25 +- apps/hudozka/src/remote/api.ts | 49 +- apps/hudozka/src/remote/doc.ts | 7 +- apps/hudozka/src/remote/factory.test.ts | 23 +- apps/hudozka/src/remote/factory.ts | 164 +- apps/hudozka/src/store/theme.ts | 15 +- apps/hudozka/src/types.ts | 17 +- apps/hudozka/src/ui/Picture/index.tsx | 57 +- apps/hudozka/src/ui/ThemeColor/index.tsx | 7 +- apps/migrate-to-pocketbase/src/blurhash.ts | 7 +- apps/migrate-to-pocketbase/src/index.ts | 184 +- apps/migrate-to-pocketbase/src/strapi.ts | 118 +- apps/migrate-to-pocketbase/src/wipe.ts | 6 +- biome.json | 75 + eslint.config.js | 42 - package-lock.json | 4736 +---------------- package.json | 15 +- packages/hooks/src/index.ts | 2 +- packages/hooks/src/useLockBodyScroll.test.ts | 7 +- packages/hooks/src/useMediaQuery.test.ts | 16 +- packages/hooks/src/useMediaQuery.ts | 2 +- packages/hooks/src/useToggle.test.ts | 4 +- packages/hooks/src/useToggle.ts | 2 +- packages/text/src/text.test.ts | 2 +- packages/ui/src/Box/Box.test.tsx | 14 +- packages/ui/src/Box/index.tsx | 3 +- .../ui/src/Breadcrumbs/Breadcrumbs.test.tsx | 22 +- packages/ui/src/Breadcrumbs/index.tsx | 37 +- packages/ui/src/Button/Button.test.tsx | 14 +- packages/ui/src/Button/index.tsx | 26 +- packages/ui/src/Overlay/Transition.tsx | 5 +- packages/ui/src/Overlay/index.tsx | 17 +- packages/ui/src/Panel/Panel.test.tsx | 11 +- packages/ui/src/Panel/index.tsx | 17 +- packages/ui/src/index.ts | 2 +- packages/utils/src/array.test.ts | 8 +- packages/utils/src/array.ts | 1 - packages/utils/src/date.test.ts | 2 +- packages/utils/src/file.test.ts | 2 +- packages/utils/src/file.ts | 8 +- packages/utils/src/image.test.ts | 10 +- packages/utils/src/index.ts | 8 +- packages/utils/src/math.test.ts | 2 +- packages/utils/src/string.test.ts | 2 +- packages/utils/src/url.test.ts | 6 +- packages/utils/src/url.ts | 4 +- 112 files changed, 1472 insertions(+), 5914 deletions(-) create mode 100644 biome.json delete mode 100644 eslint.config.js diff --git a/.github/workflows/test-app.yml b/.github/workflows/test-app.yml index af4773cd..20f8bd78 100644 --- a/.github/workflows/test-app.yml +++ b/.github/workflows/test-app.yml @@ -21,7 +21,7 @@ jobs: run: npm ci - name: Lint - run: npm run lint + run: npx biome ci test: name: Test diff --git a/CLAUDE.md b/CLAUDE.md index 7b0641d6..8455e504 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,8 +12,8 @@ From repo root (delegates to `apps/hudozka`): - `npm run dev` — dev server with Turbo (`next dev --turbo`) - `npm run build` — production build -- `npm run lint` — ESLint across the whole monorepo -- `npm run format` — auto-fix lint issues +- `npm run lint` — Biome lint + format check across the whole monorepo (`biome check`) +- `npm run format` — auto-fix lint and format issues (`biome check --write`) - `npm test` — run unit tests (Vitest) across every workspace Per-app: @@ -35,7 +35,7 @@ Feature branches are named `issue-XXX` where `XXX` is the related GitHub issue n ## Code Style -ESLint 9 flat config (`eslint.config.js`) with native `eslint-config-next` 16.x support. Extends `next/core-web-vitals`. Enforces: no semicolons, double quotes, 4-space indentation, always-multiline trailing commas, `eol-last`. +Biome (`biome.json`) handles formatting and linting. Style: no semicolons, double quotes, 4-space indentation, always-multiline trailing commas, LF line endings, 120-column line width. Lint scope is limited to `**/src/**/*.{ts,tsx,js}` via `files.includes`. Console calls are restricted to `console.error`/`console.warn` (full `console.*` is allowed only inside `apps/migrate-to-pocketbase`). `noDangerouslySetInnerHtml` is disabled because the site renders trusted CMS HTML. Inline `biome-ignore` / `eslint-disable` comments are not used — fix the underlying issue instead. **React components:** Use regular function declarations (not arrow functions) and `type` for props. Do not use `React.FC`. diff --git a/apps/hudozka-writer/src/App.tsx b/apps/hudozka-writer/src/App.tsx index c5b53d71..b34cdc18 100644 --- a/apps/hudozka-writer/src/App.tsx +++ b/apps/hudozka-writer/src/App.tsx @@ -1,7 +1,7 @@ -import { useState, useEffect, useCallback } from "react" -import { pb } from "./pb" -import { Login } from "./components/Login" +import { useCallback, useEffect, useState } from "react" import { Editor } from "./components/Editor" +import { Login } from "./components/Login" +import { pb } from "./pb" import type { PbPage } from "./types" import "./App.css" @@ -22,9 +22,7 @@ export function App() { setLoading(true) setError("") try { - const record = await pb.collection("pages").getFirstListItem( - `slug="${slug}"`, - ) + const record = await pb.collection("pages").getFirstListItem(`slug="${slug}"`) setPage(record) } catch { setError(`Page "${slug}" not found`) @@ -87,8 +85,12 @@ export function App() { if (!slug) { return (
-

Add ?page=slug to the URL to edit a page.

- +

+ Add ?page=slug to the URL to edit a page. +

+
) } @@ -101,7 +103,9 @@ export function App() { return (

{error}

- +
) } @@ -116,15 +120,15 @@ export function App() { {page.title} /{page.slug} - - + +
diff --git a/apps/hudozka-writer/src/components/BlockActions.tsx b/apps/hudozka-writer/src/components/BlockActions.tsx index 8fdeac43..b02b9442 100644 --- a/apps/hudozka-writer/src/components/BlockActions.tsx +++ b/apps/hudozka-writer/src/components/BlockActions.tsx @@ -1,6 +1,6 @@ -import { useEffect, useCallback } from "react" -import type { Editor } from "@tiptap/react" import { Fragment } from "@tiptap/pm/model" +import type { Editor } from "@tiptap/react" +import { useCallback, useEffect } from "react" import "./BlockActions.css" export type BlockActionsProps = { @@ -68,10 +68,14 @@ export function BlockActions({ editor, getPos, showSplit }: BlockActionsProps) { if (typeof pos !== "number") return const node = editor.state.doc.nodeAt(pos) if (!node) return - editor.chain().focus().deleteRange({ - from: pos, - to: pos + node.nodeSize, - }).run() + editor + .chain() + .focus() + .deleteRange({ + from: pos, + to: pos + node.nodeSize, + }) + .run() } const handleSplit = (e: React.MouseEvent) => { @@ -120,6 +124,7 @@ export function BlockActions({ editor, getPos, showSplit }: BlockActionsProps) { return (
{menuOpen && (
- +
)}
diff --git a/apps/hudozka-writer/src/components/BlockMenu.tsx b/apps/hudozka-writer/src/components/BlockMenu.tsx index ba716801..61d2b73e 100644 --- a/apps/hudozka-writer/src/components/BlockMenu.tsx +++ b/apps/hudozka-writer/src/components/BlockMenu.tsx @@ -25,21 +25,29 @@ export type BlockMenuProps = { export function BlockMenu({ editor, position, onClose, filter }: BlockMenuProps) { const filtered = filter - ? BLOCK_TYPES.filter((b) => b.label.toLowerCase().includes(filter.toLowerCase())) + ? BLOCK_TYPES.filter(b => b.label.toLowerCase().includes(filter.toLowerCase())) : BLOCK_TYPES function handleInsert(block: BlockType) { const id = generateBlockId() if (block.type === "textBlock") { - editor.chain().focus().insertContentAt(position, { - type: "textBlock", - content: [{ type: "paragraph" }], - }).run() + editor + .chain() + .focus() + .insertContentAt(position, { + type: "textBlock", + content: [{ type: "paragraph" }], + }) + .run() } else { - editor.chain().focus().insertContentAt(position, { - type: block.type, - attrs: { ...block.attrs, id }, - }).run() + editor + .chain() + .focus() + .insertContentAt(position, { + type: block.type, + attrs: { ...block.attrs, id }, + }) + .run() } onClose() } @@ -48,12 +56,8 @@ export function BlockMenu({ editor, position, onClose, filter }: BlockMenuProps) return (
- {filtered.map((block) => ( - ))} @@ -61,5 +65,5 @@ export function BlockMenu({ editor, position, onClose, filter }: BlockMenuProps) ) } -export { BLOCK_TYPES } export type { BlockType } +export { BLOCK_TYPES } diff --git a/apps/hudozka-writer/src/components/DraftBanner.tsx b/apps/hudozka-writer/src/components/DraftBanner.tsx index c1674d5e..868b0083 100644 --- a/apps/hudozka-writer/src/components/DraftBanner.tsx +++ b/apps/hudozka-writer/src/components/DraftBanner.tsx @@ -8,7 +8,7 @@ export function DraftBanner({ onDiscard }: DraftBannerProps) { return (
You have unpublished changes -
diff --git a/apps/hudozka-writer/src/components/Editor.tsx b/apps/hudozka-writer/src/components/Editor.tsx index 1f846ff0..b6473f88 100644 --- a/apps/hudozka-writer/src/components/Editor.tsx +++ b/apps/hudozka-writer/src/components/Editor.tsx @@ -1,24 +1,17 @@ -import { useEditor, EditorContent } from "@tiptap/react" -import StarterKit from "@tiptap/starter-kit" -import { Markdown } from "@tiptap/markdown" import Link from "@tiptap/extension-link" -import { useState, useCallback, useRef, useEffect } from "react" -import { pb } from "../pb" -import { docToTiptap, tiptapToDoc } from "../lib/serialize" -import { Toolbar } from "./Toolbar" -import { DraftBanner } from "./DraftBanner" -import { - WriterDocument, - TextBlock, - ImageBlock, - DocumentBlock, - EmbedBlock, - CardGridBlock, -} from "../nodes" +import { Markdown } from "@tiptap/markdown" +import { EditorContent, useEditor } from "@tiptap/react" +import StarterKit from "@tiptap/starter-kit" +import { useCallback, useRef, useState } from "react" import { SlashCommands } from "../extensions/SlashCommands" +import { docToTiptap, tiptapToDoc } from "../lib/serialize" +import { CardGridBlock, DocumentBlock, EmbedBlock, ImageBlock, TextBlock, WriterDocument } from "../nodes" +import { pb } from "../pb" +import { DraftBanner } from "./DraftBanner" +import { Toolbar } from "./Toolbar" import "../nodes/nodes.css" import "./Editor.css" -import type { PbPage, DocV1 } from "../types" +import type { DocV1, PbPage } from "../types" function serializeDoc(doc: DocV1): string { return JSON.stringify(doc) @@ -37,15 +30,10 @@ export function Editor({ page }: EditorProps) { const [hasDraft, setHasDraft] = useState(page.draft !== null) const [publishedDoc, setPublishedDoc] = useState(page.doc) - const snapshotRef = useRef("") - const initialDoc = page.draft ?? page.doc const tiptapDoc = docToTiptap(initialDoc) - // Set initial snapshot - useEffect(() => { - snapshotRef.current = serializeDoc(initialDoc) - }, []) // eslint-disable-line react-hooks/exhaustive-deps + const snapshotRef = useRef(serializeDoc(initialDoc)) const editor = useEditor({ extensions: [ @@ -157,14 +145,12 @@ export function Editor({ page }: EditorProps) { markdownMode={markdownMode} onToggleMarkdown={handleToggleMarkdown} /> - {hasDraft && ( - - )} + {hasDraft && } {markdownMode ? (