# Development Guide

This document is for contributors and self-hosters who want to customize or run Afilmory from source.

## Workspace Layout

- `apps/web`: React + Vite SPA that consumes `photos-manifest.json` and generated thumbnails under `apps/web/public`.
- `apps/ssr`: Next.js wrapper that injects the manifest for SEO/OG and proxies SPA assets.
- `packages/builder`: Photo pipeline that syncs sources → processes images → writes manifest/thumbnails into the SPA.
- `config.json` + `site.config.ts`: Presentation/content config merged at runtime for both SPA and SSR.
- `builder.config.ts`: Builder pipeline config (storage, concurrency, repo sync, plugins).

## Quick Start (from source)

1. Install dependencies

```bash
pnpm install
```

2. Copy configs and fill values

```bash
cp config.example.json config.json
cp builder.config.default.ts builder.config.ts
```

3. Environment variables (`.env`)

- Storage: `S3_REGION`, `S3_ENDPOINT`, `S3_BUCKET_NAME`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_PREFIX`, `S3_CUSTOM_DOMAIN`, `S3_EXCLUDE_REGEX`
- Repo sync (optional): `GIT_TOKEN`, `BUILDER_REPO_URL`, `BUILDER_REPO_BRANCH`

4. Build/update manifest and thumbnails (writes to `apps/web/src/data/photos-manifest.json` and `apps/web/public`)

```bash
pnpm run build:manifest             # incremental
pnpm run build:manifest -- --force # full rebuild
```

5. Run the app

```bash
pnpm dev              # SPA + SSR
pnpm --filter web dev # SPA only
pnpm --filter @afilmory/ssr dev # SSR wrapper only
```

## Builder Configuration (`builder.config.ts`)

The builder uses `defineBuilderConfig` from `@afilmory/builder`. `builder.config.default.ts` shows the recommended shape:

```ts
import os from 'node:os'
import { defineBuilderConfig, githubRepoSyncPlugin } from '@afilmory/builder'
import { env } from './env.js'

export default defineBuilderConfig(() => ({
  storage: {
    // Switch to { provider: 'local', basePath, baseUrl } for local-only testing.
    provider: 's3',
    bucket: env.S3_BUCKET_NAME,
    region: env.S3_REGION,
    endpoint: env.S3_ENDPOINT,
    accessKeyId: env.S3_ACCESS_KEY_ID,
    secretAccessKey: env.S3_SECRET_ACCESS_KEY,
    prefix: env.S3_PREFIX,
    customDomain: env.S3_CUSTOM_DOMAIN,
    excludeRegex: env.S3_EXCLUDE_REGEX,
    downloadConcurrency: 16,
  },
  system: {
    processing: { defaultConcurrency: 10, enableLivePhotoDetection: true, digestSuffixLength: 0 },
    observability: {
      showProgress: true,
      showDetailedStats: true,
      logging: { verbose: false, level: 'info', outputToFile: false },
      performance: {
        worker: { workerCount: os.cpus().length * 2, timeout: 30_000, useClusterMode: true, workerConcurrency: 2 },
      },
    },
  },
  plugins: [
    githubRepoSyncPlugin({
      repo: {
        enable: false,
        url: process.env.BUILDER_REPO_URL ?? '',
        token: env.GIT_TOKEN,
        branch: process.env.BUILDER_REPO_BRANCH ?? 'main',
      },
    }),
  ],
}))
```

Notes:

- B2/GitHub storage are also supported (see comments in `builder.config.ts`).
- Outputs land in `apps/web/public/thumbnails` and `apps/web/src/data/photos-manifest.json`.

## Site Presentation Config (`config.json`)

`config.json` merges into `site.config.ts` for both SPA and SSR:

- `name` / `title` / `description` / `url` / `accentColor`
- `author`: `{ name, url, avatar }`
- `social`: `{ github?, twitter? }`
- `feed`: supports `folo.challenge.feedId` and `userId`
- `map`: map providers, e.g. `["maplibre"]`
- `mapStyle`: `builtin` or provider-specific style
- `mapProjection`: `globe` or `mercator`

## Common Commands

```bash
# Lint and format
pnpm lint
pnpm format

# Type check (web)
pnpm --filter web type-check

# Build production
pnpm build
```
