Imported from rostredko/wishlist-app (
AGENTS.md). Install upstream withnpx skills add rostredko/wishlist-app. Copyright stays with the author.
AGENTS.md
Agent role
You are a senior frontend engineer on this codebase. Priorities: (1) SEO and performance (Core Web Vitals), (2) i18n and user-visible copy quality, (3) small, reviewable diffs - no drive-by refactors. If product intent is unclear, ask rather than assume.
About
WishList App is a free service for creating and sharing wishlists (gift lists).
Core product value: simplicity and speed. Minimal features, maximum UX. No ads.
The primary business goal is organic search traffic. No paid ads are used.
The product must rank well for queries like "зробити вішліст", "create wishlist", "список побажань онлайн".
Fast load times (Core Web Vitals) and technical cleanliness are not just UX - they are direct SEO factors.
Site: https://wishlistapp.com.ua
Stack
- Frontend: React 19, TypeScript, Vite, MUI v7
- Backend: Firebase (Auth, Firestore, Storage)
- i18n: i18next - locales in
src/locales/{en,ua}/ - Routing: React Router v7 -
/ua/*,/en/* - Testing: Vitest + Testing Library
Commands
From the repository root (copy-paste):
npm run dev— Vite dev servernpm run build—tsc -b+ production bundlenpm run lint— ESLintnpx vitest run— full test run (CI-style)npm run test:ci— coverage mode; requires@vitest/coverage-v8to be installed when using--coveragenpm run test— Vitest watch mode (dev loop);npm run test:ui— browser UInpm run preview— serve the production build locally (Vite preview)
For non-trivial changes, run lint, build, and vitest run before considering the task complete unless the user narrowed scope.
Data layer (Firebase)
- App singleton:
src/firebase/init.tsandconfig.tsinitialize one Firebase app. Consumers importauthfromauth-client.ts,dbfromdb-client.ts,storagefromstorage-client.ts(not duplicate app entry points). - Wishlist API:
src/services/wishListService.tsowns Firestore and Storage calls for wishlists and items (create/read/update/delete, realtime listeners, banner upload). - Path aliases (all defined in
tsconfig.json):@components/*→src/Components/,@models/*→src/types/,@hooks/*→src/hooks/,@api/*→src/services/,@lib/*→src/firebase/,@utils/*→src/utils/,@assets/*→src/assets/,@constants/*→src/constants/ - Banner URLs:
bannerImagestores a download URL fromgetDownloadURL. On wishlist delete, Storage deletion runs only for URLs underfirebasestorage.googleapis.com, viadeleteObject(ref(storage, url))(Firebase v11refaccepts a full download URL). - Item order:
subscribeWishlistItemsqueries items withorderBy('createdAt', 'asc'). If the Firebase console reports a missing index, add the suggested composite index.
Key Conventions
- UI kit: docs/ui-kit.md – typography, buttons, surfaces, mobile-first layout, copy rules (reference before adding new visual patterns)
- Use existing MUI components - no custom CSS unless unavoidable
- i18n: all user-visible UI copy goes in locale files (
src/locales/{en,ua}/), never hardcoded - Copy and punctuation: use a normal hyphen
-(ASCII), not an em dash (Unicode U+2014), in user-facing strings and in this file - SEO
- Per-route metadata -
SEOHeadsets title, description, canonical, alternates, and JSON-LD for SPA routes after load. - Static shell (
index.html) - default<title>, meta description, Open Graph, Twitter Card, and skeleton H1 for the first HTML response (UA-focused defaults for crawlers and pre-hydration). Keep these aligned with top UA queries; they are not a substitute forSEOHeadon/en/*and/ua/*. - Example / demo wishlists -
DEMO_WISHLISTS, guest SEO overrides (EXAMPLE_SEO), and hreflang-style alternates (EXAMPLE_WISHLIST_ALTERNATES) live insrc/constants/exampleWishlists.ts.wishListService.tsimportsDEMO_WISHLISTSfrom there;WishListItemList.tsximports SEO maps andisDemoWishlistId. Overrides apply only when the viewer cannot edit (canEdit === false; owners and admins keep{wishlist.title} - WishList App). - Sitemap -
public/sitemap.xml: update<lastmod>when shipping meaningful changes to listed URLs. - Blog - Routes
/:lng/blog(hub) and/:lng/blog/:slug(articles). Slug pairs andBLOG_LAST_UPDATEDlive insrc/constants/blogArticles.ts. Article bodies are undersrc/Components/blog/;BlogArticlePagemaps slug to a static component (no per-articlelazyfor correct client navigation).SEOHeadsupportsstructured.article,structured.howTo, andstructured.guideItemListJSON-LD for posts and the hub. Legacy/:lng/how-toand/:lng/how-to/:slugredirect to/blog(article slug preserved when present).public/llms.txtlists the blog hubs for AI crawlers.
- Per-route metadata -
- Prefer editing existing files over creating new ones
- Tests: test files live in
src/__tests__/; shared render wrapper issrc/test/render.tsx(import ascustomRender— wraps with i18n provider and router). Setup file:src/test/setupTests.ts. - Key utils:
src/utils/analytics.ts— GA4 wrapper with event queue;src/utils/locale.ts— exportsSUPPORTED_LANGS,detectPreferredLang,isProbablyBot. - Theme:
src/theme.tsexportsdarkTheme— the single MUI theme instance. Do not create a new theme. - Vitest: mocked function components are often invoked as
(props, undefined); usetoHaveBeenCalledWith(expect.objectContaining({ … }), undefined)when spying onSEOHead-style mocks
Code quality (commit-ready)
- Prefer self-documenting code: names and structure should carry meaning; avoid comments that only restate the next line.
- Use comments sparingly: non-obvious invariants, analytics/SEO behavior, one-off Firebase/auth flows, or a one-line reason next to an
eslint-disable. - Remove dead code, unused imports, and leftover debug noise before handoff.
- For a final pass before the user commits: run
npm run lint,npm run build, andnpx vitest run(unless the task scope was explicitly smaller).
Never Do
- Never commit or push - the user always commits and pushes manually
- Never add
console.logto production code - Never hardcode strings visible to users
- Never install packages without confirming with the user
Workflow
- Prepare code changes only; the user reviews and commits
- When done with a task, summarize what changed and why - no need for verbose explanations
- Ask before making structural/architectural changes
Directory map
src/
App.tsx # routing root, language sync, lazy page imports
main.tsx # React entry point
theme.ts # darkTheme (MUI)
i18n.ts # i18next init
Components/ # UI components (@components/*)
blog/ # blog hub + article components
SEOHead.tsx # per-route <head> metadata
constants/ # blogArticles.ts, exampleWishlists.ts (@constants/*)
firebase/ # init, auth/db/storage clients (@lib/*)
hooks/ # useAuth, useWishlistData (@hooks/*)
services/ # wishListService.ts (@api/*)
types/ # WishList.ts, WishListItem.ts (@models/*)
utils/ # analytics.ts, locale.ts (@utils/*)
locales/en/ # English i18n strings
locales/ua/ # Ukrainian i18n strings
__tests__/ # all test files
test/ # render.tsx (custom wrapper), setupTests.ts
Docs
Everything under docs/ is local-only (gitignored, not pushed). That includes docs/ui-kit.md – keep a copy on your machine for UI conventions; the link in this file is still valid locally even though the repo has no docs/ tree.
Language
- Documentation (
AGENTS.md,CLAUDE.md, README, technical notes underdocs/): English only. - Comments in code (
//,/* */, JSDoc in.ts/.tsx, Firebase rules such asstorage.rules): English only. Do not write comments in Russian, Ukrainian, or other languages — English keeps the codebase consistent for tools and review. - User-visible copy belongs in
src/locales/{en,ua}/and in SEO/static shell where the product needs Ukrainian or English per i18n rules above — not mixed into non-locale comments.
Keep this file concise: prefer links to locale files, wishListService.ts, and local docs/ over pasting long specifications here (progressive disclosure).