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
11 changes: 10 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
VITE_API_PREFIX='/api'
# Required at runtime for saved experiments. Obtain through the Vercel Neon integration.
DATABASE_URL=

# Optional: use disabled for an explicitly offline-only build. Default is /api.
VITE_API_PREFIX=/api

# Migration tools only. Keep these in a local .env.local, never in the frontend or Vercel.
CF_ACCOUNT_ID=
CF_KV_NAMESPACE_ID=
CF_API_TOKEN=
23 changes: 0 additions & 23 deletions .eslintrc.cjs

This file was deleted.

78 changes: 44 additions & 34 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -1,47 +1,57 @@
name: Checks and Tests
on: [push]
on:
pull_request:
push:
branches: [main]

jobs:
checks-unit-tests:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Install
run: npm install
permissions:
contents: read

- name: Style and Typing Checks
run: npm run check
concurrency:
group: checks-${{ github.ref }}
cancel-in-progress: true

- name: Unit Tests
run: npm run test:unit
env:
VITE_API_PREFIX: /api

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
jobs:
checks-unit-build:
runs-on: ubuntu-24.04
env:
CYPRESS_INSTALL_BINARY: '0'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- run: npm run check
- run: npm run check:cy
- run: npm run test:unit
- run: npm run build

cypress-run:
runs-on: ubuntu-22.04
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Cypress run
uses: cypress-io/github-action@v5
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
config: baseUrl=http://localhost:4173
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version-file: .nvmrc
cache: npm
- uses: cypress-io/github-action@18016a55674bd02cabf197de5762a1710cb5577c # v7.4.9
with:
install-command: npm ci
config: baseUrl=http://127.0.0.1:4173
build: npm run build
start: npm run preview
env:
VITE_API_PREFIX: /api

- uses: actions/upload-artifact@v3
start: npm run preview -- --host 127.0.0.1
wait-on: http://127.0.0.1:4173
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: failure()
with:
name: cypress-screenshots
path: cypress/screenshots
path: |
cypress/screenshots
cypress/e2e/__image_snapshots__
if-no-files-found: ignore
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,16 @@ node_modules
.env
# Unit test coverage
coverage

# Credentials, deployment state, local databases and exports
.env.*
!.env.template
.vercel
.wrangler
backups/
cypress/videos/
cypress/screenshots/

# Retired Cloudflare local files; history retains the rollback configuration.
/functions/
/wrangler.toml
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18.0.0
24
10 changes: 8 additions & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
"trailingComma": "all",
"printWidth": 100,
"plugins": ["prettier-plugin-svelte"],
"pluginSearchDirs": ["."],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}

This file was deleted.

This file was deleted.

Binary file removed .wrangler/state/v3/kv/KV/db.sqlite
Binary file not shown.
Binary file removed .wrangler/state/v3/kv/KV/db.sqlite-wal
Binary file not shown.
57 changes: 14 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,63 +64,34 @@ Major features, ordered by priority:

## Developing

This application is powered by [`SvelteKit`](https://kit.svelte.dev).

Once you've cloned the repository and installed dependencies by running `npm install`, start the development server:
Use Node.js 24 (`nvm use`) and install the locked dependencies:

```bash
npm ci
npm run dev

# or start the server and open the app in a new browser tab
npm run dev -- --open
```

## Building

To build static HTML files that can be served by any web server run:

```bash
npm run build
```

> You can preview the built app with `npm run preview`.

> The built app can be found in `/build`.

## Tests
The app uses Svelte 5, SvelteKit 3, Vite 8, and TypeScript 6 (the current version supported by SvelteKit and svelte-check). The editor uses Monaco 0.57. UI menus and tabs use Bits UI. Tailwind 3 and daisyUI 3 retain the existing presentation during this infrastructure migration; redesigning the styling system is a separate step.

There are broadly two kinds of automated tests:
Lessons, built-in examples, the simulator, local drafts and ZIP downloads work without a database. Saved experiments use the same-origin SvelteKit `/api/experiments` routes and require `DATABASE_URL` from a Neon Postgres database. Copy `.env.template` to `.env.local` and set the URL locally when working on persistence. Never commit credentials or migration exports.

- Unit tests - powered by Vitest, can be found next to the source code with the `.test.ts` suffix. During the dev, use the command `npm run test:unit:watch`.
- Cypress tests - can be found in the directory `./cypress`. During the dev, use the command `npm run test:cy:open`. These tests also run visual regression and accessibility checks.

All checks and tests can be run using the command `npm run test`.
These roughly match CI checks.

## Backend

The application works completely fine without any backend, but the ability to sign in save experiments requires a backend.

Start by creating a `.env` from the `.env.template`.

Then, you can run the backend and frontend together by running:
## Building and testing

```bash
# runs both backend and frontend
npm start
npm run check
npm run test:unit
npm run build
npm run preview -- --host 127.0.0.1
npm run test:cy
```

## Deployment

Currently, this application uses Cloudflare to both distribute the website and power the backend using Cloudflare Pages.
The production build generates `.vercel/output` using `@sveltejs/adapter-vercel`; it does not need database credentials. Lessons and the new-experiment page are prerendered. Shared experiment pages load on the client, and the API runs on Node.js 24 functions. The PWA precaches application assets and lessons; private API responses are never cached.

To replicate the backend, you can log in using the wrangler:
Unit tests live next to the source and include parser fixtures, simulator behavior, ownership checks, origin checks and migration validation. Cypress retains the existing accessibility and visual-regression checks. CI uses Node.js 24 and `npm ci` and runs typing, coverage, builds and Cypress on pull requests.

```bash
npm run wrangler -- login
```
## Deployment and data migration

You need to change the account_id within `wrangler.toml` and sign up for Cloudflare Workers, see [documentation](https://developers.cloudflare.com/workers/get-started/guide#7-configure-your-project-for-deployment). Then, you can run:
See [the Vercel migration runbook](docs/vercel-migration.md) for project settings, database setup, preserving legacy experiment IDs and anonymous sessions, validation, domain cutover and rollback. Import and test the migration branch before merging it or changing production DNS.

## Browser Support

Expand Down
2 changes: 1 addition & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineConfig } from 'cypress';
import { initPlugin } from '@frsource/cypress-plugin-visual-regression-diff/plugins';
import { initPlugin } from '@pixsame/cypress-plugin-visual-regression-diff/plugins';

export default defineConfig({
viewportWidth: 1280,
Expand Down
5 changes: 2 additions & 3 deletions cypress/e2e/experiment.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,8 @@ describe('Experiment - Hardware IDE', () => {
cy.get('.monaco-editor textarea:first')
.click({ force: true })
.focused()
.type(
'{downArrow}{downArrow}{downArrow}{downArrow}{downArrow}{downArrow}{downArrow}{downArrow}',
)
// Anchor to the document end rather than the cursor position left by a mouse click.
.type('{moveToEnd}{upArrow}{upArrow}')
.type('{end}{leftArrow}{leftArrow}{backspace}{backspace}{backspace}nota');
cy.contains('[role="alert"]', 'contains a loop');
cy.contains('[role="tab"]', 'Tests').contains('2 / 4');
Expand Down
34 changes: 21 additions & 13 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
/// <reference types="cypress" />

import '@frsource/cypress-plugin-visual-regression-diff';
import '@pixsame/cypress-plugin-visual-regression-diff';
import '@testing-library/cypress/add-commands';
import 'cypress-axe';

Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
// @ts-ignore Incorrect 3rd party type
return originalFn(url, {
...options,
onBeforeLoad(win) {
cy.stub(win, 'matchMedia').returns({
matches: false,
addEventListener: () => ({ matches: false }),
});
},
});
});
Cypress.Commands.overwrite(
'visit',
(originalFn, url, options: Partial<Cypress.VisitOptions> = {}) => {
const visitOptions = typeof url === 'string' ? { ...options, url } : url;
return originalFn({
...visitOptions,
onBeforeLoad(win) {
const matchMedia = win.matchMedia.bind(win);
cy.stub(win, 'matchMedia').callsFake((query: string) => {
const result = matchMedia(query);
if (query === '(prefers-color-scheme: dark)') {
Object.defineProperty(result, 'matches', { value: false });
}
return result;
});
visitOptions.onBeforeLoad?.(win);
},
});
},
);
5 changes: 4 additions & 1 deletion cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ Cypress.Screenshot.defaults({ capture: 'viewport' });

Cypress.on('uncaught:exception', (err) => {
// Monaco diff problem
if (err.message.includes('ResizeObserver loop limit')) {
if (
err.message.includes('ResizeObserver loop limit') ||
err.message.includes('ResizeObserver loop completed with undelivered notifications')
) {
return false;
}
});
10 changes: 7 additions & 3 deletions cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"types": ["cypress", "cypress-axe", "@testing-library/cypress", "node"],
"esModuleInterop": true
"esModuleInterop": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": true
},
"include": ["**/*.ts"]
}
Loading