docs(SERVICE): correct event-stream contract and document preview.html
Audited docs against the fork-salvage API surface (PR #17). SERVICE.md fixes: - Event stream: events are the compiler's granular {t, ...} log stream plus service phases, not a fixed capture/generate/verify enum. Corrected the "{seq, t, ...payload}" claim — returned bodies carry `t` but NOT `seq`. Documented that `after` is a seq cursor (DB filters seq > N) and that the in-memory backend ignores `after` and returns the full list each poll. - preview.html: new Preview section — the flat, self-contained generate-time artifact (file-map path `preview.html`, manifest `preview_html`, always present), fetched via /v1/clones/:id/files/preview.html. Documented its arrival on the `generated` event, relative `public/assets/...` resolution through the files route, the authenticated-iframe caveat + proxy recommendation, and the original→preview.html→deployed staging. Stated plainly that it is static/runtime-free: frozen animations, no interactions. - Added the `preview` option and clarified app-preview (built export) vs the flat preview.html. DEPLOY.md migration list (0000/0001/0002) verified accurate — no change. README.md / docs/README.md make no stale API claims — no change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
01e59d96bd
commit
b66a993b35
+67
-10
@@ -31,12 +31,27 @@ Two run modes, same HTTP surface:
|
|||||||
**worker** process consumes the queue, runs the clone, stores artifacts, and the
|
**worker** process consumes the queue, runs the clone, stores artifacts, and the
|
||||||
client polls to completion. This is the production mode.
|
client polls to completion. This is the production mode.
|
||||||
|
|
||||||
In both modes the job emits structured progress events (stage transitions such as
|
In both modes the job emits structured progress events as it runs — the compiler's
|
||||||
`capture`, `generate`, `verify`) as it runs. Poll `GET /v1/clones/:id/events?after=N`
|
granular log stream (`{ event: "captured", ... }`, `{ event: "generated", ... }`,
|
||||||
with the last sequence number you have seen; each event is `{seq, t, ...payload}`.
|
`{ event: "build_start", ... }`, etc.) plus service phases (`{ event: "clone_done" }`,
|
||||||
In production mode events persist in the `job_events` table (migration
|
`{ event: "clone_error", error }`). Every event carries a millisecond wall-clock
|
||||||
`0002_productive_wallflower.sql`), so they survive worker restarts and remain
|
timestamp `t`; the rest of each object is the payload the emitter logged, so field sets
|
||||||
readable after completion.
|
vary by event. There is no fixed enum of event types — treat unknown `event` strings as
|
||||||
|
opaque progress and drive UI off the coarse job `status` from `GET /v1/clones/:id`.
|
||||||
|
|
||||||
|
Poll `GET /v1/clones/:id/events?after=N` → `{ jobId, events: [{ t, ... }] }`. `after` is a
|
||||||
|
**sequence cursor** (a 1-based event count, not a timestamp): pass the number of events
|
||||||
|
you have already consumed to get only newer ones. Caveats:
|
||||||
|
|
||||||
|
- The returned event bodies do **not** include the `seq` value, so track the cursor
|
||||||
|
yourself as a running count of events received.
|
||||||
|
- The in-memory backend **ignores `after`** and returns the full list on every poll — so
|
||||||
|
in that mode you always get all events and must de-duplicate by count. The DB backend
|
||||||
|
honors `after` (`seq > N`).
|
||||||
|
|
||||||
|
In production (DB) mode events persist in the `job_events` table (migration
|
||||||
|
`0002_productive_wallflower.sql`) with a per-job `seq`, so they survive worker restarts
|
||||||
|
and remain readable after completion.
|
||||||
|
|
||||||
## Local development
|
## Local development
|
||||||
|
|
||||||
@@ -92,10 +107,10 @@ POST /v1/signup/request { email } → 202 {message} (s
|
|||||||
POST /v1/signup/verify { token } → 201 {apiKey,message} (consume email token)
|
POST /v1/signup/verify { token } → 201 {apiKey,message} (consume email token)
|
||||||
GET /v1/clones → list (metadata)
|
GET /v1/clones → list (metadata)
|
||||||
GET /v1/clones/:id → status + metadata (fileCount, totalBytes, capture, timings)
|
GET /v1/clones/:id → status + metadata (fileCount, totalBytes, capture, timings)
|
||||||
GET /v1/clones/:id/events?after=N → structured progress events since seq N (poll while running)
|
GET /v1/clones/:id/events?after=N → { jobId, events } — progress events after cursor N (poll while running)
|
||||||
GET /v1/clones/:id/result → the eager CloneResult (text files inline; binaries by URL)
|
GET /v1/clones/:id/result → the eager CloneResult (text files inline; binaries by URL)
|
||||||
|
GET /v1/clones/:id/files/* → stream one generated file (e.g. .../files/preview.html — see Preview)
|
||||||
GET /v1/clones/:id/app-preview/* → serve the clone's built static export when present (404 until built)
|
GET /v1/clones/:id/app-preview/* → serve the clone's built static export when present (404 until built)
|
||||||
GET /v1/clones/:id/files/* → stream one file
|
|
||||||
GET /v1/clones/:id/bundle?format=tgz|zip → the whole app as one archive (302 → S3 when configured)
|
GET /v1/clones/:id/bundle?format=tgz|zip → the whole app as one archive (302 → S3 when configured)
|
||||||
DELETE /v1/clones/:id → purge artifacts
|
DELETE /v1/clones/:id → purge artifacts
|
||||||
GET /healthz → { ok: true } (unauthenticated)
|
GET /healthz → { ok: true } (unauthenticated)
|
||||||
@@ -116,8 +131,10 @@ one-time email link, and `POST /v1/signup/verify` consumes the token, stores
|
|||||||
only the API key's SHA-256 hash in Postgres, stores the verified email in the
|
only the API key's SHA-256 hash in Postgres, stores the verified email in the
|
||||||
key label for attribution, and returns the raw key once.
|
key label for attribution, and returns the raw key once.
|
||||||
|
|
||||||
Normal product `options` are `{ mode?: "single" | "multi", styling?: "tailwind" | "css", framework?: "next" | "vite" }`.
|
Normal product `options` are `{ mode?: "single" | "multi", styling?: "tailwind" | "css", framework?: "next" | "vite", preview?: boolean }`.
|
||||||
`mode` defaults to `"single"`, `styling` defaults to `"tailwind"`, and `framework` defaults to `"next"`. Operational options
|
`mode` defaults to `"single"`, `styling` defaults to `"tailwind"`, and `framework` defaults to `"next"`. `preview`
|
||||||
|
controls the browsable built export served at `/v1/clones/:id/app-preview/` (see **Preview** below); it is on by
|
||||||
|
default for single-page clones, so pass `preview:true` to force it for a multi-page clone. Operational options
|
||||||
remain `{ verify?, asyncVerify?, maxRoutes?, maxCollection?, captureConcurrency?, validationConcurrency?, viewportConcurrency?, noCache? }`; `noCache` is service-level and
|
remain `{ verify?, asyncVerify?, maxRoutes?, maxCollection?, captureConcurrency?, validationConcurrency?, viewportConcurrency?, noCache? }`; `noCache` is service-level and
|
||||||
bypasses the cache. Deprecated aliases (`multiPage`, `humanizeMode`) and dev-only escape
|
bypasses the cache. Deprecated aliases (`multiPage`, `humanizeMode`) and dev-only escape
|
||||||
hatches are still accepted for compatibility, but are not part of the normal product surface.
|
hatches are still accepted for compatibility, but are not part of the normal product surface.
|
||||||
@@ -132,6 +149,46 @@ attaches the verify report afterward while the worker still has the run artifact
|
|||||||
current async QA path; a post-hoc verify endpoint would require persisting full capture artifacts,
|
current async QA path; a post-hoc verify endpoint would require persisting full capture artifacts,
|
||||||
not just the generated app bundle.
|
not just the generated app bundle.
|
||||||
|
|
||||||
|
## Preview
|
||||||
|
|
||||||
|
Every clone emits a flat, self-contained **`preview.html`** at generate time — it is a
|
||||||
|
regular entry in the result file map (path `preview.html`, at the app root), and the
|
||||||
|
manifest records it as `preview_html: "preview.html"` (always present). It exists as soon
|
||||||
|
as the compiler's `generate` step finishes emitting the file map — well before any Next
|
||||||
|
build or deploy — so a consumer can show the cloned page within seconds. Its arrival is
|
||||||
|
visible in the event stream as a `{ event: "generated", ... }` event.
|
||||||
|
|
||||||
|
Fetch it through the ordinary files route:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -H "authorization: Bearer $DITTO_API_KEY" \
|
||||||
|
"$DITTO_API_URL/v1/clones/$JOB_ID/files/preview.html" -o preview.html
|
||||||
|
```
|
||||||
|
|
||||||
|
**What it is — and isn't.** `preview.html` is a single HTML file with inline CSS and **no
|
||||||
|
JavaScript** (no runtime scripts). It renders the captured page statically: animations are
|
||||||
|
frozen at their captured start frame, Lottie/`<video>` mounts show their captured
|
||||||
|
poster/first-frame still, and nothing is interactive — no menus, no hover, no scroll
|
||||||
|
effects. It is a faithful *picture* of the page, not a working app. For interaction, use the
|
||||||
|
built export (`/v1/clones/:id/app-preview/`) or the deployed app.
|
||||||
|
|
||||||
|
**Relative assets.** `preview.html` references its images and fonts with relative paths
|
||||||
|
(`public/assets/cloned/...`), so those sub-paths resolve against the same files route the
|
||||||
|
HTML itself came from (`/v1/clones/:id/files/public/assets/cloned/...`). Serve or proxy the
|
||||||
|
file under that mount and the assets load with no rewriting.
|
||||||
|
|
||||||
|
**Iframe auth caveat.** Because the files route is authenticated (when `API_KEYS`/DB keys
|
||||||
|
are set), you cannot drop `.../files/preview.html` straight into an `<iframe src=...>` — the
|
||||||
|
browser can't attach your `Authorization` header to the iframe's asset sub-requests, so
|
||||||
|
images and fonts 404. Front it with a small server-side proxy that injects the key and
|
||||||
|
re-serves both the HTML and its `public/assets/...` sub-paths from your own origin, then
|
||||||
|
point the iframe at the proxy.
|
||||||
|
|
||||||
|
**Intended staging.** The preview is the middle rung of a three-step reveal for a consumer
|
||||||
|
UI: show the **original site** first (instant), swap to **`preview.html`** the moment
|
||||||
|
`generate` finishes (seconds — a static, frozen likeness), then swap to the **deployed app**
|
||||||
|
once the build + deploy completes (fully interactive).
|
||||||
|
|
||||||
**Incremental clone (single → multi, for speed).** Clone one URL single-page first
|
**Incremental clone (single → multi, for speed).** Clone one URL single-page first
|
||||||
(fast app back), then POST the **same URL** with `{ mode: "multi" }` — the second call
|
(fast app back), then POST the **same URL** with `{ mode: "multi" }` — the second call
|
||||||
reuses the first's entry capture (no re-capture of page 1), crawls + captures only the
|
reuses the first's entry capture (no re-capture of page 1), crawls + captures only the
|
||||||
|
|||||||
Reference in New Issue
Block a user