# videocompressor.online — Backend Architecture Closeout

Date: 2026-06-03
Task: t_0a08fafe
Tenant: site-videocompressor-20260603
Domain: videocompressor.online
Owner: 墨枢

## 0. Verdict

architecture_verdict: STATIC_CLIENT_SIDE_GO

v0 should ship as a static/client-side-first Cloudflare Pages app. The video compression path must run in the browser by default; Cloudflare Workers must not perform video transcoding in v0.

Backend scope is intentionally small:

- No server-side video upload/compression endpoint in v0.
- No D1 user/job/order schema in v0.
- No R2 temporary media storage in v0.
- No Queue / Durable Object / Cron transcoding flow in v0.
- Optional Worker/Pages Functions only for safe non-media endpoints: runtime config, health/readiness, robots/sitemap if not static, and analytics-safe event collection/proxy if the frontend cannot call the analytics provider directly.

Reason: the product’s market and compliance edge is no-upload local compression. Adding backend transcoding in v0 would increase privacy, cost, retention, abuse, and runtime risk while weakening the core SEO message.

## 1. Inputs read

- Research: `/root/.hermes/reports/site-videocompressor-20260603/research-v0.md`
- PRD: `/root/.hermes/reports/site-videocompressor-20260603/prd-v1.md`
- SEO matrix: `/root/.hermes/reports/site-videocompressor-20260603/seo-matrix-v0.md`
- Pricing: `/root/.hermes/reports/site-videocompressor-20260603/pricing-v0.md`
- Compliance: `/root/.hermes/reports/site-videocompressor-20260603/compliance-v0.md`
- Design handoff: `/root/.hermes/reports/site-videocompressor-20260603/design/HANDOFF.md`
- Design acceptance: `/root/.hermes/reports/site-videocompressor-20260603/design-acceptance.md`
- DNS latest: `/root/.hermes/reports/site-videocompressor-20260603/dns-watchdog-latest.json`

Parent design gate result: DESIGN_GO. No P0 design blockers.

## 2. Cloudflare architecture

Recommended v0 deployment:

```text
User browser
  ├─ downloads static app assets from Cloudflare Pages / CDN
  ├─ chooses local video file
  ├─ compresses locally with WebCodecs-first path
  ├─ optionally uses ffmpeg.wasm fallback if frontend spike accepts bundle/memory cost
  └─ downloads compressed MP4 via Blob/Object URL

Cloudflare
  ├─ Pages static app: HTML/CSS/JS/assets/legal pages
  ├─ optional Pages Function / Worker: /api/config
  ├─ optional Pages Function / Worker: /api/health
  ├─ optional Pages Function / Worker: /api/events, only if privacy-safe analytics proxy is needed
  └─ DNS/SSL/CDN/security
```

Do not build this in v0:

```text
Browser -> /api/compress -> Worker FFmpeg -> R2 temporary object -> Queue -> Worker download
```

Ordinary Cloudflare Workers are not the right runtime for large video FFmpeg jobs. If cloud transcoding becomes necessary later, use R2 + Queues + a dedicated long-running compute/transcoding runtime, then re-open privacy/retention/pricing/compliance.

## 3. Required frontend compression contract

P0 compression engine:

- WebCodecs-first for desktop Chrome/Edge.
- Output target: MP4/H.264 + AAC first.
- Input support copy: P0 recommended MP4/H.264; other formats only after real tests.
- MP4 muxing must be explicit; WebCodecs alone does not solve final MP4 container output.
- ffmpeg.wasm fallback is allowed only after frontend validates:
  - bundle size and lazy-loading strategy;
  - memory use on representative files;
  - initialization time;
  - failure behavior on mobile/Safari;
  - cancellation and worker cleanup.

Target-size algorithm:

- Presets: Discord 10MB, WhatsApp 16MB, Email 25MB, Instagram recommended resolution/quality, custom MB.
- Treat target size as an aim, not a guarantee.
- If output misses target, show `target_missed`, actual output size, and next actions.
- Do not hide target misses inside a generic success state.

Minimum UI states that must be wired to real logic:

- empty
- selected
- unsupported
- ready
- processing
- success
- target_missed
- failed
- mobile_warning

## 4. Backend/data shell

v0 has no durable media/job/user/order data model. That is a decision, not an omission.

### 4.1 No D1 tables required for v0

Do not create these in v0:

- users
- sessions
- orders
- compression_jobs
- media_objects
- upload_sessions
- usage_records
- rate_limits for local compression

Reason: there is no login, no payment, no server compression, and no server-side media job. Creating unused tables would imply product behavior that does not exist and would increase maintenance surface.

### 4.2 Optional static/runtime config

If the frontend needs a runtime config endpoint, expose only non-sensitive constants:

`GET /api/config`

Response:

```json
{
  "mode": "client_side_local_v0",
  "uploadCompressionEnabled": false,
  "checkoutEnabledPlans": [],
  "presets": [
    { "id": "discord_10mb", "targetMb": 10, "route": "/compress-video-for-discord" },
    { "id": "whatsapp_16mb", "targetMb": 16, "route": "/compress-video-for-whatsapp" },
    { "id": "email_25mb", "targetMb": 25, "route": "/" },
    { "id": "instagram", "recommendedResolution": "1080p", "route": "/compress-video-for-instagram" }
  ],
  "recommendedBrowser": "desktop Chrome or Edge",
  "recommendedFileSizeCopy": "Under 500MB on desktop Chrome/Edge, pending QA calibration"
}
```

This endpoint can also be replaced by a static JSON file if all values are build-time constants.

### 4.3 Optional health endpoint

`GET /api/health`

Response:

```json
{
  "ok": true,
  "service": "videocompressor.online",
  "mode": "static_client_side_v0",
  "serverSideCompression": false
}
```

Health must not claim WebCodecs/ffmpeg success. It only proves the deployment/runtime is reachable.

### 4.4 Optional analytics-safe event endpoint

Use only if needed. Prefer Plausible or another privacy-safe provider with direct frontend events. If proxying through Worker, only bucketed events are allowed.

`POST /api/events`

Allowed event names:

- `file_selected`
- `preset_selected`
- `compression_started`
- `compression_completed`
- `compression_failed_reason`
- `download_clicked`
- `cta_click`
- `faq_open`
- `privacy_link_click`

Allowed fields:

```json
{
  "event": "compression_completed",
  "route": "/compress-video-for-discord",
  "preset": "discord_10mb",
  "input_size_bucket": "25-100MB",
  "output_size_bucket": "<=10MB",
  "duration_bucket": "30s-2m",
  "target_size_bucket": "<=10MB",
  "device_class": "desktop",
  "browser_family": "chromium",
  "resolution_option": "720p",
  "quality_option": "balanced",
  "success_target_met": true
}
```

Forbidden fields:

- filename
- local path
- video content
- frame/thumbnail/waveform
- Blob/Object URL
- raw file hash or media fingerprint
- exact file size
- exact duration
- exact bitrate
- exact codec profile
- exact resolution if not bucketed
- custom IP field
- screenshots/session replay from tool area

Validation rule: reject or drop any unknown keys. Do not log rejected payload bodies verbatim.

## 5. API contract for frontend handoff

If frontend implements with zero dynamic endpoints, this section becomes a static contract instead of Worker routes.

```ts
export type AppMode = 'client_side_local_v0';

export interface PublicConfig {
  mode: AppMode;
  uploadCompressionEnabled: false;
  checkoutEnabledPlans: [];
  presets: Array<
    | { id: 'discord_10mb'; targetMb: 10; route: '/compress-video-for-discord' }
    | { id: 'whatsapp_16mb'; targetMb: 16; route: '/compress-video-for-whatsapp' }
    | { id: 'email_25mb'; targetMb: 25; route: '/' }
    | { id: 'instagram'; recommendedResolution: '1080p'; route: '/compress-video-for-instagram' }
  >;
  recommendedBrowser: 'desktop Chrome or Edge';
  recommendedFileSizeCopy: string; // QA must calibrate before launch.
}

export interface SafeAnalyticsEvent {
  event:
    | 'file_selected'
    | 'preset_selected'
    | 'compression_started'
    | 'compression_completed'
    | 'compression_failed_reason'
    | 'download_clicked'
    | 'cta_click'
    | 'faq_open'
    | 'privacy_link_click';
  route: string;
  preset?: 'discord_10mb' | 'whatsapp_16mb' | 'email_25mb' | 'instagram' | 'custom';
  input_size_bucket?: '<10MB' | '10-25MB' | '25-100MB' | '100-500MB' | '500MB-1GB' | '>1GB';
  output_size_bucket?: '<10MB' | '10-25MB' | '25-100MB' | '100-500MB' | '500MB-1GB' | '>1GB';
  duration_bucket?: '<30s' | '30s-2m' | '2-10m' | '>10m';
  target_size_bucket?: '<=10MB' | '11-16MB' | '17-25MB' | '26-50MB' | '>50MB';
  device_class?: 'desktop' | 'tablet' | 'mobile';
  browser_family?: 'chromium' | 'firefox' | 'safari' | 'other';
  resolution_option?: 'original' | '1080p' | '720p' | '480p';
  quality_option?: 'high' | 'balanced' | 'smallest';
  success_target_met?: boolean;
  reason?: 'unsupported_browser' | 'unsupported_codec' | 'memory' | 'timeout' | 'mux_error' | 'user_cancelled' | 'unknown';
}
```

## 6. Routes and indexability contract

P0 index routes:

- `/`
- `/mp4-compressor`
- `/compress-video-for-discord`
- `/compress-video-for-whatsapp`
- `/compress-video-for-instagram`
- `/privacy-video-compressor`
- `/privacy`
- `/terms`

Redirects or noindex until unique implementation exists:

- `/no-upload-video-compressor` -> 308 `/privacy-video-compressor`, unless unique content is written.
- `/online-video-compressor` -> 308 `/`, unless unique page is built.
- `/free-video-compressor` -> 308 `/`, unless unique page is built.
- Unsupported format/platform pages must be noindex and excluded from sitemap.

Each P0 route must have either the full tool or a preset/deep-link into the tool. Do not index placeholder pages.

## 7. Cloudflare / DNS status

Known zone data:

- zone_id: `c5fd47553bd8cfc704ec2055aa06f78b`
- required nameservers: `coraline.ns.cloudflare.com`, `nico.ns.cloudflare.com`
- latest watchdog status: `active`
- `dig +short NS videocompressor.online` observed: `coraline.ns.cloudflare.com. nico.ns.cloudflare.com.`

Production DNS activation is no longer the apparent blocker. Frontend/deploy should still verify Pages custom domain, SSL strict, canonical host, robots, sitemap, and CDN cache after the actual app is deployed.

## 8. Cloudflare settings plan for deploy owner

Before any production CF mutation, run Plan-before-Execute and read current state first. Safe target state:

- SSL/TLS: Full (Strict)
- Always Use HTTPS: on
- Crawler Hints: on
- Browser Cache TTL: 4h
- API rate limiting only if dynamic `/api/*` endpoints exist
- Static asset cache for build assets
- No WAF/page rule that interferes with large local JS/WASM asset downloads if ffmpeg.wasm is used

If ffmpeg.wasm is used, ensure the static WASM/core assets are cacheable and served with correct MIME/CORS/COOP/COEP requirements as required by the chosen library. Do not silently load a huge wasm bundle on first paint; lazy-load after user intent.

## 9. Privacy and compliance gates

Frontend and QA must prove:

- Default compression flow uploads no media bytes.
- No thumbnail/frame/screenshot is sent to analytics or logs.
- No filename/local path is sent.
- Analytics use buckets only.
- Platform pages do not imply official affiliation.
- Copy does not contain forbidden claims:
  - unlimited
  - lossless
  - no quality loss
  - support all formats
  - any video size
  - fastest
  - official Discord/WhatsApp/Instagram compressor

Approved no-upload language:

- “No upload by default.”
- “Videos are processed locally in your browser in supported browsers.”
- “We do not collect video content, thumbnails, local file paths, or filenames for analytics.”

Avoid absolute language like “100% private”, “zero data collection”, or “no upload ever”.

## 10. Pricing/payment decision

v0 payment verdict: no Stripe, no checkout, no auth, no paid wall.

If the UI shows Pro/Business, it must be waitlist/contact only.

Public implementation state:

```json
{
  "checkout_enabled_plans": [],
  "auth_required_for_compression": false,
  "server_side_processing": false,
  "billing_provider": null
}
```

Future paid/server-side work requires a new backend phase:

- D1 users/orders/entitlements schema.
- Stripe Checkout + Webhook with raw body signature verification.
- Stripe Tax readiness.
- R2 temporary media storage with deletion SLA.
- Queue + external transcode runtime.
- Abuse handling and storage limits.
- Updated privacy/terms.

## 11. Build/deploy/source state

No project source repository was present under `/root/projects/*videocompressor*` during this backend closeout run, and the Kanban workspace is a scratch directory, not a git worktree.

Therefore this task produced an architecture/data-shell artifact, not a code commit or deployment.

- commit_sha: null
- deploy_url: null
- git_status: scratch workspace is not a git repository
- source_repo_required_for_next_phase: true

If a later worker creates the frontend repo, they must satisfy the code task rule independently: git status, build, commit, push, deploy from the same commit.

## 12. Frontend implementation inputs

Use these as direct inputs:

- Visual truth: `/root/.hermes/reports/site-videocompressor-20260603/design/html`
- Screenshots: `/root/.hermes/reports/site-videocompressor-20260603/design/screens`
- Stitch index: `/root/.hermes/reports/site-videocompressor-20260603/design/stitch/screen-index.json`
- Design handoff: `/root/.hermes/reports/site-videocompressor-20260603/design/HANDOFF.md`
- Content-fit matrix: `/root/.hermes/reports/site-videocompressor-20260603/design/content-fit-matrix.md`
- Assets: `/root/.hermes/reports/site-videocompressor-20260603/design/assets`
- SEO matrix: `/root/.hermes/reports/site-videocompressor-20260603/seo-matrix-v0.md`
- Compliance: `/root/.hermes/reports/site-videocompressor-20260603/compliance-v0.md`

Frontend must decide and document:

1. WebCodecs muxing library choice.
2. Whether ffmpeg.wasm is included or explicitly deferred.
3. Browser support matrix.
4. Real tested file cap. Replace the provisional 500MB copy if tests are lower.
5. Network evidence showing no media upload.
6. Target-size success/miss behavior.
7. Lazy-load/caching strategy for large compression assets.

## 13. Backend scope metadata

```json
{
  "architecture_verdict": "STATIC_CLIENT_SIDE_GO",
  "backend_scope": {
    "video_processing": "browser_local_only_v0",
    "d1_required": false,
    "r2_required": false,
    "queues_required": false,
    "durable_objects_required": false,
    "auth_required": false,
    "payment_required": false,
    "optional_endpoints": ["GET /api/config", "GET /api/health", "POST /api/events"],
    "forbidden_v0_endpoints": ["POST /api/compress", "POST /api/upload", "GET /api/jobs/:id"]
  },
  "privacy_contract": {
    "default_media_upload": false,
    "filename_telemetry": false,
    "thumbnail_or_frame_telemetry": false,
    "analytics_bucketed_only": true
  },
  "next_inputs": [
    "/root/.hermes/reports/site-videocompressor-20260603/design/html",
    "/root/.hermes/reports/site-videocompressor-20260603/design/screens",
    "/root/.hermes/reports/site-videocompressor-20260603/design/stitch/screen-index.json",
    "/root/.hermes/reports/site-videocompressor-20260603/design/HANDOFF.md",
    "/root/.hermes/reports/site-videocompressor-20260603/design/content-fit-matrix.md",
    "/root/.hermes/reports/site-videocompressor-20260603/design/assets",
    "/root/.hermes/reports/site-videocompressor-20260603/seo-matrix-v0.md",
    "/root/.hermes/reports/site-videocompressor-20260603/compliance-v0.md"
  ],
  "artifact_paths": [
    "/root/.hermes/reports/site-videocompressor-20260603/backend-architecture-closeout.md"
  ]
}
```
