The comment implied the old-space limit prevents a VM-level OOM. It only
makes the V8 heap limit explicit; measurements showed the package build
completing under a 1 GiB container limit, not a guarantee against host OOM.
Claude-Session: https://claude.ai/code/session_01DYifP8wM4XJQ3Hc2qsF6zf
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(docker): build workspace packages in the builder stage, not during install
The root postinstall builds nine workspace packages inside `pnpm install`.
In the Docker deps stage that step peaks above 1 GiB, almost entirely from
the importer's rollup + terser pass, which exhausts small Docker VMs and
hangs the build (a 1 GiB container fails with a JS heap OOM).
- deps: `pnpm install --frozen-lockfile --ignore-scripts`, so the stage only
resolves and links dependencies (peak ~1051 MiB -> ~315 MiB).
- builder: run the same chain explicitly via the new `build:packages` script,
with `--max-old-space-size=1024` so a runaway build fails with a clear heap
error instead of taking down the VM; public/vendor is produced here now.
- `postinstall` delegates to `build:packages`, so local installs are unchanged.
- CI: build the deps + builder stages when an image input changes; the main
Dockerfile was not built in CI before.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DYifP8wM4XJQ3Hc2qsF6zf
* ci(docker): build the main image for any non-docs change
The builder stage copies the whole build context, so application sources
and config such as next.config.ts are image inputs too. Only skip the
image build when every changed file is documentation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DYifP8wM4XJQ3Hc2qsF6zf
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* fix(docker): create /app/data owned by the runtime user before dropping privileges
* docs(deployment): note one-time ownership repair for pre-existing data volumes
---------
Co-authored-by: wyuc <wang-yc24@mails.tsinghua.edu.cn>
* fix: enable Pro workbench flag in Docker builds; allow non-TLS localhost cookie
Two deployment fixes discovered while self-hosting with Docker Compose:
1. Expose NEXT_PUBLIC_PRO_WORKBENCH_ENABLED as a Docker build arg.
Persistence and other NEXT_PUBLIC_* flags are already wired through
Dockerfile + docker-compose.yml, but the Pro workbench entry flag was
missing, so Docker deployments could not enable the workbench at all.
2. Add COOKIE_SECURE opt-out for the anonymous owner cookie.
Production builds always set `Secure` on the anonymous_id cookie. Safari
refuses to store Secure cookies served over plain http://localhost (it
does not special-case localhost like Chromium/Firefox), so every request
minted a fresh anonymous owner and owner-scoped document writes were
rejected with 403 — scenes never persisted and generation appeared to
hang after the first scene. COOKIE_SECURE=0 lets plain-HTTP deployments
opt out; the default (Secure in production) is unchanged.
Documents COOKIE_SECURE in .env.example alongside the persistence opt-ins.
* fix: share the COOKIE_SECURE opt-out with the Server Action cookie mint
Review follow-up on the COOKIE_SECURE opt-out.
- Extract anonymousCookieSecure() in owner.ts and use it in
lib/workbench/workspace-actions.ts, which re-implements the anonymous_id
mint: with COOKIE_SECURE=0 that path still sent a Secure cookie, so a
Server Action running before any /api/agent/* request (e.g. deleting a
workspace session) minted an ephemeral owner in Safari.
- State the opt-out accurately in owner.ts; the previous comment claimed the
flag never forces Secure off.
- Add the regression test beside the existing production case (verified it
fails when the opt-out is dropped).
- .env.example: document the security cost of dropping Secure and that only
the exact value 0 disables it.
---------
Co-authored-by: wyuc <wang-yc24@mails.tsinghua.edu.cn>
* feat(video-export): add deterministic Quiz/PBL cover cards
Replace unsupported placeholders with authored-metadata cover cards that
stay independent of learner progress, and pin dense-layout evidence for
720p/480p.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(video-export): add configurable localized cover CTA
Replace the inert Start Quiz affordance with a destination-configurable
CTA (NEXT_PUBLIC_VIDEO_EXPORT_CTA_DESTINATION), including Unicode/bidi
display guards.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(ci): require Hyperframes video-export gates
Lock CI/Docker contracts so cover materialization and Hyperframes sample
lint stay required for export changes.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(ci): move HF_E2E_DIR onto e2e steps
GitHub rejects runner.* in job-level env, which aborted the CI workflow
with zero jobs. Keep the sample dir on the materialize/lint steps only.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore(video-export): tidy CI invoke and panel-box export
Run the Hyperframes materialize step through pnpm exec vitest like the
cover-card guardrail above it; the e2e job already pins Node 22. Stop
re-exporting PBL_PANEL_DESIGN_BOX from the public barrel — only the
layout browser test needs it, and it can import the emitter module
directly.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: 杨慎 <117187635+cosarah@users.noreply.github.com>
Co-authored-by: wyuc <wang-yc24@mails.tsinghua.edu.cn>
Refresh the Chinese documentation against current functionality, synchronize all localized guides, document deployment and configuration behavior, and align the PPTX import feature flag implementation.
* feat(persistence): one-command server-backed stack — embedded API + compose postgres profile
docker compose --profile server-persistence up --build runs exactly two
containers: the app (persistence HTTP server embedded at /api/persistence,
enabled only when DATABASE_URL is set — unset keeps today's browser-only
behavior byte-for-byte) and PostgreSQL 16. Client bootstrap configures both
storage seams against the same-origin route when NEXT_PUBLIC_PERSISTENCE=1,
riding the existing lazy migrations so prior browser data reaches the
server per-course on first open. Dev auth (bearer token + client-asserted
learner header) lives in one overridable module and is loudly documented
as development-grade. Verified end-to-end against the real stack:
document PUT/GET and runtime session create land in Postgres; bad tokens 401.
Co-authored-by: Codex <codex@openai.com>
* fix(persistence): review round — structural bootstrap ordering, retryable init, honest failure surfaces
The bootstrap side-effect import moves into the two storage seam entry
modules, so any module graph that can resolve a store necessarily
evaluates it first — no client-entry ordering luck, and the two configure
calls are all-or-nothing. A failed server init clears its cached promise
(next request retries instead of permanent 500s). listStages rethrows
backend failures and the dashboard surfaces a persistence-unavailable
error instead of rendering an empty list that reads as data loss. Docs
state the build-time nature of NEXT_PUBLIC_PERSISTENCE, the real token
boundary (any visitor can extract it and impersonate any learner —
localhost/trusted-network only), and the correct postgres password
rotation. Dev-token compare is timing-safe; the handler singleton lives
on globalThis so HMR cannot leak pools.
Co-authored-by: Codex <codex@openai.com>
* fix(persistence): review response — no-confidentiality wording + adapter round-trip coverage
The token docs now state plainly that the NEXT_PUBLIC token is compiled
into the public bundle and provides no confidentiality or user isolation
(its only role is keeping unrelated scanners off a trusted network). The
Fetch/Node adapter — the route's most bug-prone code — gains a round-trip
integration test: PUT body streaming, 201 with multi-value headers, 204
empty body, and encoded path segments reaching the handler un-decoded.
---------
Co-authored-by: Codex <codex@openai.com>
Fix Docker builds by making the deps stage copy scripts before pnpm install, then carrying the generated public/vendor importer bundle from deps into the builder stage.
- Add README.md (English) and README-zh.md (Chinese) with banner, GIF
demos, feature showcase, use cases, and quick start guide
- Add Dockerfile (multi-stage build), docker-compose.yml, .dockerignore
- Add .env.example with all supported environment variables
- Add MIT LICENSE file
- Move all README assets into assets/ directory
- Update i18n: new slogan and input placeholder text
- Add Vercel one-click deploy button and instructions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>