131 lines
7.7 KiB
Markdown
131 lines
7.7 KiB
Markdown
# CLAUDE.md — working brief for agent sessions
|
||
|
||
Read this before touching anything. Human setup instructions live in [README.md](README.md);
|
||
this file is the short version plus the rules that are easy to break.
|
||
|
||
## What the project is
|
||
|
||
**SHARP-ER** — *Shared Action on Rule of Law Progress – Exit Route*. EU-funded, nationwide
|
||
Libya, December 2025 – December 2027. Implemented by **Acted** (lead), **United Efforts
|
||
Consultancy (UEC)** and **Stichting Bureau Ensemble (SBE)**. Supports Libyan civil society,
|
||
local institutions and communities on justice, accountability and governance. Successor to
|
||
SHARP I and II (2019–2024).
|
||
|
||
Tone in all copy: professional, neutral, factual, inclusive, non-confrontational.
|
||
**Not** political, **not** advocacy, **not** campaign-style.
|
||
|
||
## Architecture in one screen
|
||
|
||
```
|
||
apps/web React Router 8 (framework mode, ssr: true) · React 19 · Vite 8 · Tailwind 4
|
||
SSR only reads the CMS from loaders. No client-side fetch on first paint.
|
||
/ 302 (Vary: Accept-Language) -> /en or /ar
|
||
/:lang locale layout, validates params.lang, provides { locale, dir }
|
||
/:lang/slug ten top-level sections + detail routes
|
||
Route ids: root · locale · home · about · whatWeDo · activities · events ·
|
||
applications · news · contact · resources · knowledgeHub (+ *Detail)
|
||
|
||
apps/cms Strapi 5.53 (TypeScript) · PostgreSQL 16 · i18n (en default, ar) · draft & publish
|
||
src/api/** content types (schema.json is the contract, commit it)
|
||
src/bootstrap/** runs on every boot: creates locales, grants Public permissions
|
||
(find/findOne on content; create-only on submission types)
|
||
config/ server · admin · database · middlewares (CORS) · plugins
|
||
|
||
docs/ Source material: ToR, approved EN/AR draft copy, brand guideline. READ ONLY.
|
||
```
|
||
|
||
Data flow: browser → React Router SSR loader → Strapi REST (`~/lib/strapi`, server only) →
|
||
PostgreSQL. `STRAPI_TOKEN` never reaches the browser (`~/lib/env.server.ts` is `.server`, and
|
||
the bundler enforces it). Every page falls back to bundled draft copy when the CMS is
|
||
unreachable — the site must never 500 because Strapi is down.
|
||
|
||
## Hard rules
|
||
|
||
1. **Design tokens are two-tier. Components use tier 2 only.**
|
||
Tier 1 (`app/styles/primitives.css`) holds raw values; tier 2 (`semantic.css`,
|
||
`typography.css`) holds meanings. Never a raw hex, never a tier-1 primitive, never a raw
|
||
`font-size` / `line-height` — use the type-role utilities (`display-sm`, `heading-h2`,
|
||
`body-md`, `label-md`, …).
|
||
Check: `grep -rn "#[0-9a-fA-F]\{6\}\|--color-green\|--color-neutral\|--color-red" apps/web/app/components apps/web/app/routes`
|
||
→ must return nothing.
|
||
2. **Logical properties only. Never `left` / `right`.**
|
||
`ps-` `pe-` `ms-` `me-` `start-` `end-`, `margin-inline-start`, `text-align: start`.
|
||
The site ships RTL Arabic; physical directions silently break it.
|
||
Check: `grep -rnE "\b(pl|pr|ml|mr|left|right)-[0-9]" apps/web/app` → nothing meaningful.
|
||
3. **Surfaces, not a theme.** `:root` *is* the dark surface; `data-surface="light"` flips a
|
||
subtree and repaints itself. Do not add a global theme toggle or a `dark:` variant.
|
||
4. **Figma is READ ONLY.** Only `get_design_context`, `get_screenshot`, `get_metadata`,
|
||
`get_variable_defs`. Never call anything that writes (`use_figma`, `generate_figma_design`,
|
||
`create_new_file`, `upload_assets`, shaders, plugins, …). Before the first
|
||
`get_design_context`, read `skill://figma/figma-design-to-code/SKILL.md` and pass
|
||
`skillNames: "resource:figma-design-to-code"`. File key `yvKHp3Ha2rQmv9kt9tCC7j`.
|
||
5. **Never invent statistics, dates, names or quotes.** If a number is not in `docs/` or in
|
||
the CMS, it does not go on the page. Leave the slot empty or omit the component.
|
||
6. **`docs/` is read-only source material.** Never edit, move or "clean up" those files.
|
||
7. **Both locales, always.** New UI text goes into `app/i18n/messages/en.json` *and*
|
||
`ar.json` with identical keys (the `Messages` type enforces it). A component that hard-codes
|
||
an English string is a bug — that is why `Card.Content`'s `actionLabel` is required.
|
||
8. **EU visibility is contractual.** "Funded by the European Union" badge top-right, project
|
||
logo top-left, the three partner logos always shown together, no standalone partner
|
||
branding. Do not restyle or omit them.
|
||
9. **No long-running servers or installs without being asked.** Do not run `pnpm install`,
|
||
`pnpm dev`, `strapi develop` or `docker compose up` speculatively. If you need a new
|
||
dependency, report it instead of installing it.
|
||
10. **No `?lang=` parameter, ever.** The locale is a URL prefix on both languages.
|
||
|
||
## Where things live
|
||
|
||
| You need | It is in |
|
||
| --- | --- |
|
||
| Tokens, surfaces, type roles | `apps/web/app/styles/` (+ its own `README.md`) |
|
||
| UI components | `apps/web/app/components/ui/{Button,Input,Card,Navigation,Footer,LanguageSwitcher}` |
|
||
| Page modules | `apps/web/app/routes/*.tsx`; shared kits in `routes/_page-kit/`, `routes/_shared/`, `routes/_events-opportunities/` |
|
||
| Route config, slugs | `apps/web/app/routes.ts`, `apps/web/app/lib/route-slugs.ts` |
|
||
| i18n | `apps/web/app/i18n/` (`config.ts`, `paths.ts`, `t.ts`, `format.ts`, `messages/*.json`) |
|
||
| Strapi client, SEO, env | `apps/web/app/lib/{strapi.ts,seo.ts,env.server.ts,route-handle.ts,site-navigation.ts}` |
|
||
| HTML shell, canonical, hreflang | `apps/web/app/root.tsx` |
|
||
| Content types | `apps/cms/src/api/<name>/content-types/<name>/schema.json` |
|
||
| Boot-time locale + permission setup | `apps/cms/src/bootstrap/` |
|
||
| CORS, upload rules, i18n locales | `apps/cms/config/{middlewares.ts,plugins.ts}` |
|
||
|
||
## Conventions that bite
|
||
|
||
- **`meta` reads `loaderData`, not `data`.** In React Router 8, `matches[0].data` is
|
||
`undefined`; use `readRootSeo(matches)` from `~/routes/_shared/listing`.
|
||
- **Do not pass `alternates: true` to `buildMeta`.** `root.tsx` emits the canonical and the
|
||
`hreflang` alternates for every route; a leaf that repeats them duplicates the tags. Use
|
||
`handle.canonicalPath` to override the canonical.
|
||
- **`app/routes.ts` existence-checks section modules** and Vite does not re-evaluate it when a
|
||
file appears — restart the dev server after adding a route module.
|
||
- **Strapi 5 addresses entries by `documentId`**, not by a numeric `id`, and localisation is a
|
||
`locale` query parameter on the same entry.
|
||
- **Submission content types are write-only for the public** (`create`, never `find`). They
|
||
hold personal data. The field is `language`, not `locale` — `locale` is reserved by the i18n
|
||
plugin.
|
||
- **`.server.ts` files never reach the browser.** Anything touching `STRAPI_TOKEN`, the
|
||
database or `process.env` belongs in one.
|
||
- **Licensed fonts are not in the repository** (`.gitignore` excludes `public/fonts/*.woff2`).
|
||
Expect fallback typefaces locally; never commit a font file.
|
||
|
||
## Verifying a change
|
||
|
||
```bash
|
||
pnpm typecheck # react-router typegen + tsc (web; the CMS is checked by its build)
|
||
pnpm --filter web build # the SSR build must succeed
|
||
bash -n scripts/<name>.sh # any shell script you touched
|
||
docker compose config # any docker-compose.yml change (needs the docker CLI)
|
||
```
|
||
|
||
Then, in a browser or with `curl`, on **both** locales:
|
||
|
||
```bash
|
||
curl -sI http://localhost:5173/ # 302 + Vary: Accept-Language
|
||
curl -s http://localhost:5173/en | head -c 200
|
||
curl -s http://localhost:5173/ar | head -c 200 # lang="ar" dir="rtl" in the first bytes
|
||
```
|
||
|
||
Checklist before calling a UI change done: renders in **both** locales; Arabic mirrors
|
||
correctly; no raw hex or tier-1 token; no physical-direction utility; keyboard focus is
|
||
visible; text comes from the message catalogue or the CMS, not from a literal in the
|
||
component.
|