# SoftVowels — project conventions A monorepo with two apps: ``` apps/web Astro 5 (static site, Tailwind v4) apps/cms Payload 3 (admin UI + REST/GraphQL) ``` ## Package manager `pnpm` 9. From the repo root: ```bash pnpm install # install both pnpm dev # dev:web pnpm dev:web # Astro only pnpm dev:cms # Payload only pnpm build # build:web pnpm build:cms # build Payload pnpm typecheck # Astro check pnpm format # prettier pnpm format:check # prettier --check ``` ## Source paths - `apps/web/src/components/{layout,home,article,category,search,newsletter,ui}/` — components - `apps/web/src/layouts/` — page layouts - `apps/web/src/lib/` — Payload client, types, SEO, formatting - `apps/web/src/styles/` — `global.css` (Tailwind v4 `@theme` tokens) and `prose.css` - `apps/web/public/fonts/` — drop `Geist-Variable.woff2` + `GeistMono-Variable.woff2` here - `apps/cms/src/collections/` — Payload collections - `apps/cms/src/hooks/` — Payload hooks (e.g. `revalidateAstro`) ## Code style - All component files are `.astro` single-file components. Frontmatter (between `---`) is TypeScript. - No comments in code unless explicitly requested. - Tailwind v4 tokens live in CSS (`@theme` in `global.css`). Do not create a `tailwind.config.*` file. - Use `icon` (`astro-icon`) with names like `material-symbols:memory`. Self-host icon font woff2 under `public/icons/material-symbols-outlined.woff2` and wire it via `@font-face` in `global.css` (already declared; add the binary file when ready). - Prefer `border border-border-subtle rounded-md` over arbitrary values to stay on the 4px baseline. - For code blocks in MDX / Lexical output, the article body uses `prose-article` (see `prose.css`). ## Lint / typecheck ```bash pnpm typecheck # astro check pnpm format:check ``` There is no separate ESLint run; the project's quality bar is enforced by `astro check` and Prettier. ## Build outputs - `apps/web/dist/` is bind-mounted into the `web` nginx container (`./apps/web/dist:/usr/share/nginx/html:ro`). The host-side webhook rewrites this folder on demand; the nginx container does not need to be rebuilt. - `apps/cms/dist/` is built by Payload (Next.js) and runs as the CMS Node process. ## Deploy (homeserver) 1. Copy the repo to `/opt/softvowels` on the server. 2. `cp .env.example .env` and fill in the secrets. 3. `docker compose up -d --build`. 4. In Nginx Proxy Manager add two proxied hosts: - `softvowels.example` → `web:80` (force SSL, HSTS) - `cms.softvowels.example` → `cms:3000` (force SSL, HSTS) 5. On the host, install the rebuild webhook: ```bash sudo cp scripts/softvowels-webhook.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now softvowels-webhook ``` Make sure `REBUILD_TOKEN` in `/etc/systemd/system/softvowels-webhook.service` matches `REVALIDATE_TOKEN` in `.env`. ## Revalidate flow 1. Editor publishes in `/admin`. 2. Payload `afterChange` hook POSTs to `http://host.docker.internal:4321/__revalidate?secret=…&collection=…&op=…`. 3. The host-side Node webhook spawns `scripts/rebuild.sh`, which `git pull`s and runs `pnpm --filter web build`. 4. nginx container now serves the new `apps/web/dist/`. ## Self-hosting fonts Drop these two files into `apps/web/public/fonts/`: - `Geist-Variable.woff2` (https://github.com/vercel/geist-font — rename `GeistVF.woff2`) - `GeistMono-Variable.woff2` (rename `GeistMonoVF.woff2`) The `@font-face` rules are already declared in `global.css`. ## Environment Root `.env` (used by `docker compose`): ``` POSTGRES_PASSWORD= PAYLOAD_SECRET= REVALIDATE_TOKEN= WEB_HOSTNAME= CMS_HOSTNAME= ``` `apps/web/.env`: ``` PUBLIC_SITE_URL= PUBLIC_PAYLOAD_API= PAYLOAD_API_INTERNAL= REVALIDATE_TOKEN= ``` `apps/cms/.env`: ``` PAYLOAD_SECRET= DATABASE_URI= PAYLOAD_PUBLIC_SERVER_URL= SERVER_URL= CORS_ORIGINS= ASTRO_REVALIDATE_URL= ASTRO_REVALIDATE_TOKEN= ```