5.9 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What is Mue?
Mue is a browser extension (Chrome, Firefox, Safari) that replaces the new tab page with a customizable dashboard featuring widgets like clock, weather, quotes, greetings, quick links, and backgrounds. Built with React 19, Vite 7, and Manifest V3.
Commands
bun install # Install dependencies (always use bun, never npm/yarn)
bun run dev # Dev server with HMR (opens browser automatically)
bun run dev:host # Dev server exposed on network
bun run build # Production build for all browsers (Chrome, Firefox, Safari)
bun run lint # Run ESLint + Stylelint
bun run lint:fix # Auto-fix lint issues
bun run pretty # Format with Prettier
bun run translations # Sync all locale files from en_GB.json base
bun run translations:percentages # Update translation completion stats
bun run translations:unused # Find unused translation keys
Build outputs: build/chrome/, build/firefox/, safari/Mue Extension/Resources/. Vite's prepareBuilds plugin (in vite.config.mjs) copies dist + manifests + icons into each browser folder and creates versioned zips.
E2E tests use Cypress:
bun run test:e2e # Open Cypress UI
bun run test:e2e:headless # Run headlessly (CI)
Tests live in cypress/e2e/.
Architecture
Bootstrap Flow
src/index.jsx- Initializes i18n from localStorage language, sets up Sentry, runs data migrations, exposes globalwindow.t(), renders<RouterProvider router={router} />src/router/- Hash-based router (createHashRouter) required for extension compatibility (no physical file routing).ModalRouter.jsxandRouterBridge.jsxhandle modal deep-linking.src/App.jsx-useAppSetup()checks first-run state, callsloadSettings()(which applies theme, fonts, custom CSS to the DOM), listens for EventBus'refresh'events. Renders:<TranslationProvider>→<Background>+<CustomWidgets>+<Widgets>+<Modals>
State Management (no Redux/Zustand)
All persistent state lives in localStorage. Components read from localStorage on mount and re-read when they receive EventBus refresh events. The only React Context is TranslationContext for i18n.
localStorage.getItem('key') === 'true'is the standard boolean check pattern- Settings changes write to localStorage, then emit
EventBus.emit('refresh', 'category')to notify widgets src/utils/settings/load.jsapplies localStorage settings to the DOM (theme classes, injected style elements for custom fonts/CSS)
EventBus (src/utils/eventbus.js)
Static class wrapping DOM CustomEvents. Primary communication mechanism between settings UI and widgets.
Key events:
'refresh'with payload:'quote','greeting','background','widgets','clock','other'- triggers widget reload'languageChange'with{language: 'en_GB'}- switches locale'modal'with'openMainModal'- opens settings modal
Pattern: register in useEffect, clean up with EventBus.off() on unmount.
Feature Organization (src/features/)
Each feature (background, time, quote, greeting, weather, search, quicklinks, message, navbar, stats, marketplace, welcome, helpers, modals) follows this structure:
feature/
├── index.jsx # Main component
├── options/index.jsx # Settings panel UI
├── hooks/ # Feature-specific hooks (useQuoteState, useQuoteLoader, etc.)
├── components/ # Subcomponents
├── api/ # Data fetching/processing
└── scss/ # Styles
The misc feature is special - it contains the modal system (modals/Modals.jsx), widget layout (CustomWidgets.jsx, views/Widgets.jsx), and the settings view (views/Settings.jsx).
Modal & Settings System
Modals.jsx orchestrates four modals (main, welcome, update, apps). The main modal has three tabs: Settings, Discover (marketplace), Library. Deep-linking via URL hash: #settings/appearance/fonts, #discover/quote_packs.
Global Variables (src/config/variables.js)
Singleton object holding language (i18n instance), languagecode, stats, and constants. Mutated during initialization. variables.getMessage() is dynamically set by TranslationContext.
Hooks
useFrequencyInterval()- configurable update intervals with visibility-aware pause/resumeuseCachedFetch()- fetch with localStorage caching and TTLuseT()/useTranslation()- access translation function from context
Path Aliases (configured in vite.config.mjs)
Use these instead of relative imports: @/, components/, contexts/, hooks/, assets/, config/, features/, lib/, scss/, translations/, utils/, i18n/
Code Rules
Do not add comments
Keep code self-explanatory. Use descriptive names instead of comments.
Do not use emojis in code strings
No emojis in console logs, placeholders, or code strings. Exception: user-facing toast notifications may include emojis.
All user-visible strings must use i18n
Never hardcode user-facing text. Use const t = useT(); t('widgets.greeting.morning') or getMessage('key'). Console logs don't need i18n.
Translation workflow
Edit src/i18n/locales/en_GB.json first (it's the base file), then run bun run translations to sync all other locales.
Naming conventions
Concise names without redundant prefixes. const [open, setOpen] = useState(false) not const [isOpen, setIsOpen]. Exception: is prefix is fine for boolean-returning functions (isValid()), has prefix for boolean properties.
Branch strategy
Three branches: dev (all PRs target here) → beta → main. Hotfix branches (hotfix/*) branch from main.
Conventional commits
Enforced by commitlint: feat:, fix:, chore:, docs:, refactor:, test:, style:, perf:. Scopes encouraged: feat(weather): add hourly forecast.