This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
- No AI attribution in commits: Never add
Co-Authored-By:trailers (e.g.Co-Authored-By: Claude ...) to commit messages.
# Development
npm start # Dev server with proxy (http://localhost:4200)
npm run emulator # Firebase emulator with persistence
npm run emulator:debug # Firebase emulator with debug output
# Build
npm run build # Default build
npm run build:prod # Production build against the tracked demo Firebase config
npm run build:deploy # Production build against firebase-config.build.json (used by deploy/CI)
npm run build:docker # Docker-specific build
# Code quality
npm run lint # ESLint check
npm run lint:fix # Auto-fix lint issues
npm run prettier:fix # Format code
# Testing
npm test # Vitest + happy-dom (via Angular's @angular/build:unit-test builder); specs use Jasmine-style describe/it
npm run test:scripts # node:test suite for scripts/ (*.test.mjs)
# Deployment (one CLI: scripts/localess.mjs; these are aliases)
npm run localess -- <command> # CLI entry point (setup | sync | deploy | check)
npm run localess:setup # Provision Firebase infrastructure and record the project markers
npm run localess:sync # Regenerate local project files from remote state
npm run localess:deploy # Build and deploy to a Localess-managed project
npm run localess:check # Report what a project is still missing (--fix repairs the safe ones)
# Firebase Functions (from /functions directory)
cd functions && npm run build # Compile TypeScript functions
cd functions && npm run serve # Run functions locallyLocaless is a Translation & Content Management System (CMS) built with Angular 21+, NgRx Signals, and Firebase.
- Frontend: Angular 21 (standalone components, signals, OnPush)
- State: NgRx Signals (
@ngrx/signals) - Backend: Firebase (Firestore, Auth, Storage, Functions, Hosting)
- UI: Spartan/Helm component library (
libs/ui/); Angular Material remains only as residual providers inapp.config.ts - Styling: Tailwind CSS 4 + SCSS
- Rich Text: TipTap editor
- Functions: Express.js on Firebase Functions (region configurable, default
europe-west6)
src/app/
├── core/ # Singleton services: error handler, HTTP interceptors, title service
├── shared/ # Cross-feature code
│ ├── models/ # TypeScript interfaces for all domain types
│ ├── services/ # 20 Firebase-backed domain services
│ ├── stores/ # 4 NgRx Signal stores (UserStore, SpaceStore, AppSettingsStore, LocalSettingsStore)
│ ├── guards/ # dirty-form.guard.ts (unsaved-changes guard); permission guards live in features-routing.module.ts
│ └── components/# Shared dialogs, table, paginator, tree, filter-toolbar, locale-icon, logo, etc. (toasts via NotificationService/Sonner)
├── features/ # Lazy-loaded feature routes
│ ├── admin/ # Space & user administration
│ └── spaces/ # Main workspace: contents, translations, schemas, assets, tasks, dashboard
├── auth/ # Auth: login (Email, Google, Microsoft), reset
└── app.config.ts # Root provider configuration (Firebase, HTTP, etc.)
functions/src/ # Firebase Cloud Functions backend
libs/ui/ # 44+ reusable Spartan/Helm UI components
Four NgRx Signal stores initialized at app startup:
UserStore: Auth state, role, granular permissions (derived from Firebase custom claims)SpaceStore: Selected workspace, content hierarchy, schemas, documentsAppSettingsStore: Global UI settingsLocalSettingsStore: User preferences (persisted to localStorage)
- Root redirects to
/features, authenticated viaauthGuard() - Feature routes use granular permission checks passed as
authGuardPipefunctions infeatures-routing.module.ts(e.g.,hasPermissionTranslationRead,hasPermissionSchemaRead) - All features are lazy-loaded
Domain services (in shared/services/) wrap Firestore CRUD operations. The Functions region is configurable (default europe-west6) and recorded on the GCP project as the localess-region label. The public REST API is exposed via the publicv1 Firebase Function rewrite at /api/v1/**.
These apply to all Angular code in this project (from .github/copilot-instructions.md):
- Standalone components: Do NOT add
standalone: truein@Component/@Directive/@Pipedecorators (it's the default) - Change detection: Always set
changeDetection: ChangeDetectionStrategy.OnPush - Signals: Use
signal()for local state,computed()for derived state; useupdate()/set(), nevermutate() - Inputs/Outputs: Use
input()andoutput()functions, not@Input()/@Output()decorators - Injection: Use
inject()function, not constructor injection - Control flow: Use
@if,@for,@switch— not*ngIf,*ngFor,*ngSwitch - CSS bindings: Use
[class]bindings — notngClass; use[style]bindings — notngStyle - Host bindings: Put in the
hostobject of@Component/@Directive— not@HostBinding/@HostListener - Forms: Reactive forms only (no template-driven)
- Services:
providedIn: 'root'for singletons - Images: Use
NgOptimizedImagefor static images - TypeScript: Strict mode, avoid
any(useunknown), prefer type inference
Never create git commits unless the user explicitly asks. Do not commit after making changes, after a migration, or at the end of a task. Only commit when the user says "commit" or "push".
After every code change, always run the following in order:
npm run build— verify the project compiles without errorsnpm run lint:fix— auto-fix lint and prettier issues
Four Angular build configurations: development, production, docker, deploy. Firebase emulators support Firestore, Auth, Storage, and Functions locally. The proxy config (proxy.conf.cjs) forwards API calls during development.
There is no in-app setup wizard. To get an admin in the local emulator, create a user in the Auth emulator UI (http://localhost:4000) and set its custom claims to {"role":"admin"}. This is a one-time step per checkout — npm run emulator runs with --import=./firebase-export --export-on-exit=./firebase-export, so the account persists across restarts. Against a real project, use npm run localess:check -- --project <id> --fix instead.
Detailed documentation lives in docs/. Read the relevant file when working on the corresponding area:
| Topic | File | Read when working on |
|---|---|---|
| Domain concepts (Space, Content, Schema, Translation, Asset), how localised values are stored | docs/concepts.md | Any new feature, onboarding, anything reading/writing a localised field |
CDN caching, cv param, redirect logic, TTLs |
docs/cdn-caching.md | functions/src/v1/cdn.ts, public API |
| V1 API — all endpoints, routers, middleware, token permissions | docs/v1-functions-api.md | Any work in functions/src/v1/ |
| Publish flow & cache invalidation | docs/publish-flow.md | Content/translation publish, tasks |
| Webhooks — events, payload, HMAC signing, logging | docs/webhooks.md | functions/src/webhooks.ts, webhook-utils.ts, webhook UI |
| API token auth & permissions | docs/auth-tokens.md | Middleware, token management, public API |
| Firebase billing & cost optimization | docs/billing.md | Functions, Storage, cost analysis |
| Frontend architecture, routing, libs/ui | docs/frontend-architecture.md | Any Angular feature work |
| NgRx Signal stores, state patterns | docs/frontend-state.md | Adding/editing stores or components |
| User roles, route guards, UI permissions | docs/frontend-permissions.md | Auth, guards, user management |
| Spartan UI migration (checkbox, select, notifications) | docs/spartan-ui-migration.md | Migrating Material → Spartan, dialogs, forms |
Shared components (ll-table, ll-paginator, ll-tree, ll-filter-toolbar) — index, required doc structure |
docs/components/README.md | Anything in src/app/shared/components/; read before adding or changing one |
Frontend testing — Vitest setup (test.isolate: true), centralized Firebase mocking pattern and why it stays centralized |
docs/testing.md | Any new/edited *.spec.ts, src/test-setup.ts |
| Deployment & self-hosting | ||
| Deployment overview, prerequisites, automated vs manual | docs/deployment/overview.md | Any deployment/self-hosting question |
Phase 1 — Firebase provisioning (npm run localess:setup) |
docs/deployment/firebase-setup.md | scripts/localess.mjs, scripts/localess/, firebase.json auth block |
Phase 2 — npm run localess:deploy, .env.<project-id>, LOCALESS_* build-time config |
docs/deployment/first-deploy.md | Deploys, scripts/localess/commands/, scripts/localess/config.mjs, scripts/localess/generate.mjs, scripts/localess/defines.mjs, region wiring, login provider flags |
| Phase 3 — Pushing updates, targeted deploys, rollback | docs/deployment/updates.md | Redeploys, --only targets, upgrade steps |
Health check (npm run localess:check), --fix, invoker bindings |
docs/deployment/check.md | scripts/localess/checks.mjs, scripts/localess/commands/check.mjs, diagnosing a broken install |
| Feature modules — Admin | ||
| Admin overview (users, spaces, settings) | docs/features/admin/overview.md | Any admin feature |
| Admin → Users | docs/features/admin/admin-users.md | features/admin/users/ |
| Admin → Spaces | docs/features/admin/admin-spaces.md | features/admin/spaces/ |
| Admin → Settings | docs/features/admin/admin-settings.md | features/admin/settings/ |
| Feature modules — Spaces | ||
| Spaces overview | docs/features/spaces/overview.md | Any space feature |
| Dashboard | docs/features/spaces/dashboard.md | features/spaces/dashboard/ |
| Translations | docs/features/spaces/translations.md | features/spaces/translations/ |
| Contents | docs/features/spaces/contents.md | features/spaces/contents/ |
| Assets | docs/features/spaces/assets.md | features/spaces/assets/ |
| Schemas | docs/features/spaces/schemas.md | features/spaces/schemas/ |
| Tasks | docs/features/spaces/tasks.md | features/spaces/tasks/ |
| Space Settings | docs/features/spaces/settings.md | features/spaces/settings/ |
| Open API | docs/features/spaces/open-api.md | features/spaces/developers/open-api/ |
| Feature modules — Me | ||
| Me / User profile | docs/features/me.md | features/me/ |