From 9d9e69c865f946edb4728935dcabd3411fc5cf45 Mon Sep 17 00:00:00 2001 From: Syrnnik Date: Tue, 31 Mar 2026 22:46:19 +0300 Subject: [PATCH 1/7] docs: add AGENTS baseline rules --- AGENTS.md | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..04e7ef9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,181 @@ +# AGENTS.md - Agent Rules + +This is an autonomous coding agent in this repo. Goals: clean, readable code and minimal diffs + +## Non-negotiables + +- Minimal diff: change only what the task requires. No cleanup/refactor unless asked +- Match local style: keep existing patterns, naming, imports, and formatting in touched files +- No new dependencies or toolchain/config changes without approval (see Approval Gates) +- No new top-level directories under `src/` without approval +- This repository uses only this `AGENTS.md` file as the source of agent rules + +--- + +## Tech & basics + +- Stack: React + TypeScript + Vite + Chakra UI (v2). Package manager: npm +- Branches: + - `main` - production branch. Deployment runs from this branch via GitHub Actions + - `dev` - main development branch + - `` - feature branches created by the agent +- Commits: Conventional Commits +- Commit tags: + - `feat:` - new functionality + - `fix:` - bug fixes + - `refactor:` - code changes without behavior changes or bug fixes + - `chore:` - config/build system changes, dependency updates + - `test:` - anything related to tests + - `style:` - formatting-only changes without behavior changes + - `docs:` - documentation-only changes + - `ci:` - CI configuration changes + +--- + +## How to Publish Changes to GitHub + +1. Switch to `dev` +2. Pull the latest changes +3. Switch to a new branch with a semantic name that matches the task +4. Split changed files into logical commits and commit them +5. Push the branch to GitHub +6. Create a PR into `dev` +7. Assign the PR to the person who did the task (usually `@me`) +8. If the task is complete, add the `Ready To Merge` label (check that the label exists in the repo first) +9. Always provide the PR link +10. If the task is done, ask whether the PR can be merged + +### Branch protection rule + +- Direct commits/pushes/merges to `main` are forbidden +- `main` can be updated only via PR from `dev` + +--- + +## PR Description Format + +Use this template when creating a PR description: + +```md +## Summary +- +``` + +--- + +## Project structure (do not invent new layers) + +Top-level layers inside `src/`: + +- `app/` - application entry, providers, routes +- `components/` - reusable UI components (feature folders) +- `context/` - global React Context providers + hooks +- `core/` - non-React logic: `api/`, `config/`, `constants/`, `firebase/`, `utils/` +- `hooks/` - reusable logic hooks (no JSX) +- `pages/` - routed screens (feature folders) +- `styles/` - Chakra theme setup +- `types/` - shared TypeScript types + +### Structure rules + +- Put new code into existing layers and existing feature folders +- Do not create new top-level directories under `src/` without approval +- Components live as `.tsx` files inside existing feature folders; do not add barrel `index.ts` files unless that folder already uses them +- `core/` must not import from React/UI modules +- Page components should compose reusable logic from `components/`, `hooks/`, and `context/` + +--- + +## Import rules + +- Use TS path aliases for cross-layer imports: + - `app/*`, `pages/*`, `components/*`, `core/*`, `hooks/*`, `context/*`, `styles/*`, `types/*` +- Relative imports are OK within the same feature folder +- Avoid deep relative imports that cross top-level boundaries +- Do not mass-convert import styles; match the file's existing approach + +--- + +## Coding standards + +### Components + +- Arrow components with typed props: + + ```tsx + interface ExampleProps { + title: string + } + + export const Example: FC = (props) => { + const { title } = props + + return {title} + } + ``` + +- Use `FC` for components and `FCC` (from `types/fcc`) when a component accepts `children` +- For new components, accept a `props` param and destructure on the first line; if the file already destructures in the signature, keep that style +- Prefer Chakra props (`Flex`, `HStack`, `VStack` etc). Avoid custom CSS unless necessary for third-party integrations +- One primary component per file; small helper components may live in the same file + +### Hooks + +- Hook functions must start with `use` +- Keep hooks pure - no JSX +- No `any`. Prefer explicit types where helpful + +### Comments + +- Only for non-obvious intent +- Use: + - `// NOTE:` for rationale/design + - `// TODO:` for follow-ups + - `// FIX:` for known bugs + +--- + +## Approval Gates (only these require approval) + +The agent MUST stop and ask for approval before doing any of the following: + +### 1. Top-level structure changes + +- Create/move/rename any top-level directory under `src/` +- Large restructuring: moving/renaming across layers or changing more than 5 files in a structural move + +### 2. Dependencies + +- Add/remove dependencies +- Change versions in `package.json` (including `devDependencies`) +- Run upgrades that modify the dependency graph (e.g. `npm update`, `npm audit fix`) +- `package-lock.json` may change only as a result of `npm install` with unchanged versions + +### 3. Build/toolchain/global styling config + +- Modify `tsconfig*.json` +- Modify `vite.config.*` +- Modify Chakra theme system under `src/styles/**` beyond small task-required edits +- Modify TS path aliases or alias-related config + +Everything else (routes, contexts, pages, components, hooks) is autonomous + +--- + +## Verification (required) + +After changes, the agent MUST run: + +```bash +npm install +npm run lint +npm run build +``` + +--- + +## Working style + +- If a file already has a pattern, extend it instead of introducing a new one +- Avoid new abstractions/helpers unless reused at least twice within the same task +- Do not rename/move files unless the task explicitly requires it From a347368347cf12e9da896e736a08fc373698b436 Mon Sep 17 00:00:00 2001 From: Syrnnik Date: Tue, 31 Mar 2026 22:46:38 +0300 Subject: [PATCH 2/7] docs: align AGENTS with actual project structure --- AGENTS.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 04e7ef9..6cd9582 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,29 +67,31 @@ Use this template when creating a PR description: Top-level layers inside `src/`: -- `app/` - application entry, providers, routes -- `components/` - reusable UI components (feature folders) +- `api/` - API request functions +- `asset/` - static assets used by UI +- `component/` - reusable UI components +- `constant/` - shared constants and enums - `context/` - global React Context providers + hooks -- `core/` - non-React logic: `api/`, `config/`, `constants/`, `firebase/`, `utils/` -- `hooks/` - reusable logic hooks (no JSX) -- `pages/` - routed screens (feature folders) -- `styles/` - Chakra theme setup -- `types/` - shared TypeScript types +- `hook/` - reusable hooks (no JSX) +- `page/` - routed screens +- `service/` - orchestration/mutation logic over API layer +- `type/` - shared TypeScript types +- `util/` - shared helper functions +- `configuration.tsx`, `theme.ts`, `index.tsx` - app-level setup files ### Structure rules - Put new code into existing layers and existing feature folders - Do not create new top-level directories under `src/` without approval - Components live as `.tsx` files inside existing feature folders; do not add barrel `index.ts` files unless that folder already uses them -- `core/` must not import from React/UI modules -- Page components should compose reusable logic from `components/`, `hooks/`, and `context/` +- Page components should compose reusable logic from `component/`, `hook/`, `context/`, and `service/` --- ## Import rules -- Use TS path aliases for cross-layer imports: - - `app/*`, `pages/*`, `components/*`, `core/*`, `hooks/*`, `context/*`, `styles/*`, `types/*` +- Use the existing absolute imports from `src` `baseUrl` for cross-layer imports: + - `api/*`, `asset/*`, `component/*`, `constant/*`, `context/*`, `hook/*`, `page/*`, `service/*`, `type/*`, `util/*` - Relative imports are OK within the same feature folder - Avoid deep relative imports that cross top-level boundaries - Do not mass-convert import styles; match the file's existing approach @@ -114,7 +116,7 @@ Top-level layers inside `src/`: } ``` -- Use `FC` for components and `FCC` (from `types/fcc`) when a component accepts `children` +- Use `FC` for components and `FCC` (from `type/fcc`) when a component accepts `children` - For new components, accept a `props` param and destructure on the first line; if the file already destructures in the signature, keep that style - Prefer Chakra props (`Flex`, `HStack`, `VStack` etc). Avoid custom CSS unless necessary for third-party integrations - One primary component per file; small helper components may live in the same file @@ -155,7 +157,7 @@ The agent MUST stop and ask for approval before doing any of the following: - Modify `tsconfig*.json` - Modify `vite.config.*` -- Modify Chakra theme system under `src/styles/**` beyond small task-required edits +- Modify Chakra theme setup in `src/theme.ts` beyond small task-required edits - Modify TS path aliases or alias-related config Everything else (routes, contexts, pages, components, hooks) is autonomous From f662137b590c65f177ffb7f63ad71d946b5c71f5 Mon Sep 17 00:00:00 2001 From: Syrnnik Date: Tue, 31 Mar 2026 22:46:49 +0300 Subject: [PATCH 3/7] docs: switch AGENTS verification to yarn --- AGENTS.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6cd9582..28dfa43 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ This is an autonomous coding agent in this repo. Goals: clean, readable code and ## Tech & basics -- Stack: React + TypeScript + Vite + Chakra UI (v2). Package manager: npm +- Stack: React + TypeScript + Vite + Chakra UI (v2). Package manager: yarn - Branches: - `main` - production branch. Deployment runs from this branch via GitHub Actions - `dev` - main development branch @@ -150,8 +150,8 @@ The agent MUST stop and ask for approval before doing any of the following: - Add/remove dependencies - Change versions in `package.json` (including `devDependencies`) -- Run upgrades that modify the dependency graph (e.g. `npm update`, `npm audit fix`) -- `package-lock.json` may change only as a result of `npm install` with unchanged versions +- Run upgrades that modify the dependency graph (e.g. `yarn upgrade`) +- `yarn.lock` may change only when dependency changes are approved ### 3. Build/toolchain/global styling config @@ -169,9 +169,9 @@ Everything else (routes, contexts, pages, components, hooks) is autonomous After changes, the agent MUST run: ```bash -npm install -npm run lint -npm run build +yarn install --frozen-lockfile +yarn run lint +yarn run build ``` --- From 63109a5239de62887bddc44814708909cd5992b0 Mon Sep 17 00:00:00 2001 From: Syrnnik Date: Tue, 31 Mar 2026 22:50:01 +0300 Subject: [PATCH 4/7] chore: update gitignore for codex files --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1c8d367..1ab63ff 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,11 @@ build/ # IDEA .idea/ .vscode/ -.vscode/ *.code-workspace # TO-DO files **/*[Tt][Oo][Dd][Oo]* + +# Codex +.codex/ +codex-history.md From 72185240af34f7bb4eef674f6c053f7b66efecb7 Mon Sep 17 00:00:00 2001 From: Syrnnik Date: Mon, 14 Sep 2026 21:06:48 +0300 Subject: [PATCH 5/7] feat: tickets pagination and search by order id on backend --- src/api/ticket/ticket.ts | 9 +++- src/component/ticket/TicketsList.tsx | 39 ++++++++++++--- src/component/ticket/TicketsPanel.tsx | 34 +++++++++----- src/constant/tables.ts | 2 + src/page/Tickets.tsx | 68 +++++++++++++++++++++++---- src/type/ticket/ticket.ts | 4 ++ 6 files changed, 125 insertions(+), 31 deletions(-) diff --git a/src/api/ticket/ticket.ts b/src/api/ticket/ticket.ts index 9201816..3f4556d 100644 --- a/src/api/ticket/ticket.ts +++ b/src/api/ticket/ticket.ts @@ -1,12 +1,17 @@ import { axiosClient } from "api/axiosClient" import { ApiRequest } from "type/api/apiRequest" import { ApiResponse } from "type/api/apiResponse" -import { FullTicket, Ticket, TicketCreate } from "type/ticket/ticket" +import { + FullTicket, + Ticket, + TicketCreate, + TicketSearchFilter, +} from "type/ticket/ticket" import { WithId } from "type/withId" import { beautifyBody } from "util/apiRequestBody" export const getAllTickets = async ( - body: ApiRequest, + body: ApiRequest, ): Promise[]>> => { const { data: ticketsList } = await axiosClient.post( "/tickets", diff --git a/src/component/ticket/TicketsList.tsx b/src/component/ticket/TicketsList.tsx index 3b8f65f..0e9b421 100644 --- a/src/component/ticket/TicketsList.tsx +++ b/src/component/ticket/TicketsList.tsx @@ -1,18 +1,34 @@ -import { Flex, Text } from "@chakra-ui/react" +import { Flex, Spinner, Text } from "@chakra-ui/react" import { TicketListItem } from "component/ticket/TicketListItem" -import { FC } from "react" +import { FC, UIEvent } from "react" import { FullTicket } from "type/ticket/ticket" import { WithId } from "type/withId" +// NOTE: scrollTop can be fractional (browser zoom), so compare with a threshold +const LOAD_MORE_SCROLL_THRESHOLD_PX = 50 + interface TicketsListProps { ticketsList?: WithId[] + hasNextPage?: boolean + isFetchingNextPage?: boolean + fetchNextPage?: () => void } export const TicketsList: FC = (props) => { - const { ticketsList } = props + const { ticketsList, hasNextPage, isFetchingNextPage, fetchNextPage } = props const isTicketsExist = !!ticketsList?.length + const handleScroll = (e: UIEvent) => { + const { scrollHeight, scrollTop, clientHeight } = e.currentTarget + const isAtBottom = + scrollHeight - scrollTop - clientHeight < LOAD_MORE_SCROLL_THRESHOLD_PX + + if (isAtBottom && hasNextPage && !isFetchingNextPage) { + fetchNextPage?.() + } + } + return ( = (props) => { )} - - {ticketsList?.map((ticket, index) => ( - + + {ticketsList?.map((ticket) => ( + ))} + + {isFetchingNextPage && ( + + + + )} ) diff --git a/src/component/ticket/TicketsPanel.tsx b/src/component/ticket/TicketsPanel.tsx index e7671f5..93a2297 100644 --- a/src/component/ticket/TicketsPanel.tsx +++ b/src/component/ticket/TicketsPanel.tsx @@ -5,32 +5,35 @@ import { TicketCreateBtn } from "component/ticket/TicketCreateBtn" import { TicketSearch } from "component/ticket/TicketSearch" import { TicketsList } from "component/ticket/TicketsList" import { useUserPermissions } from "hook/useUserPermissions" -import { FC, useState } from "react" +import { Dispatch, FC, SetStateAction } from "react" import { FullTicket } from "type/ticket/ticket" import { WithId } from "type/withId" interface TicketsPanelProps { ticketsList?: WithId[] isLoading?: boolean + ticketOrderId?: string + setTicketOrderId: Dispatch> + hasNextPage?: boolean + isFetchingNextPage?: boolean + fetchNextPage?: () => void } export const TicketsPanel: FC = (props) => { - const { ticketsList, isLoading } = props + const { + ticketsList, + isLoading, + ticketOrderId, + setTicketOrderId, + hasNextPage, + isFetchingNextPage, + fetchNextPage, + } = props const { canCreateTickets } = useUserPermissions() - const [ticketOrderId, setTicketOrderId] = useState() - const isTicketsExist = ticketsList !== undefined - const isTicketOrderIdExists = !!ticketOrderId - - const filteredTicketsList = isTicketOrderIdExists - ? ticketsList?.filter((ticket) => - ticket.order.order_id.includes(ticketOrderId), - ) - : ticketsList - return ( {!isTicketsExist && isLoading && } @@ -46,7 +49,12 @@ export const TicketsPanel: FC = (props) => { {isTicketsExist && !isLoading && ( // TODO: maybe remove tabs component // - + )} {/* Create Btn */} diff --git a/src/constant/tables.ts b/src/constant/tables.ts index bd8e724..6b3b8cf 100644 --- a/src/constant/tables.ts +++ b/src/constant/tables.ts @@ -4,6 +4,8 @@ export const INITIAL_ROWS_PER_PAGE = 10 export const ROWS_PER_PAGE_SELECT_VARIANTS = [10, 50, 100] +export const TICKETS_PER_PAGE = 20 + export const STORAGE_GOODS_TABLE_COLUMNS: (TableColumn | null)[] = [ // { name: "", param: "quantity_color", isSearchable: false, isSortable: false }, { name: "ID", param: "id", isSearchable: true, isSortable: true }, diff --git a/src/page/Tickets.tsx b/src/page/Tickets.tsx index 1afc57c..188f57f 100644 --- a/src/page/Tickets.tsx +++ b/src/page/Tickets.tsx @@ -5,13 +5,17 @@ import { LoadingPage } from "component/page/LoadingPage" import { Page } from "component/page/Page" import { PageHeading } from "component/page/PageHeading" import { TicketsPanel } from "component/ticket/TicketsPanel" +import { TICKETS_PER_PAGE } from "constant/tables" import { useTicketsContext } from "context/tickets" -import { useQuery } from "react-query" +import { useEffect, useState } from "react" +import { useInfiniteQuery } from "react-query" import { ApiResponse } from "type/api/apiResponse" import { PageProps } from "type/page/page" import { FullTicket } from "type/ticket/ticket" import { WithId } from "type/withId" +const SEARCH_DEBOUNCE_MS = 400 + export const Tickets = (props: PageProps) => { const { guideNotionPageId } = props @@ -22,15 +26,51 @@ export const Tickets = (props: PageProps) => { isProcessingOrderLoading, } = useTicketsContext() - const { data: ticketsResponse, isLoading } = useQuery< - ApiResponse[]> - >("ticketsList", () => - getAllTickets({ - sortField: "id", - sortDirection: "desc", - }), + const [ticketOrderId, setTicketOrderId] = useState() + const [searchOrderId, setSearchOrderId] = useState() + + // NOTE: don't request backend on every keystroke of Order ID search + useEffect(() => { + const timeout = setTimeout( + () => setSearchOrderId(ticketOrderId || undefined), + SEARCH_DEBOUNCE_MS, + ) + return () => clearTimeout(timeout) + }, [ticketOrderId]) + + const { + data: ticketsResponse, + isLoading, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + } = useInfiniteQuery[]>>( + ["ticketsList", searchOrderId], + ({ pageParam = 0 }) => + getAllTickets({ + limit: TICKETS_PER_PAGE, + offset: pageParam, + sortField: "id", + sortDirection: "desc", + searchFilter: searchOrderId + ? { marketplace_order_id: searchOrderId } + : undefined, + }), + { + // Keep current list (and search input) visible while new search loads + keepPreviousData: true, + // NOTE: next page offset is a count of already loaded tickets, + // there is no next page when all tickets from response count are loaded + getNextPageParam: (lastPage, allPages) => { + const loadedCount = allPages.reduce( + (count, page) => count + page.result.length, + 0, + ) + return loadedCount < lastPage.count ? loadedCount : undefined + }, + }, ) - const ticketsList = ticketsResponse?.result + const ticketsList = ticketsResponse?.pages.flatMap((page) => page.result) return ( @@ -38,7 +78,15 @@ export const Tickets = (props: PageProps) => { - + diff --git a/src/type/ticket/ticket.ts b/src/type/ticket/ticket.ts index 0bbb6af..1f0a653 100644 --- a/src/type/ticket/ticket.ts +++ b/src/type/ticket/ticket.ts @@ -22,6 +22,10 @@ export type FullTicket = Ticket & { order: WithId } +export type TicketSearchFilter = Partial & { + marketplace_order_id?: string +} + export type TicketCreate = { orderId: number description: string From 6193d8707a02ec0e6b4257ed838df171d93e9aa4 Mon Sep 17 00:00:00 2001 From: Syrnnik Date: Mon, 14 Sep 2026 22:15:25 +0300 Subject: [PATCH 6/7] docs: add agent docs and rework AGENTS.md --- AGENTS.md | 202 ++++---------------- CLAUDE.md | 1 + agent-docs/README.md | 11 ++ agent-docs/frontend-architecture.md | 71 +++++++ agent-docs/frontend-conventions.md | 49 +++++ agent-docs/frontend-pages-and-components.md | 48 +++++ 6 files changed, 218 insertions(+), 164 deletions(-) create mode 120000 CLAUDE.md create mode 100644 agent-docs/README.md create mode 100644 agent-docs/frontend-architecture.md create mode 100644 agent-docs/frontend-conventions.md create mode 100644 agent-docs/frontend-pages-and-components.md diff --git a/AGENTS.md b/AGENTS.md index 28dfa43..40449d9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,183 +1,57 @@ -# AGENTS.md - Agent Rules +# AGENTS.md - Agent Rules (frontend) -This is an autonomous coding agent in this repo. Goals: clean, readable code and minimal diffs +React + TypeScript + Vite + Chakra UI v2 + react-query v3 SPA of SouvTech CMS. +Package manager: yarn. Goals: clean, readable code and minimal diffs. -## Non-negotiables - -- Minimal diff: change only what the task requires. No cleanup/refactor unless asked -- Match local style: keep existing patterns, naming, imports, and formatting in touched files -- No new dependencies or toolchain/config changes without approval (see Approval Gates) -- No new top-level directories under `src/` without approval -- This repository uses only this `AGENTS.md` file as the source of agent rules - ---- - -## Tech & basics - -- Stack: React + TypeScript + Vite + Chakra UI (v2). Package manager: yarn -- Branches: - - `main` - production branch. Deployment runs from this branch via GitHub Actions - - `dev` - main development branch - - `` - feature branches created by the agent -- Commits: Conventional Commits -- Commit tags: - - `feat:` - new functionality - - `fix:` - bug fixes - - `refactor:` - code changes without behavior changes or bug fixes - - `chore:` - config/build system changes, dependency updates - - `test:` - anything related to tests - - `style:` - formatting-only changes without behavior changes - - `docs:` - documentation-only changes - - `ci:` - CI configuration changes - ---- - -## How to Publish Changes to GitHub - -1. Switch to `dev` -2. Pull the latest changes -3. Switch to a new branch with a semantic name that matches the task -4. Split changed files into logical commits and commit them -5. Push the branch to GitHub -6. Create a PR into `dev` -7. Assign the PR to the person who did the task (usually `@me`) -8. If the task is complete, add the `Ready To Merge` label (check that the label exists in the repo first) -9. Always provide the PR link -10. If the task is done, ask whether the PR can be merged - -### Branch protection rule - -- Direct commits/pushes/merges to `main` are forbidden -- `main` can be updated only via PR from `dev` - ---- +## Read first (short files, pick by task) -## PR Description Format +- `agent-docs/README.md` - index of docs +- `agent-docs/frontend-architecture.md` - routing, permissions, API layer, react-query, WS +- `agent-docs/frontend-pages-and-components.md` - where pages and reusable components live +- `agent-docs/frontend-conventions.md` - style, dates, pagination, checks, gotchas +- `../backend/agent-docs/business-rules.md` - domain rules (UI language, dates, SKU…) -Use this template when creating a PR description: - -```md -## Summary -- -``` - ---- - -## Project structure (do not invent new layers) - -Top-level layers inside `src/`: - -- `api/` - API request functions -- `asset/` - static assets used by UI -- `component/` - reusable UI components -- `constant/` - shared constants and enums -- `context/` - global React Context providers + hooks -- `hook/` - reusable hooks (no JSX) -- `page/` - routed screens -- `service/` - orchestration/mutation logic over API layer -- `type/` - shared TypeScript types -- `util/` - shared helper functions -- `configuration.tsx`, `theme.ts`, `index.tsx` - app-level setup files - -### Structure rules - -- Put new code into existing layers and existing feature folders -- Do not create new top-level directories under `src/` without approval -- Components live as `.tsx` files inside existing feature folders; do not add barrel `index.ts` files unless that folder already uses them -- Page components should compose reusable logic from `component/`, `hook/`, `context/`, and `service/` - ---- +## Non-negotiables -## Import rules +- Minimal diff: change only what the task requires; no drive-by refactors or cleanups +- Match the style of the touched file; reuse existing components, hooks and patterns +- No new dependencies, no `package.json` version changes, no `tsconfig`/`vite.config`/ + `theme.ts` changes without approval; `yarn.lock` changes only with approved deps +- No new top-level directories under `src/`; no barrel `index.ts` files +- Do not rename or move files unless the task requires it +- Never commit, push, deploy or merge unless explicitly asked -- Use the existing absolute imports from `src` `baseUrl` for cross-layer imports: - - `api/*`, `asset/*`, `component/*`, `constant/*`, `context/*`, `hook/*`, `page/*`, `service/*`, `type/*`, `util/*` -- Relative imports are OK within the same feature folder -- Avoid deep relative imports that cross top-level boundaries -- Do not mass-convert import styles; match the file's existing approach +## Structure (`src/`) ---- +`api/` (requests) · `service/` (mutations over api) · `context/` · `hook/` (no JSX) · +`component/` (feature folders) · `page/` (routed screens) · `type/` · `constant/` · `util/` · +`asset/`; app setup in `configuration.tsx`, `theme.ts`, `index.tsx`. ## Coding standards -### Components - -- Arrow components with typed props: - - ```tsx - interface ExampleProps { - title: string - } - - export const Example: FC = (props) => { - const { title } = props - - return {title} - } - ``` - -- Use `FC` for components and `FCC` (from `type/fcc`) when a component accepts `children` -- For new components, accept a `props` param and destructure on the first line; if the file already destructures in the signature, keep that style -- Prefer Chakra props (`Flex`, `HStack`, `VStack` etc). Avoid custom CSS unless necessary for third-party integrations -- One primary component per file; small helper components may live in the same file - -### Hooks - -- Hook functions must start with `use` -- Keep hooks pure - no JSX -- No `any`. Prefer explicit types where helpful - -### Comments - -- Only for non-obvious intent -- Use: - - `// NOTE:` for rationale/design - - `// TODO:` for follow-ups - - `// FIX:` for known bugs - ---- - -## Approval Gates (only these require approval) - -The agent MUST stop and ask for approval before doing any of the following: - -### 1. Top-level structure changes - -- Create/move/rename any top-level directory under `src/` -- Large restructuring: moving/renaming across layers or changing more than 5 files in a structural move - -### 2. Dependencies - -- Add/remove dependencies -- Change versions in `package.json` (including `devDependencies`) -- Run upgrades that modify the dependency graph (e.g. `yarn upgrade`) -- `yarn.lock` may change only when dependency changes are approved - -### 3. Build/toolchain/global styling config - -- Modify `tsconfig*.json` -- Modify `vite.config.*` -- Modify Chakra theme setup in `src/theme.ts` beyond small task-required edits -- Modify TS path aliases or alias-related config - -Everything else (routes, contexts, pages, components, hooks) is autonomous - ---- +- Absolute imports from `src` (`api/*`, `component/*`, …); relative only inside a feature folder +- Arrow components with typed props: `export const X: FC = (props) => { const {…} = props … }`; + `FCC` from `type/fcc` when children are accepted; Chakra props over custom CSS +- No `any`; hooks start with `use` and contain no JSX +- Prettier config in `.prettierrc` (no semicolons, double quotes, width 80, trailing commas) +- Comments only for non-obvious intent: `// NOTE:`, `// TODO:`, `// FIX:` +- UI text in English; permissions via `useUserPermissions`, never hardcoded role checks ## Verification (required) -After changes, the agent MUST run: - ```bash yarn install --frozen-lockfile -yarn run lint +yarn run lint # tsc --noEmit yarn run build +npx prettier --check ``` +Then check the change in the browser against the local backend +(`../backend/agent-docs/local-dev-and-testing.md`). ---- - -## Working style +## Git workflow -- If a file already has a pattern, extend it instead of introducing a new one -- Avoid new abstractions/helpers unless reused at least twice within the same task -- Do not rename/move files unless the task explicitly requires it +- `main` = production (deploy on push, only via PR from `dev`); `dev` = development +- Feature branch with a semantic name from `dev`; Conventional Commits + (`feat:`, `fix:`, `refactor:`, `chore:`, `docs:`, `ci:`) +- PR into `dev`, assign to the author, label `Ready To Merge` when done; PR body: + `## Summary` with bullets; always give the PR link; never merge yourself diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/agent-docs/README.md b/agent-docs/README.md new file mode 100644 index 0000000..4c8d51f --- /dev/null +++ b/agent-docs/README.md @@ -0,0 +1,11 @@ +# Документация фронта для кодинг-агентов + +React 18 + TypeScript + Vite + Chakra UI v2 + react-query v3 + react-router v6 + axios. +Правила репозитория — `../AGENTS.md`. Общая картина системы и предметной области — +`../../backend/agent-docs/overview.md` и `business-rules.md` там же. + +| Файл | Когда читать | +|---|---| +| [frontend-architecture.md](frontend-architecture.md) | Любая правка: провайдеры, маршруты и права, API-слой, react-query, WebSocket, контексты и хуки | +| [frontend-pages-and-components.md](frontend-pages-and-components.md) | Найти страницу/компонент под задачу, повторить существующий паттерн (таблицы, модалки, флоу гравёра) | +| [frontend-conventions.md](frontend-conventions.md) | Стиль кода, импорты, даты, пагинация, фильтры магазинов, тосты, проверки перед сдачей, ловушки | diff --git a/agent-docs/frontend-architecture.md b/agent-docs/frontend-architecture.md new file mode 100644 index 0000000..1ff48ab --- /dev/null +++ b/agent-docs/frontend-architecture.md @@ -0,0 +1,71 @@ +# Архитектура фронта + +## Загрузка приложения +`src/index.tsx` → `App` (ChakraProvider с `theme.ts`) → `AppRouter`: +`BrowserRouter` → `QueryClientProvider` → `WebSocketContextProvider` → `AuthContextProvider` → +side-маршруты (`/auth`, `/noaccess`, `/maintenance`) → `UserContextProvider` → `SearchContextProvider` +→ `PurchaseTabsContextProvider` → `PaginationContextProvider` → `Routes` внутри `AppLayout` +(сайдбар + `Outlet`; без токена редирект на `/auth`; на каждой смене маршрута дёргает +`GET /healthcheck`, при ошибке уводит на `/maintenance`). + +## Маршруты и права (`src/configuration.tsx`) +`configuration.sidebarItems` — массив маршрутов `{type: main|child|side, path, component, +permissions, icon, name}`. `permissions` не задан — доступно всем, `[]` — только админу, +список — нужны **все** перечисленные права. Оборачивается `withAuthAndPermission` (`hook/`): +пока грузится пользователь — спиннер, нет прав — `/noaccess`. Страницам с таблицами +оборачивают `TableContextProvider`, тикетам и гравировке — `TicketsContextProvider` / +`EngravingContextProvider`. `guideNotionPageId` у страницы → кнопка «?» с гайдом из Notion. +Версия в сайдбаре — `configuration.version`. + +## Слой API +- `api/axiosClient.ts`: `baseURL` из `VITE_API_BASE_URL`; в каждый запрос `Authorization: + Bearer ` и `device-token`; на 401 токен стирается (редирект сделает + `AppLayout`). Ошибка сервера в `error.response.data.detail`. +- `api//.ts` — по функции на эндпоинт: `getAll*`, `get*ById`, `create*`, + `update*`, `delete*`; возвращают `data`. Списки на бэке — **POST с телом** + (`POST /order/all/`, `POST /tickets`, `POST /storage/good/all/`). +- Тело списка собирается `beautifyBody` (`util/apiRequestBody.ts`) из `ApiRequest`: + `limit`, `offset`, `shopId → shops: [id]` (если > 0), `sortField → sort_field`, + `sortDirection → sort_direction`, `searchFilter → search_filter`. Ответ — `ApiResponse` + `{count, result}`. Тип `WithId` добавляет `id`. +- Мутации — в `service//*.ts`: хуки `useMutation` на `useMutation` + с `invalidateQueries(<ключи>)` в `onSuccess` и `notifyApiError`/`notify` в `onError` + (не везде — при добавлении делай `onError`). + +## react-query +`queryClient` с `refetchOnWindowFocus: false`, других дефолтов нет. Ключи: строки для списков +(`ordersResponse`, `storageGoodsList`, `purchasesList`, `ticketsList`, `currentUser`, +`usersList`…), кортежи для параметризованных (`["order", id]`, `["processingOrder", id]`, +`["ticketMessages", ticketId]`, `["ticketsList", searchOrderId]`). Паттерн пагинированных +таблиц: статичный ключ + `useEffect(() => refetch(), [offset, rowsPerPageCount, selectedShopId, +sortField, sortDirection, searchFilter])`. Тикеты — единственный `useInfiniteQuery` +(страницы по `TICKETS_PER_PAGE`, `getNextPageParam` по `count`). + +## WebSocket-инвалидация (`context/websocket.tsx`) +Одно соединение на `VITE_WEBSOCKET_BASE_URL`, без реконнекта. Бэкенд шлёт имя очереди +(`constant/ws_queues.py` на бэке), фронт по `switch` инвалидирует ключи: +`purchase`→`purchasesList`; `purchase_delivery`→`purchaseDeliveriesList`; `supplier*`→`suppliersList`; +`file`→закупки и посылки; `comment`→**все запросы**; `order`→`ordersResponse`; `good`→`goodsResponse`; +`user`/`role`→`currentUser`,`usersList`,`rolesList`; `storage`/`storage_good`→`purchaseDeliveriesList`, +`storageGoodsList`,`storageActualInfo`; `production_info`→`goodsWithProductionInfoList`; +`table_access`→`roleTableAccess`,`userTableAccessList`; `ticket`→`ticketsList`,`ticketMessages`. +Новая очередь на бэке → добавить ветку здесь. + +## Контексты (`src/context`) +| Контекст | Что держит | +|---|---| +| `auth` | `isAuthenticated` (по токену), `signIn`, `signOut` | +| `user` | `currentUser` (`GET /user/current/`), роли, права (плоский список имён), магазины, `isUserAdmin/Manager/Engraver`, `currentEngraverId`, доступ к колонкам | +| `pagination` | глобальный `rowsPerPageCount` (10/50/100) | +| `table` | generic: `sortField`, `sortDirection`, `searchFilter` для одной таблицы | +| `search` | глобальный текст поиска для карточных страниц (клиентская фильтрация) | +| `purchaseTabs` | индекс вкладки Purchases/Deliveries | +| `tickets` | открытый тикет, его сообщения и processing order | +| `engraving` | активная смена, текущий processing order, редиректы `/engraving` ↔ `/engraving/:id`, модалки перерывов и бездействия, опрос плановых перерывов каждые 10 с | +| `websocket` | только побочный эффект | + +## Хуки (`src/hook`) +`usePagination` (страница/offset локально), `useShopFilter` (`selectedShopId`, 0 = все), +`useUserPermissions` (булевы `canReadOrders`, `canEditStorage`… из `constant/permissions.ts`, +имена совпадают с бэкендом), `useUserTableAccess` (колонки по ролям, только Production Info), +`useAuthorizedDevice`, `useCommentInput` (комментарии к объекту), `withAuthAndPermission`. diff --git a/agent-docs/frontend-conventions.md b/agent-docs/frontend-conventions.md new file mode 100644 index 0000000..db69a53 --- /dev/null +++ b/agent-docs/frontend-conventions.md @@ -0,0 +1,49 @@ +# Соглашения и ловушки фронта + +## Код +- Импорты абсолютные от `src`: `api/…`, `component/…`, `constant/…`, `context/…`, `hook/…`, + `page/…`, `service/…`, `type/…`, `util/…`. Плюральные алиасы из `tsconfig.paths` + (`components/*`…) битые — не использовать. Относительные импорты запрещены eslint-правилом. +- Компоненты: `export const X: FC = (props) => { const {…} = props; … }`, `FCC` из + `type/fcc` для `children`; один основной компонент на файл, без barrel-файлов; Chakra-пропсы + вместо CSS. Хуки начинаются с `use`, без `any`. +- Prettier: 2 пробела, без точек с запятой, двойные кавычки, `printWidth 80`, trailing commas. + Комментарии `// NOTE:` (почему), `// TODO:`, `// FIX:`. + +## Данные +- Даты приходят строками: `order.date` как `d.M.yyyy`, остальное ISO. Инструменты — + `util/formatting.ts` (`dateAsStringToDate(str, isUTC)`, `formatDate`, `stringToDate`, + `dateToDateAsString`, `timestampToDate`…) и `util/dates.ts`. Бэкенд хранит UTC, клиент в + America/Chicago; целевой формат для клиента `MM/DD/YYYY`, 12-часовое время (CMS-задача). +- `WithId` вместо `id` в базовых типах; `ApiRequest`/`ApiResponse` для запросов. +- Пагинация: `INITIAL_ROWS_PER_PAGE=10`, варианты `[10, 50, 100]`, `TICKETS_PER_PAGE=20` + (`constant/tables.ts`); `offset = currentPage * rowsPerPageCount`. +- Фильтр магазина: `useShopFilter` → `shopId` в запросе → `shops: [id]`; опции — магазины + текущего пользователя (`userShops`), 0 = все. +- `engraving_info` — JSON-строка вариаций Etsy; `parseEngravingInfoStr` убирает служебные + ключи (`listing_id`, `product_id`, `transaction_type`, `OriginalSKU`), персонализация — + `PERSONALIZATION_PARAM`; текст декодировать `he` (Etsy присылает HTML-сущности). +- Права: `constant/permissions.ts` — строки должны совпадать с бэкендом; для UI — + `useUserPermissions().canXxx`. `constant/roles.ts` — `admin`, `manager`, `storager`, `engraver`. + +## Проверки перед сдачей +```bash +yarn install --frozen-lockfile +yarn run lint # tsc --noEmit +yarn run build # vite build → build/ +npx prettier --check <изменённые файлы> +``` +Тестов нет; проверяй в браузере (dev-сервер 5173, токен в `localStorage.token`, +см. `backend/agent-docs/local-dev-and-testing.md`). В dev включён `StrictMode` — двойной запрос +при монтировании нормален. + +## Ловушки +- Статичные query-ключи + ручной `refetch()` в таблицах: при добавлении параметра не забудь + его в зависимости `useEffect`, иначе данные не обновятся. +- `PurchasesHistory` использует ключ `ordersResponse` (как `Orders`). +- WS-инвалидация `comment` сбрасывает все запросы; соединение не переподключается. +- `TicketsPanel` скрывает поиск, пока `isLoading` — для поисковых запросов держи + `keepPreviousData`, иначе инпут размонтируется. +- Заголовок `device-token` уходит строкой `"null"`, если устройство не авторизовано. +- Гайды тянутся с `notion-api.splitbee.io` мимо `axiosClient`. +- `NotFound` не подключён к маршрутам; в `/storage` в `configuration.tsx` лишняя запятая в JSX. diff --git a/agent-docs/frontend-pages-and-components.md b/agent-docs/frontend-pages-and-components.md new file mode 100644 index 0000000..fdbe178 --- /dev/null +++ b/agent-docs/frontend-pages-and-components.md @@ -0,0 +1,48 @@ +# Страницы и ключевые компоненты + +## Страницы (`src/page`) +| Путь | Страница | Что показывает | +|---|---|---| +| `/` | `Dashboard` | графики продаж по дням, штатам, городам, итоги месяца (`component/chart/*`) | +| `/orders`, `/order/:id` | `Orders`, `OrderInfo` | серверная таблица заказов с фильтрами магазина/дат/«SKU not recognized»; карточка заказа со строками | +| `/goods` | `Goods` | товары магазинов (таблица, скрытие `is_actual`) | +| `/clients`, `/client/:id` | `Clients`, `ClientDetails` | карточки типов клиентов + таблица; заказы клиента | +| `/tickets` | `Tickets` | список тикетов (бесконечная прокрутка, серверный поиск по номеру заказа) + `EngravingPanel` в режиме просмотра с чатом | +| `/engraving`, `/engraving/:id` | `OrdersForEngraving`, `Engraving` | рабочее место гравёра: поиск заказа, колонки Paused/Completed, перерывы, завершение смены; экран заказа | +| `/engravers` | `Engravers` | карточки гравёров (создание = пользователь + роль), аналитика | +| `/storage`, `/storage-good/:id` | `Storage`, `StorageGoodDetails` | склад: карточки аналитики, фильтры, таблица позиций; поставки, дефекты, закупки позиции | +| `/production-info` | `ProductionInfo` | параметры гравировки, колонки по `table_access` | +| `/shelves` | `Shelves` | зоны и полки с позициями | +| `/purchases`, `/purchases/history`, `/purchase/:id`, `/delivery/:id` | `Purchases`… | Kanban закупок и посылок, история, карточки | +| `/suppliers` | `Suppliers` | карточки поставщиков и менеджеров | +| `/reports` | `DetailedReports` | детальный отчёт за месяц + скачивание xlsx | +| `/users`, `/roles`, `/authorized-devices` | `Users`, `Roles`, `AuthorizedDevices` | сотрудники, роли с правами и доступом к колонкам, устройства | +| `/auth`, `/noaccess`, `/maintenance`, `/guide/:id` | | вход, 403, техработы, гайд из Notion (`react-notion` через splitbee) | + +## Таблицы с серверной пагинацией +`component/customTable/`: `CustomTh` (сортировка asc→desc→none и поповер поиска по колонке, +пишет в `TableContext`), `SearchFiltersClearBtn`, `TableTdSkeleton`. Описание колонок — +`constant/tables.ts` (`ORDERS_TABLE_COLUMNS`…, `null` = колонка действий). Пагинация — +`component/page/Pagination.tsx` + `RowsPerPageSelect`. Образец страницы — `page/Orders.tsx`. + +## Модалки и карточки +`type/modalProps.ts` (`isOpen`, `onClose`, `isReadOnly?`); открываются `useDisclosure` из +`New*Btn`/`*CardMenu`; фон `ModalBackgroundBlur`; сохранение через хук из `service/` и +`notify(...)`. Канонический пример — `component/storage/StorageModal.tsx`. Карточные страницы +(`Users`, `Roles`, `Suppliers`, `Engravers`, `Devices`) фильтруются на клиенте по `SearchContext`. + +## Флоу гравёра (`component/orderProcessing`, `workShift`, `workBreak`, `engraver`) +`EngravingPanel` = детали заказа (`ProcessingOrderDetails`, `ProcessingGood` с +`engraving_info` через `util/engravingInfo.ts` и `he.decode`) + кнопки +`ProcessingOrderButtons` (`FinishBtn`→`Finished`, `PauseBtn`→`Paused`, `TakeBreakBtn`, +`BackBtn` в просмотре) + `TicketChat`. Вход заказа: `FindOrderColumn` + `OrderIdSearchInput` +(≥10 символов) → `OrderToEngravingCard` → мутация создания processing order. Смена: +`WorkShiftStart`/`WorkShiftFinishBtn`; перерывы: `ActiveWorkBreakModal`, +`ActiveScheduledBreakModal`; бездействие: `InactivityModal` (10 минут). Статусы — +`constant/orderStatus.ts`. + +## Общие элементы +`Page` (обёртка + кнопка гайда), `PageHeading` (`HeadingBtns`: поиск, уведомления, +профиль), `Container` (белая карточка), `LoadingPage`, `CollapsibleCardsGrid`, `CustomTooltip`, +`component/filter/*` (магазин, даты, месяц, год), `component/select/*`, `component/badge/*`, +тосты `util/toasts.tsx` (`notify`, `notifyApiError`). From a07416764e3448ba1e56151ca9f598d41feebced Mon Sep 17 00:00:00 2001 From: Syrnnik Date: Mon, 14 Sep 2026 22:19:36 +0300 Subject: [PATCH 7/7] docs: rule to keep agent docs current --- AGENTS.md | 12 ++++++++++++ agent-docs/README.md | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 40449d9..47b9499 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,6 +21,18 @@ Package manager: yarn. Goals: clean, readable code and minimal diffs. - Do not rename or move files unless the task requires it - Never commit, push, deploy or merge unless explicitly asked +## Keep the docs current + +- Before changing a page or mechanic, read its `agent-docs/` file; if the doc contradicts + the code, fix the doc in the same PR +- When a change alters routes, permissions, query keys, WebSocket invalidations, contexts, + conventions or workflow described in `agent-docs/`, update the affected file(s) in the same + branch (separate `docs:` commit) +- Domain rules agreed with the client live in `../backend/agent-docs/business-rules.md` - + add new ones there +- Keep files short and topic-split: extend the matching file instead of creating new ones, + register new files in `agent-docs/README.md` + ## Structure (`src/`) `api/` (requests) · `service/` (mutations over api) · `context/` · `hook/` (no JSX) · diff --git a/agent-docs/README.md b/agent-docs/README.md index 4c8d51f..ef29ceb 100644 --- a/agent-docs/README.md +++ b/agent-docs/README.md @@ -9,3 +9,8 @@ React 18 + TypeScript + Vite + Chakra UI v2 + react-query v3 + react-router v6 + | [frontend-architecture.md](frontend-architecture.md) | Любая правка: провайдеры, маршруты и права, API-слой, react-query, WebSocket, контексты и хуки | | [frontend-pages-and-components.md](frontend-pages-and-components.md) | Найти страницу/компонент под задачу, повторить существующий паттерн (таблицы, модалки, флоу гравёра) | | [frontend-conventions.md](frontend-conventions.md) | Стиль кода, импорты, даты, пагинация, фильтры магазинов, тосты, проверки перед сдачей, ловушки | + +## Поддержание актуальности +Документация правится вместе с кодом: меняешь маршрут, права, query-ключ, WS-инвалидацию, +контекст или соглашение — обнови соответствующий файл в той же ветке. Нашёл расхождение доки +с кодом — исправь доку. Правила предметной области — в `backend/agent-docs/business-rules.md`.