Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,51 @@ jobs:
if-no-files-found: error
retention-days: 7

public-conversion-e2e:
name: Public conversion Playwright E2E
needs: validate
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 11.9.0
run_install: false

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Install Playwright Chromium
run: pnpm exec playwright install --with-deps chromium

- name: Run public conversion acceptance tests
run: >-
pnpm exec playwright test
e2e/playwright/public-conversion.spec.js
--grep-invert @visual

- name: Upload Playwright failure artifacts
if: failure()
uses: actions/upload-artifact@v4
with:
name: international-chinese-platform-public-conversion-e2e-failure
path: |
playwright-report
test-results
if-no-files-found: warn
retention-days: 7

e2e:
name: Cross-role browser E2E
runs-on: ubuntu-latest
Expand Down
58 changes: 51 additions & 7 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
·
<a href="#learning-journeys"><strong>Learning journeys</strong></a>
·
<a href="#public-conversion"><strong>Public experience</strong></a>
·
<a href="#quick-start"><strong>Quick start</strong></a>
·
<a href="#architecture"><strong>Architecture</strong></a>
Expand Down Expand Up @@ -112,6 +114,38 @@ flowchart TB
| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Discover verified teachers and published courses; request lessons; submit work; practice persistent Chinese dialogue; read notifications and grades. | Maintain a professional profile; submit courses; manage appointments; join classrooms; publish, close, and grade assignments. | Verify teachers; review courses; inspect aggregated data, audit activity, and platform-level workflow state. |

<a id="public-conversion"></a>

## Public discovery and student conversion

The public beta no longer treats sign-in as a stranger's first experience. The homepage explains the product first, and visitors can browse verified teachers and published courses returned by the real API without an account. Authentication is requested only when a visitor is ready to book. After a student signs in or registers, the application restores the original teacher page and booking intent.

```mermaid
flowchart LR
Home["Public homepage / "] --> Teachers["Teacher directory /teachers"]
Home --> Courses["Course directory /courses"]
Teachers --> Teacher["Teacher detail /teachers/:teacherId"]
Courses --> Course["Course detail /courses/:courseId"]
Teacher -->|"Request a lesson"| Auth["Sign in or register /login"]
Auth -->|"student + validated same-origin redirect"| Booking["Return to teacher and continue booking"]
Auth -->|"teacher or administrator"| Workspace["Open the role's workspace"]
```

| Route | Authentication | Purpose |
| -------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <code>/</code> | No | Bilingual value proposition, verified teachers, and featured courses. Teacher and course resources fail independently instead of blocking the homepage. |
| <code>/teachers</code>, <code>/teachers/:teacherId</code> | No | Browse active, verified teachers. Search, specialty, minimum rating, and page state remain in the URL. |
| <code>/courses</code>, <code>/courses/:courseId</code> | No | Browse published courses taught by verified teachers. Search, category, and page state remain in the URL. |
| <code>/login?mode=login\|register&redirect=...</code> | No | Semantic sign-in or three-step registration. A student returns to a validated booking path after authentication. |
| <code>/legal/terms</code>, <code>/legal/privacy</code> | No | Versioned public-beta terms and privacy notices. |
| <code>/account-recovery</code>, <code>/403</code>, <code>/404</code> | No | A real support route plus explicit access-denied and not-found states. |

### Locale, redirect, and booking semantics

- <strong>Locale priority</strong>: use the visitor's saved <code>localStorage</code> preference first; otherwise inspect the browser's preferred language. Chinese-language browsers start in Chinese and all others start in English. Switching locale also updates <code>html lang</code> and page metadata.
- <strong>Safe return</strong>: <code>redirect</code> accepts only same-origin paths beginning with one <code>/</code>. Protocol-relative, backslash, cross-origin, and login-loop destinations are rejected. Only a student resumes a booking target; teachers and administrators enter their own workspace with a role explanation.
- <strong>Booking is not a purchase</strong>: displayed prices are references only and the beta does not collect payment. A request becomes an appointment only after the teacher confirms it. On a time conflict, the form preserves input and lets the student choose again instead of creating a duplicate request or pretending to know the teacher's live availability.

<a id="quick-start"></a>

## Quick local start
Expand Down Expand Up @@ -199,8 +233,11 @@ pnpm db:migrate # apply pending numbered PostgreSQL migrations
pnpm db:seed # import idempotent demo data outside production
pnpm db:reset # rebuild local development data; drops the local public schema
pnpm admin:bootstrap # create the first production administrator once
pnpm test:unit # Vitest locale selection, message catalog, and client unit tests
pnpm test:api # Node API, database, and security tests
pnpm check # ESLint + Prettier + API tests + production build
pnpm test:e2e # Playwright public discovery, auth, booking, accessibility, and responsive checks
python e2e/test_workflows.py # Python Playwright cross-role workflows against a production build
pnpm check # ESLint + Prettier + unit/API tests + production build
pnpm backup:create # after production backup setup, create an encrypted PostgreSQL backup
pnpm backup:restore # after production restore setup, restore only with explicit CONFIRM_RESTORE
```
Expand Down Expand Up @@ -258,23 +295,30 @@ For local development, Vite, Fastify, PostgreSQL, and MinIO replace Vercel, Rail

### What CI actually proves

<code>pnpm check</code> runs ESLint, Prettier, the complete Node API/database/security suite, and a Vite production build. The repository currently contains <strong>71 Node tests</strong>. GitHub Actions also starts isolated PostgreSQL and MinIO services, builds the production app, and exercises four cross-role browser journeys:
<code>pnpm check</code> runs ESLint, Prettier, Vitest unit tests, the complete Node API/database/security suite, and a Vite production build. The repository currently contains <strong>71 Node API tests</strong>. GitHub Actions adds two complementary browser gates:

- <code>pnpm test:e2e -- e2e/playwright/public-conversion.spec.js --grep-invert @visual</code> verifies the public homepage, anonymous teacher and course discovery, booking-intent recovery after authentication, recoverable 409 conflicts, registration drafts that never persist passwords or verification codes, keyboard and touch targets, axe results, and horizontal-overflow behavior across breakpoints.
- <code>python e2e/test_workflows.py</code> runs against PostgreSQL, MinIO, and the production build to verify four cross-role workflows:

1. Course submission → rejection → revision → approval → student catalog visibility.
2. Student request → teacher acceptance → dedicated classroom → classroom completion.
3. Teacher publishes → student submits → teacher grades → student sees feedback.
4. Administrator revokes verification → student cannot discover or book → administrator reapproves → teacher becomes public again.

To reproduce browser E2E locally, install <code>e2e/requirements.txt</code> and Playwright Chromium, start the service with the same PostgreSQL, MinIO, and production environment used by the [CI workflow](./.github/workflows/ci.yml), then run <code>python e2e/test_workflows.py</code>. See the [Node tests](./server/test) and [browser E2E](./e2e/test_workflows.py).
The public-conversion suite starts Vite through its Playwright configuration; run <code>pnpm test:e2e:install</code> once to install Chromium. To reproduce the cross-role suite, install <code>e2e/requirements.txt</code> and Python Playwright Chromium, then start the service with the same PostgreSQL, MinIO, and production environment used by the [CI workflow](./.github/workflows/ci.yml). See the [Node tests](./server/test), [public-conversion E2E](./e2e/playwright/public-conversion.spec.js), and [cross-role E2E](./e2e/test_workflows.py).

### Front-end performance and experience budgets

For the public beta, these are release gates rather than aspirational clean-up items: initial JavaScript must remain below 200 KiB gzip, and critical above-the-fold resources below 800 KiB. Mobile Lighthouse targets are LCP below 2.5 seconds, CLS below 0.1, and INP below 200 milliseconds. The interface is checked at 360, 390, 768, 1024, 1280, and 1440 px without page-level horizontal scrolling. Core controls provide at least a 44 × 44 px touch target and respect <code>prefers-reduced-motion</code>.

## Deployment, operations, and beta scope

The recommended public-beta topology is Vercel for the Vue SPA and mail-relay function; Railway for one Fastify API instance, PostgreSQL, and an independent backup Cron; and Cloudflare R2 for private files and encrypted backups. Secrets, R2 CORS, migrations, administrator bootstrap, monitoring, restore drills, and rollback are documented in the [operations manual](./docs/operations.md).

| A good fit today | Not in the current scope |
| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Course projects, portfolio demonstrations, small self-hosted teaching collaboration, and public-beta validation | Payments, formal enrollment and seat deduction, recording, external certificate OCR/verification, paid AI, multi-instance real-time fan-out, and high availability |
| Classrooms that can connect directly or have a configured TURN service | Reliable audio/video behind strict NAT, because TURN is not bundled yet |
| A good fit today | Not in the current scope |
| --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Course projects, portfolio demonstrations, small self-hosted teaching collaboration, and public-beta validation | Payments, formal enrollment and seat deduction, recording, real teacher-certificate upload and external authenticity checking, paid AI, multi-instance real-time fan-out, and high availability |
| Classrooms that can connect directly or have a configured TURN service | Reliable audio/video behind strict NAT, because TURN is not bundled yet |

Course price and capacity are informational only; they are not a payment or seat-allocation system. Until a formal enrollment model exists, assignments for published courses are visible to and submittable by every signed-in student; there is no roster or enrollment authorization layer yet. Before the system can scale horizontally, classroom presence needs cross-instance broadcast and coordination.

Expand Down
Loading
Loading