docs: update architecture for custom sign-in UI and CI pipeline
All checks were successful
CI / build (push) Successful in 1m15s
CI / docker (push) Successful in 2m52s

- CLAUDE.md: add custom sign-in UI section, update routing table,
  document auto-redirect, CI-built images, no local builds, dev
  override without volume mounts
- Design spec: reflect final implementation — custom Logto image,
  no CUSTOM_UI_PATH, no init containers, bundled favicon

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-06 16:29:37 +02:00
parent 194004f8f9
commit edbb66b056
2 changed files with 34 additions and 33 deletions

View File

@@ -1,4 +1,4 @@
# Custom Logto Sign-In UI
# Custom Logto Sign-In UI — IMPLEMENTED
## Problem
@@ -10,7 +10,7 @@ Replace Logto's sign-in UI with a custom React SPA that visually matches the cam
## Scope
**MVP**: Username/password sign-in only.
**MVP (implemented)**: Username/password sign-in only.
**Later**: Sign-up, forgot password, social login, MFA.
## Architecture
@@ -21,19 +21,22 @@ Separate Vite+React app at `ui/sign-in/`. Own `package.json`, shares `@cameleer/
### Build
Added as a Docker build stage in the SaaS `Dockerfile`. The dist is copied into the SaaS image at `/app/sign-in-dist/`.
Custom Logto Docker image (`cameleer-logto`). `ui/sign-in/Dockerfile` has a multi-stage build: node stage builds the SPA, then copies dist over Logto's built-in experience directory at `/etc/logto/packages/experience/dist/`. CI builds and pushes the image.
**Note**: `CUSTOM_UI_PATH` env var does NOT work for Logto OSS — it's a Logto Cloud-only feature. The correct approach for self-hosted is replacing the experience dist directory.
### Deploy
SaaS entrypoint (`docker/saas-entrypoint.sh`) copies the built sign-in dist to a shared Docker volume (`signinui`). Logto reads from that volume via `CUSTOM_UI_PATH=/etc/logto/custom-ui`.
`docker-compose.yml` pulls the pre-built `cameleer-logto` image. No init containers, no shared volumes, no local builds needed.
### Flow
### Auth Flow
```
User visits /platform/ → SaaS redirects to Logto OIDC
User visits /platform/ → LoginPage auto-redirects to Logto OIDC
→ Logto sets interaction cookie, serves custom sign-in UI
→ User enters credentials → Experience API 4-step flow
→ Logto redirects back to /platform/callback with auth code
→ SaaS app exchanges code for token, user lands on dashboard
```
## Experience API
@@ -60,39 +63,22 @@ Matches cameleer3-server LoginPage exactly:
- `Alert` for errors
- Background: `var(--bg-base)` (light beige)
- Dark mode support via design-system tokens
- Favicon bundled in `ui/sign-in/public/favicon.svg` (served by Logto, not SaaS)
## Files
### New
| File | Purpose |
|------|---------|
| `ui/sign-in/Dockerfile` | Multi-stage: node build + FROM logto:latest + COPY dist |
| `ui/sign-in/package.json` | React 19 + @cameleer/design-system + Vite 6 |
| `ui/sign-in/.npmrc` | Gitea npm registry for @cameleer scope |
| `ui/sign-in/tsconfig.json` | TypeScript config |
| `ui/sign-in/vite.config.ts` | Vite build config |
| `ui/sign-in/index.html` | Entry HTML |
| `ui/sign-in/src/main.tsx` | React mount + design-system CSS import |
| `ui/sign-in/src/App.tsx` | App shell |
| `ui/sign-in/src/SignInPage.tsx` | Login form with Experience API integration |
| `ui/sign-in/src/SignInPage.module.css` | CSS Modules (matches server LoginPage) |
| `ui/sign-in/src/experience-api.ts` | Typed Experience API client |
| `docker/saas-entrypoint.sh` | Copies sign-in dist to shared volume |
| `.gitattributes` | LF line endings |
### Modified
| File | Change |
|------|--------|
| `Dockerfile` | Add `sign-in-frontend` build stage, copy dist to runtime image |
| `docker-compose.yml` | Add `CUSTOM_UI_PATH` to Logto, `signinui` shared volume |
## Docker Timing
Logto starts before SaaS (dependency chain: postgres → logto → logto-bootstrap → cameleer-saas). The `CUSTOM_UI_PATH` volume is initially empty. This is acceptable because:
1. Nobody hits the sign-in page until redirected from `/platform/`
2. By that time, SaaS has started and copied the dist files
3. Logto serves static files at request time, not startup time
| `ui/sign-in/src/experience-api.ts` | Typed Experience API client (4-step flow) |
| `ui/sign-in/src/main.tsx` | React mount + design-system CSS import |
| `ui/sign-in/public/favicon.svg` | Cameleer camel logo (bundled in dist) |
| `ui/src/auth/LoginPage.tsx` | Auto-redirects to Logto OIDC (no button) |
| `.gitea/workflows/ci.yml` | Builds and pushes `cameleer-logto` image |
| `docker-compose.yml` | Uses `cameleer-logto` image (no build directive) |
## Future Extensions