diff --git a/docs/01-project-context.md b/docs/01-project-context.md new file mode 100644 index 0000000..a3b7e65 --- /dev/null +++ b/docs/01-project-context.md @@ -0,0 +1,108 @@ +# 01 — Project Context + +> **Purpose of this document:** Explain *why* this project exists, what problem it solves, how it fits into the MetaCall ecosystem, and what the production goal looks like. + +--- + +## What is MetaCall? + +MetaCall is an **open-source polyglot runtime**. It lets you call Python functions from Node.js, Node.js functions from Python, Ruby from C, TypeScript from Java — all within a single execution environment. The language boundary disappears. + +The **FaaS (Function as a Service)** layer takes this further: it wraps any polyglot function in a REST API. You deploy your code to a server, and it becomes callable over HTTP — no language-specific server boilerplate required. + +MetaCall's production cloud is at **dashboard.metacall.io**. For local development and testing, the open-source [metacall/faas](https://github.com/metacall/faas) server runs the same API on `localhost:9000`. + +--- + +## Why Launchpad Exists + +Before Launchpad, a developer working with local MetaCall FaaS had only two options: + +1. Use the `metacall-deploy` CLI — requires knowing exact arguments and flag combinations +2. Use `curl` directly — requires knowing API endpoint structure and auth headers + +Neither gives you any **visibility** into what is currently deployed. There is no way to see function signatures, view logs, or test a deployed function interactively without writing manual HTTP requests. + +Launchpad solves this by providing a **visual interface** that wraps the exact same API the CLI uses, making the local FaaS server accessible to any developer regardless of their comfort with the terminal. + +--- + +## The MetaCall Ecosystem (Four Repositories) + +Understanding Launchpad requires understanding where it sits. + +``` +metacall/core + │ + │ C library + 13 language loaders + │ Installed as the 'metacall' binary + ▼ +metacall/faas ← The backend Launchpad talks to + │ + │ Node.js/Express server (localhost:9000) + │ Spawns 'metacall' binary workers via IPC + │ REST API defined by @metacall/protocol + ▼ +@metacall/protocol ← Shared npm package + │ + │ All HTTP API types and endpoint wrappers + │ Used by BOTH metacall/deploy AND Launchpad + ▼ +metacall/deploy ← CLI tool (metacall-deploy) + │ + │ TypeScript CLI — same API calls as Launchpad + └─► --dev flag targets localhost:9000 +``` + +**Launchpad is equivalent to `metacall/deploy` in a browser.** They both consume `@metacall/protocol`, they both talk to `metacall/faas`. The difference is interface: CLI vs. visual dashboard. + +--- + +## What Launchpad Does + +Launchpad provides these capabilities: + +| Capability | Description | +|---|---| +| **Authentication** | Login / signup against local FaaS. Token-based session persisted in localStorage | +| **Dashboard Overview** | Server status, deployment count, and recent deployment activity | +| **Deployments List** | All active deployments with status badges, language badges, and quick actions | +| **Deployment Detail** | Metadata, function list, and inline function invocation tester | +| **Deploy Wizard** | Guided 3-step flow for ZIP-based deployments | +| **Repository Deploy** | Deploy from a public Git repository URL and branch | +| **Logs Viewer** | Fetch and display deployment logs | +| **Settings** | Configure FaaS URL and account preferences | +| **Plans** | Subscription plan selection UI (mocked locally) | +| **Chat** | MetaCall team support channel | + +--- + +## What Launchpad Does NOT Do + +Understanding the limits is as important as understanding the features: + +- **Does not replace the FaaS backend.** Launchpad is a frontend only. It needs `metacall/faas` running separately. +- **Does not store any data itself.** All deployment state lives in the FaaS server's memory (and its on-disk app directory). If FaaS restarts, it reloads persisted apps automatically, but Launchpad is stateless. +- **Does not work against the production cloud** (`dashboard.metacall.io`) without modification. That API may differ from the local FaaS API. +- **Logs are partially functional.** The FaaS `POST /api/deploy/logs` endpoint is a stub — it returns a TODO response. See [Known Issues](./06-status-and-issues.md). + +--- + +## Project Goal + +This project was built as part of a contribution to the MetaCall open-source organization. + +The overall goal is to deliver a **stable, production-quality open-source dashboard** that gives local MetaCall FaaS users the same experience as the closed-source `dashboard.metacall.io`. + +Expected outcomes: + +1. Web Dashboard UI (Launchpad) — complete developer workflow in a browser +2. All backend tests passing in `metacall/faas` +3. Smoother CLI integration (`metacall-deploy` → local FaaS) +4. Updated documentation with screenshots and troubleshooting guide + +**Mentors:** Thomas Rory Gummerson, Jose Antonio Dominguez, Alexandre Gimenez Fernandez, Param Siddharth, Raj Aryan + +--- + +*Next: [Architecture →](./02-architecture.md)* diff --git a/docs/02-architecture.md b/docs/02-architecture.md new file mode 100644 index 0000000..15c3803 --- /dev/null +++ b/docs/02-architecture.md @@ -0,0 +1,329 @@ +# 02 — Architecture + +> **Purpose of this document:** Explain how the codebase is organized, what every file and folder does, how data flows from the browser to the FaaS backend and back, and how the key internal systems work. + +--- + +## High-Level Picture + +``` +Browser (Launchpad — React SPA) + │ + │ HTTP via Axios + │ Authorization: Bearer + ▼ +MetaCall FaaS Server (localhost:9000) + │ + │ IPC (stdio / child_process.spawn) + ▼ +MetaCall Core Workers (metacall binary per deployment) + │ + │ Language loaders: Python, Node, Ruby, TypeScript, etc. + ▼ +User-deployed polyglot functions +``` + +Launchpad has **no database, no server, no backend of its own**. It is a pure React SPA. All state lives in the FaaS server. + +--- + +## Tech Stack + +| Layer | Library / Tool | Version | Why | +|---|---|---|---| +| UI framework | React | 19 | Concurrent features, stable ecosystem | +| Language | TypeScript | ~5.9 | Type safety across the entire codebase | +| Build tool | Vite | 7 | Fast HMR, ESM-native, simple config | +| Styling | Tailwind CSS | v4 | Utility-first, no runtime overhead | +| Routing | React Router | v7 | Nested routes, layout composition | +| HTTP | Axios | ^1.13 | Interceptors for auth token injection + 401 handling | +| API contract | `@metacall/protocol` | ^0.1.27 | Shared types with metacall/faas and metacall/deploy | +| Icons | Lucide React | ^0.575 | Consistent, tree-shakable icon set | +| ZIP handling | JSZip | ^3.10 | In-browser ZIP creation for package uploads | +| Toasts | Sonner | ^1.7 | Accessible toast notifications | +| Unit tests | Vitest | ^4 | Vite-native, Jest-compatible | +| E2E tests | Playwright | ^1.48 | Cross-browser, reliable, POM support | +| Linter | ESLint (+ TS + React plugins) | ^9 | Catches type and hook errors | +| Formatter | Prettier | ^3.8 | Uniform style, no arguments | + +--- + +## Full Folder Structure + +``` +Dashboard/ +├── src/ +│ ├── main.tsx # App entry point — mounts into DOM +│ ├── App.tsx # Root component — wraps AppProviders + AppRouter +│ ├── App.css # Global base styles +│ │ +│ ├── app/ # App-level bootstrap only (no business logic here) +│ │ ├── config/ +│ │ │ └── env.ts # Type-safe env config object (VITE_* vars) +│ │ ├── providers/ +│ │ │ └── AppProviders.tsx # BrowserRouter + AuthProvider wrapper +│ │ └── router/ +│ │ └── AppRouter.tsx # All route definitions, guards, lazy loading +│ │ +│ ├── features/ # Feature-first business logic +│ │ ├── auth/ # Login, signup, session +│ │ │ ├── components/ # LoginForm, SignupForm UI components +│ │ │ ├── hooks/ +│ │ │ │ └── useAuth.tsx # AuthContext + AuthProvider + useAuth hook +│ │ │ ├── pages/ +│ │ │ │ ├── LoginPage.tsx +│ │ │ │ └── SignupPage.tsx +│ │ │ ├── services/ # (empty — auth calls live in api-client.ts) +│ │ │ └── types/ # Auth-specific TypeScript types +│ │ │ +│ │ ├── dashboard/ # Home overview page +│ │ │ └── pages/ +│ │ │ └── DashboardPage.tsx +│ │ │ +│ │ ├── deployments/ # Core feature: deploy, inspect, delete, test +│ │ │ ├── components/ +│ │ │ │ └── DeployWizard.tsx # 3-step ZIP deploy wizard +│ │ │ ├── hooks/ +│ │ │ │ ├── useDeployments.ts # Polling hook for /api/inspect +│ │ │ │ └── useDeploymentMonitor.ts # Post-deploy status polling +│ │ │ ├── pages/ +│ │ │ │ ├── DeploymentsPage.tsx # Deployment list +│ │ │ │ ├── DeployPage.tsx # Deploy hub (choose ZIP or repo) +│ │ │ │ ├── DeployRepositoryPage.tsx # Git repo deploy flow +│ │ │ │ └── DeploymentFunctionPage.tsx # Deployment detail + function tester +│ │ │ ├── services/ # (empty — calls go through api-client.ts) +│ │ │ └── types/ # Deployment-specific types +│ │ │ +│ │ ├── logs/ +│ │ │ └── pages/ +│ │ │ └── LogsViewerPage.tsx # Fetches and displays deployment logs +│ │ │ +│ │ ├── settings/ +│ │ │ └── pages/ +│ │ │ └── SettingsPage.tsx +│ │ │ +│ │ ├── plan/ +│ │ │ └── pages/ +│ │ │ └── PlanPage.tsx # Subscription plan UI +│ │ │ +│ │ ├── chat/ +│ │ │ └── pages/ +│ │ │ └── ChatPage.tsx # MetaCall support chat +│ │ │ +│ │ └── errors/ +│ │ └── pages/ +│ │ └── NotFoundPage.tsx +│ │ +│ ├── lib/ # Infrastructure / integrations +│ │ ├── api-client.ts # Axios instance + all API method definitions +│ │ ├── utils.ts # Pure utility functions (cn, formatDate, timeAgo, etc.) +│ │ └── api/ # (directory reserved, currently empty) +│ │ +│ ├── shared/ # Reusable across all features +│ │ ├── constants/ # App-wide constants +│ │ ├── errors/ # Shared error types / utilities +│ │ ├── layout/ +│ │ │ └── AppShell.tsx # Persistent layout: Sidebar + Navbar + main content +│ │ ├── lib/ # Shared pure helpers +│ │ ├── styles/ # Tailwind customizations, global tokens +│ │ ├── types/ # Shared TypeScript types (Deployment, Plans, etc.) +│ │ └── ui/ # Design-system primitives +│ │ ├── LoadingState.tsx # Spinner and skeleton components +│ │ ├── ProgressBar.tsx # Deployment progress with status values +│ │ └── ... # Buttons, badges, modals, etc. +│ │ +│ ├── assets/ # Images, logos, SVGs +│ ├── styles/ # Global CSS entry point +│ └── tests/ # Vitest unit tests +│ +├── tests/ # Playwright E2E tests +│ ├── e2e/ +│ │ ├── auth/ # Login, signup, logout specs +│ │ ├── deployments/ # Deploy, inspect, delete specs +│ │ ├── logs/ # Log viewer specs +│ │ └── smoke/ # Quick sanity checks +│ ├── pages/ # Page Object Models +│ ├── fixtures/ # Shared Playwright fixtures +│ ├── mocks/ # API mock data +│ ├── utils/ # Test helper functions +│ ├── storage/ # Saved auth state (auth.json) +│ └── global/ # globalSetup.ts / globalTeardown.ts +│ +├── public/ # Static files (favicon, etc.) +├── .env.example # Required environment variables template +├── vite.config.ts # Vite build + dev proxy config +├── playwright.config.ts # Playwright test configuration +├── eslint.config.js # ESLint rules +├── .prettierrc # Prettier formatting rules +├── tsconfig.json # TypeScript root config +├── tsconfig.app.json # App-specific TS settings (strict mode) +└── tsconfig.node.json # Node-specific TS settings (vite.config) +``` + +--- + +## Application Bootstrap + +The app mounts in this order: + +``` +index.html + └─► src/main.tsx + └─► (App.tsx) + └─► # BrowserRouter + AuthProvider + └─► # All routes defined here + ├─► # Redirects logged-in users away from /login + └─► # Redirects unauthenticated users to /login + └─► # AppShell Navbar + └─► + lazy-loaded page component +``` + +--- + +## Authentication System + +Auth is managed entirely in `src/features/auth/hooks/useAuth.tsx`. + +### How it works + +``` +User opens app + → AuthProvider reads localStorage + → 'faas_token' + 'faas_user_email' both present? + → Set user = { email } immediately (no network call) + → Either missing? + → user = null → ProtectedRoute redirects to /login + +User submits Login form + → POST /api/auth/login { email, password } + ← { token } + → localStorage.setItem('faas_token', token) + → localStorage.setItem('faas_user_email', email) + → setUser({ email }) + → React Router navigates to / + +User logs out + → localStorage.removeItem('faas_token') + → localStorage.removeItem('faas_user_email') + → setUser(null) + → window.location.href = '/login' +``` + +### 401 Handling + +The Axios response interceptor in `src/lib/api-client.ts` intercepts every 401 response. If the user is not already on `/login` or `/signup`, it: + +1. Removes the token from localStorage +2. Redirects to `/login` via `window.location.href` + +This covers expired tokens, revoked sessions, and server restarts that clear tokens. + +### Important: Local FaaS Auth Behavior + +The local `metacall/faas` server does **not validate tokens**. `GET /validate` always returns `true`. However, the login/signup endpoints **do** check credentials (the FaaS has its own user store). This means: + +- A user must still sign up and log in — they cannot just make up a token +- Once logged in, the token is stored in localStorage and sent on every request +- The FaaS will accept any Bearer token for most endpoints (except login/signup) + +--- + +## Routing System + +All routes are defined in `src/app/router/AppRouter.tsx`. Route components are **lazy-loaded** using `React.lazy()` to reduce initial bundle size. + +### Route Guard Pattern + +Two route guards are implemented as layout components: + +**``** — wraps `/login` and `/signup`. If the user is already logged in, redirects to the page they were trying to reach (stored in `location.state.from`), or `/` by default. + +**``** — wraps all authenticated routes. If user is null, redirects to `/login` with the current `location` in state so the user is returned after login. + +### Full Route Table + +| Path | Component | Guard | Description | +|---|---|---|---| +| `/login` | `LoginPage` | Guest only | Login form | +| `/signup` | `SignupPage` | Guest only | Registration form | +| `/` | `DashboardPage` | Auth required | Overview: stats, server status | +| `/deployments` | `DeploymentsPage` | Auth required | Full deployments list | +| `/deployments/new` | `DeployHubPage` | Auth required | Choose ZIP or repo deploy | +| `/deployments/new/wizard` | `DeployWizardPage` | Auth required | 3-step ZIP upload wizard | +| `/deployments/new/repository` | `DeployRepositoryPage` | Auth required | Git repo deploy | +| `/deployments/:id` | `DeploymentFunctionPage` | Auth required | Detail + function tester | +| `/deployments/:id/logs` | `LogsViewerPage` | Auth required | Log viewer | +| `/settings` | `SettingsPage` | Auth required | User preferences | +| `/plans` | `PlanPage` | Auth required | Subscription plans | +| `/chat` | `ChatPage` | Auth required | MetaCall support | +| `*` | `NotFoundPage` | Auth required | 404 fallback | + +--- + +## API Layer + +### How API calls work + +All HTTP communication lives in **`src/lib/api-client.ts`**. It exposes a single `api` object with typed methods. + +```typescript +// How a component makes an API call: +const deployments = await api.inspect(); + +// What happens internally: +// 1. Axios sends GET /api/inspect with Authorization: Bearer +// 2. Vite proxy forwards to http://localhost:9000/api/inspect (dev only) +// 3. FaaS returns Deployment[] +// 4. Axios interceptor checks for 401 — handles session expiry if needed +// 5. Typed response is returned to the caller +``` + +### Vite Proxy + +In development, `vite.config.ts` proxies `/api` and function call paths to the FaaS server. This avoids CORS issues: + +```typescript +// vite.config.ts (simplified) +proxy: { + '/api': 'http://localhost:9000', + '/:prefix/:suffix': 'http://localhost:9000', +} +``` + +In production builds, `VITE_FAAS_URL` is embedded at build time and used as the `baseURL` for Axios directly. + +### Polling Pattern + +The `useDeployments` hook polls `GET /api/inspect` every 30 seconds. It uses: + +- `AbortController` to cancel in-flight requests on component unmount +- `document.visibilitychange` event to resume polling immediately when the user returns to the tab (instead of waiting for the next interval) +- A `tick` state integer as the `useEffect` dependency to trigger re-fetches imperatively via `refetch()` + +--- + +## Deployment Monitor + +After a new deployment is submitted, `useDeploymentMonitor` polls `GET /api/inspect` every ~1.5 seconds, looking for the deployed suffix to appear with `status: 'ready'` (or `'error'`). It calls `onReady` or `onFailed` callbacks accordingly. + +This gives the wizard its "building → ready" progress feedback. + +--- + +## Design System + +| Token | Value | Usage | +|---|---|---| +| Primary (Cyan) | `#05b2d1` | Buttons, links, active states | +| Secondary (Purple) | `#7b2fff` | Accents, highlights | +| Background | `#0d1117` | Page background | +| Surface | `#161b22` | Cards, panels | +| Border | `#30363d` | Dividers, input borders | +| Font (UI) | Inter | All interface text | +| Font (Code / Logs) | JetBrains Mono | Log viewer, code blocks | + +Design tokens are defined in `src/shared/styles/` and applied via Tailwind CSS v4 utilities. + +--- + +*Next: [Getting Started →](./03-getting-started.md)* diff --git a/docs/03-getting-started.md b/docs/03-getting-started.md new file mode 100644 index 0000000..d61d1e6 --- /dev/null +++ b/docs/03-getting-started.md @@ -0,0 +1,122 @@ +# 03 — Getting Started + +This guide walks you through running Launchpad — the open-source local dashboard for MetaCall FaaS — from scratch. + +--- + +## Prerequisites + +| Requirement | Version | Notes | +|---|---|---| +| Node.js | ≥ 20 | [nodejs.org](https://nodejs.org) | +| npm | ≥ 10 | Bundled with Node | +| MetaCall FaaS | latest | Running on `localhost:9000` | +| MetaCall Core | latest | Required by FaaS to spawn workers | + +> **Launchpad is a frontend.** It does not replace the FaaS backend — it talks to it. You need a running FaaS server first. + +--- + +## Backend + +Launchpad communicates with the local [metacall/faas](https://github.com/metacall/faas) server over HTTP. + +### Start FaaS + +```bash +# Clone and start the FaaS backend +git clone https://github.com/metacall/faas.git +cd faas +npm install +npm start +# → Server ready at http://localhost:9000 +``` + +Once the server is up, `GET http://localhost:9000/api/readiness` returns `200 OK`. + +--- + +## Frontend (Launchpad) + +### 1. Install dependencies + +```bash +cd Dashboard +npm install +``` + +### 2. Configure environment + +```bash +cp .env.example .env +``` + +Edit `.env`: + +```env +# URL of your local MetaCall FaaS server +VITE_FAAS_URL=http://localhost:9000 + +# Optional: static API token (used only in dev/testing flows) +VITE_FAAS_TOKEN=any_string_works_locally +``` + +> **Note:** For normal use, authentication is handled via the login flow. The token in `.env` is only used in specific test/dev scenarios. + +### 3. Start the dev server + +```bash +npm run dev +``` + +Launchpad opens at [http://localhost:5173](http://localhost:5173). + +--- + +## First Run + +1. Navigate to **http://localhost:5173** +2. You will see the **Login** page +3. Sign up for a local account (local FaaS always returns `true` on auth validation) +4. You are now on the **Dashboard** — the home screen showing deployment stats and server status + +--- + +## Available Scripts + +| Script | Description | +|---|---| +| `npm run dev` | Start the Vite dev server | +| `npm run build` | Compile TypeScript and build production bundle | +| `npm run preview` | Preview the production build locally | +| `npm run lint` | Run ESLint | +| `npm run lint:fix` | Auto-fix ESLint violations | +| `npm run format` | Format source files with Prettier | +| `npm run format:check` | Check formatting without writing | +| `npm run typecheck` | Run TypeScript type check without building | +| `npm run unit` | Run Vitest unit tests | +| `npm run unit:watch` | Run Vitest in watch mode | +| `npm run unit:coverage` | Generate test coverage report | +| `npm run test` | Run full Playwright E2E suite | +| `npm run test:smoke` | Run Playwright smoke tests only | + +--- + +## Troubleshooting + +### Launchpad shows "Server Unreachable" + +- Ensure FaaS is running: `curl http://localhost:9000/api/readiness` +- Check that `VITE_FAAS_URL` matches the actual FaaS port + +### Login always fails + +- Local FaaS validates any token as `true`. If login fails, verify the FaaS server is running and reachable + +### Port conflict + +- Vite defaults to `5173`. Run `npm run dev -- --port 3000` to use a different port + +--- + +*Next: [Architecture →](./architecture.md)* diff --git a/docs/04-api-reference.md b/docs/04-api-reference.md new file mode 100644 index 0000000..2898b49 --- /dev/null +++ b/docs/04-api-reference.md @@ -0,0 +1,296 @@ +# 04 — API Reference + +Launchpad communicates exclusively with the [MetaCall FaaS](https://github.com/metacall/faas) server via the `@metacall/protocol` package. All endpoints are prefixed with the FaaS base URL (default: `http://localhost:9000`). + +--- + +## Authentication + +All protected endpoints require an `Authorization` header: + +``` +Authorization: Bearer +``` + +Tokens are obtained from the login/signup flow and stored in `localStorage`. + +> **Local behavior:** The local FaaS server does **not** enforce token validation — `GET /validate` always returns `true`. This means any non-empty token works locally. + +--- + +## Endpoints + +### Health + +#### `GET /api/readiness` + +Returns a simple health check. + +**Response:** `200 OK` + +--- + +### Authentication + +#### `GET /validate` + +Validates the current auth token. + +**Response:** +```json +true +``` + +> Always returns `true` on local FaaS. Used by Launchpad to verify the session on startup. + +#### `GET /api/account/deploy-enabled` + +Checks whether the current account is allowed to deploy. + +**Response:** +```json +true +``` + +--- + +### Deployments + +#### `GET /api/inspect` + +Returns all currently active deployments. + +**Response:** +```json +[ + { + "status": "ready", + "prefix": "hostname", + "suffix": "my-app", + "version": "v1", + "packages": { + "node": { + "scripts": ["index.js"], + "path": "/path/to/app" + } + }, + "ports": [3000] + } +] +``` + +**Status values:** + +| Value | Meaning | +|---|---| +| `create` | Deployment is being initialized | +| `ready` | Deployment is running and callable | +| `error` | Deployment failed | + +--- + +#### `POST /api/package/create` + +Uploads a ZIP archive of a function package. + +**Content-Type:** `multipart/form-data` + +**Fields:** + +| Field | Type | Description | +|---|---|---| +| `id` | string | Unique deployment name | +| `jsons` | JSON string | Array of `MetaCallJSON` descriptors | +| `runners` | JSON string | Array of detected runtime IDs | +| `package` | file | The ZIP archive | + +**Response:** `201 Created` +```json +{ "id": "my-app" } +``` + +--- + +#### `POST /api/deploy/create` + +Creates a new deployment from an uploaded package. + +**Body:** +```json +{ + "id": "my-app", + "env": ["KEY=VALUE"], + "plan": "Essential", + "type": "Package" +} +``` + +**Response:** `200 OK` +```json +{ + "prefix": "hostname", + "suffix": "my-app", + "version": "v1" +} +``` + +--- + +#### `POST /api/deploy/delete` + +Stops and removes a deployment. + +**Body:** +```json +{ "suffix": "my-app" } +``` + +**Response:** `200 OK` + +--- + +#### `POST /api/deploy/logs` + +Fetches logs for a deployment. + +**Body:** +```json +{ + "container": "hostname", + "type": "Package", + "suffix": "my-app", + "prefix": "hostname" +} +``` + +> **Known issue:** This endpoint is currently a stub in local FaaS and returns `"TODO: Implement Logs..."`. Log output is written to `faas/logs/app.log` but not yet exposed here. + +--- + +### Repository Deployments + +#### `POST /api/repository/branchlist` + +Lists branches in a remote Git repository. + +**Body:** +```json +{ "url": "https://github.com/user/repo.git" } +``` + +**Response:** +```json +["main", "develop", "feature/x"] +``` + +--- + +#### `POST /api/repository/filelist` + +Lists files in a specific branch. + +**Body:** +```json +{ + "url": "https://github.com/user/repo.git", + "branch": "main" +} +``` + +**Response:** +```json +["index.js", "metacall.json", "package.json"] +``` + +--- + +#### `POST /api/repository/add` + +Clones a remote repository and registers it as a deployment. + +**Body:** +```json +{ + "url": "https://github.com/user/repo.git", + "branch": "main", + "jsons": [{ "language_id": "node", "path": ".", "scripts": ["index.js"] }] +} +``` + +**Response:** `200 OK` + +--- + +### Function Invocation + +#### `GET /:prefix/:suffix/:version/call/:fn` + +Invokes a deployed function. + +**URL Parameters:** + +| Param | Example | Description | +|---|---|---| +| `prefix` | `hostname` | Machine hostname (from inspect) | +| `suffix` | `my-app` | Deployment name | +| `version` | `v1` | Always `v1` currently | +| `fn` | `add` | Function name to call | + +**Query Params:** + +Pass function arguments as query parameters (or as a JSON body for POST). + +**Example:** +``` +GET /hostname/my-app/v1/call/add?x=2&y=3 +``` + +**Response:** +```json +5 +``` + +> Supports both `GET` and `POST`. Use `POST` with a JSON body for structured arguments. + +--- + +### Billing (Mock Data) + +The following routes return hardcoded mock responses on local FaaS. They exist to support plan selection in the UI. + +| Route | Mock Response | +|---|---| +| `GET /api/billing/plans` | `["Essential", "Essential"]` | +| `GET /api/billing/current` | `"Essential"` | + +> These are local mocks. Production values differ. + +--- + +## Shared Types (`@metacall/protocol`) + +```typescript +type Deployment = { + status: 'create' | 'ready' | 'error'; + prefix: string; // machine hostname + suffix: string; // deployment name + version: string; // always 'v1' + packages: Record; + ports: number[]; +}; + +type MetaCallJSON = { + language_id: LanguageId; + path: string; + scripts: string[]; +}; + +type LanguageId = + | 'node' | 'py' | 'rb' | 'ts' + | 'cs' | 'java' | 'wasm' | 'c' + | 'rs' | 'cob' | 'lua' | 'jl'; +``` + +--- + +*Next: [Testing Guide →](./testing.md)* diff --git a/docs/05-decisions.md b/docs/05-decisions.md new file mode 100644 index 0000000..b1210f0 --- /dev/null +++ b/docs/05-decisions.md @@ -0,0 +1,108 @@ +# 05 — Key Technical Decisions + +> **Purpose of this document:** Explain *why* specific technical choices were made. A new developer should understand the reasoning before they decide to change anything. + +--- + +## Auth: Context + localStorage (not Redux or cookies) + +**Decision:** Authentication state is managed using a single React Context (`AuthContext`) with `localStorage` for persistence. No external state library (Redux, Zustand) is used for auth. + +**Why:** +- The auth state needed here is minimal: `{ user: { email } | null, loading, login, signup, logout }`. This does not justify a full state management library. +- `localStorage` is appropriate for a local development tool. This is not a production banking app — the threat model for a local FaaS dashboard running on `localhost` does not require `HttpOnly` cookies. +- The Axios 401 interceptor handles token expiry centrally, so no component needs to manage session refresh logic. + +**Trade-off:** If the FaaS server is rebuilt in future to support refresh tokens or server-side sessions, the `AuthProvider` and Axios interceptor are the only two files that need to change. + +--- + +## API Layer: Single `api` Object (not per-feature service files) + +**Decision:** All HTTP calls are defined as methods on a single `api` object in `src/lib/api-client.ts`. + +**Why:** +- The project talks to a single backend (`metacall/faas`). There is no multi-service architecture. +- Keeping all API calls in one place makes it trivial to understand the full API surface of the app. +- The `@metacall/protocol` package already provides typed responses — wrapping each method per feature would add abstraction with no benefit. + +**Trade-off:** For a significantly larger app with many backend services, the per-feature service pattern (`features/auth/services/auth.api.ts`) would be better. If Launchpad grows to talk to multiple backends (e.g., a dedicated auth service), this should be refactored. + +--- + +## Routing: React Router v7 with Nested Layouts + +**Decision:** All routes use React Router v7's nested `` pattern. Protected routes are implemented as layout-route guards (``, ``), not higher-order components. + +**Why:** +- The layout-route guard pattern is idiomatic with React Router v6+/v7. It avoids wrapping individual route components and instead uses `` composition. +- This makes the route tree in `AppRouter.tsx` readable as a single source of truth — the nesting itself communicates the auth and layout requirements. +- `` (Navbar + Sidebar) is applied once as a layout route, so it renders consistently for all protected pages without being imported on every page. + +**Trade-off:** Developers unfamiliar with the `` pattern may find it confusing initially. The router file in `AppRouter.tsx` is the single place to look. + +--- + +## Code Splitting: All Pages Lazy-Loaded + +**Decision:** Every page component is loaded via `React.lazy()` and wrapped in ``. + +**Why:** +- Reduces the initial JS bundle. A user opening the login page does not download the DeployWizard or LogsViewer code. +- Vite supports dynamic imports natively with zero extra configuration. +- An `` wraps the Suspense to catch chunk-load failures (e.g., after a new build where the chunk hash changed). + +**Implementation:** `AppRouter.tsx` — all lazy imports are at the top. The `` component shows a "Refresh Page" button when a chunk fails to load. + +--- + +## Deployment Polling: AbortController + visibilitychange + +**Decision:** `useDeployments` polls every 30 seconds using `setInterval`, but also re-fetches immediately when the user returns to the tab (`document.visibilitychange`). In-flight requests are cancelled on unmount using `AbortController`. + +**Why:** +- Polling is the simplest approach for a local dev tool. WebSockets would require FaaS backend changes. +- Without `visibilitychange`, a user who switches away and returns would wait up to 30 seconds to see updated state. +- Without `AbortController`, requests from unmounted components would call `setState` and produce React warnings. + +**Where it lives:** `src/features/deployments/hooks/useDeployments.ts` + +--- + +## Post-Deploy Monitoring: 1.5s Tight Poll + +**Decision:** After submitting a deployment, `useDeploymentMonitor` polls `GET /api/inspect` every 1.5 seconds until the deployment reaches `'ready'` or `'error'`. + +**Why:** +- MetaCall FaaS worker startup is fast for small apps. 1.5 seconds gives near-instant feedback without hammering the server. +- The monitor stops itself (`stopped = true`) as soon as a terminal state is reached. +- A separate monitor hook avoids coupling deployment submission logic with the list polling hook. + +**Where it lives:** `src/features/deployments/hooks/useDeploymentMonitor.ts` + +--- + +## No Server-Side Rendering + +**Decision:** Launchpad is a pure client-side SPA. There is no SSR (Next.js, Remix, etc.). + +**Why:** +- The entire audience is local developers with `localhost` access. SEO and public indexing are irrelevant. +- SSR would require a Node.js server to be deployed alongside the dashboard — adding operational complexity for a local dev tool. +- Vite + React (CSR) gives excellent DX with HMR and zero-config TypeScript. + +--- + +## Tailwind CSS v4 + +**Decision:** Tailwind CSS v4 (not v3) is used. + +**Why:** +- v4 is the current Tailwind major version at the time of development. +- v4 uses CSS-first configuration (no `tailwind.config.js`) and integrates with Vite via `@tailwindcss/vite` plugin. + +**Important for new developers:** Tailwind v4 configuration syntax differs from v3. If you look up Tailwind docs, make sure you are on the v4 documentation. + +--- + +*Next: [Current Status & Known Issues →](./06-status-and-issues.md)* diff --git a/docs/06-status-and-issues.md b/docs/06-status-and-issues.md new file mode 100644 index 0000000..8da1588 --- /dev/null +++ b/docs/06-status-and-issues.md @@ -0,0 +1,135 @@ +# 06 — Current Status & Known Issues + +> **Purpose of this document:** Give the incoming developer or org reviewer an honest, precise picture of what is Partial, what is inPartial, and every known bug or limitation — with enough context to prioritize work. + +--- + +## Feature Completion Matrix + +| Feature | Status | Notes | +|---|---|---| +| Login / Signup | Partial | Works against local FaaS auth endpoints | +| Session persistence | Partial | Token + email stored in localStorage | +| 401 auto-logout | Partial | Axios interceptor handles this globally | +| Dashboard overview page | Partial | Server status, deployment stats | +| Deployment list | Partial | Polling, status badges, language badges | +| Deploy via ZIP wizard | Partial | 3-step flow: upload → configure → monitor | +| Deploy from Git repository | Partial | Branch selection + deploy | +| Deployment detail page | Partial | Function list, metadata | +| Function invocation tester | Partial | In-page JSON arg → call → response | +| Deployment deletion | Partial | With confirmation | +| Logs viewer | Partial | UI Partial, but FaaS logs endpoint is a stub (see Issue #1) | +| Settings page | Partial | FaaS URL, account settings | +| Plans page | Partial | Plan selection UI (locally mocked) | +| Chat page | Partial | MetaCall support interface | +| Deployment polling | Partial | Auto-refresh every 30s + tab focus resume | +| Post-deploy status monitor | Partial | Polls until ready/error | +| Responsive layout | Partial | Works on small screens | +| Error boundaries | Partial | Route-level, with chunk-load recovery | +| Unit tests | Partial | Framework in place, coverage not Partial | +| E2E tests | Partial | Auth + smoke flows covered | + +--- + +## Known Issues + +### Issue 1 — Logs Endpoint is a Stub (Backend, not Frontend) + +**Severity:** Medium +**Location:** `metacall/faas` — `faas/src/controller/logs.ts` +**Symptom:** The Logs Viewer page sends `POST /api/deploy/logs` to FaaS. FaaS returns the string `"TODO: Implement Logs..."`. The logs viewer currently displays this placeholder instead of real log output. + +**Root cause:** The FaaS backend has not implemented the logs endpoint. The FaaS server already writes output to `faas/logs/app.log` via its logger. The implementation just needs to read and filter that file by deployment suffix. + +**Fix location:** `metacall/faas` repository, not in Launchpad. +**Workaround:** Developers can read `faas/logs/app.log` directly on disk. + +--- + +### Issue 2 — Auth is Intentionally Bypassed on Local FaaS + +**Severity:** Low (by design, but needs documentation for new devs) +**Location:** `metacall/faas` — `faas/src/controller/validate.ts` +**Symptom:** `GET /validate` always returns `true` regardless of the token. Most protected FaaS endpoints accept any Bearer token. + +**Important:** This is intentional for the local development server. It is **not** a security issue in local context. However, a new developer testing with curl may be confused when their manual token-less requests succeed. + +**Action for handover:** Document clearly in the project README and Getting Started guide that local FaaS has no real auth enforcement. + +--- + +### Issue 3 — `inspectByName` is a Client-Side Filter, Not a Direct API Call + +**Severity:** Low +**Location:** `src/lib/api-client.ts` — `api.inspectByName()` +**Symptom:** There is no `GET /api/inspect/:suffix` endpoint on FaaS. `api.inspectByName()` calls `api.inspect()` (which returns all deployments) and then filters client-side. + +**Impact:** On the Deployment Detail page and the deployment monitor, every lookup fetches the full list. For local use with a small number of deployments, this is negligible. At scale it would be inefficient. + +**Future fix:** FaaS could expose a filtered inspect endpoint. Until then, the client-side filter is the correct approach. + +--- + +### Issue 4 — `loading` is Hardcoded to `false` in AuthProvider + +**Severity:** Low +**Location:** `src/features/auth/hooks/useAuth.tsx` — line 35 +**Code:** +```typescript +const loading = false; +``` + +**Context:** Auth state is initialized synchronously from localStorage in the `useState` initializer. There is no async auth validation call on startup (unlike apps that verify the token against the server on load). This means `loading` is always `false` — there is no async auth phase to show a spinner for. + +**Note:** This is a deliberate simplification. If a future version adds server-side token validation on startup (e.g., calling `GET /validate` before rendering), `loading` would need to become actual state. + +--- + +### Issue 5 — Deployment Detail Route Uses `:id` but FaaS Uses `suffix` + +**Severity:** Low (cosmetic inconsistency) +**Location:** `AppRouter.tsx` — `/deployments/:id` route +**Context:** The URL parameter is named `:id` but the FaaS API identifies deployments by `suffix`. The detail page extracts the URL param and passes it to `api.inspectByName(suffix)`. It works, but the naming is inconsistent. + +--- + +## Known Backend Bugs (in `metacall/faas`, tracked for awareness) + +These are bugs in the FaaS backend that affect Launchpad behavior. They are documented here so the incoming team knows what to expect and where to look. + +### Backend Bug A — Logger Color Infinite Loop + +**File:** `faas/src/utils/logger.ts` lines 44–58 +**Symptom:** When 17+ simultaneous deployments exist, the FaaS color assignment loop spins forever, freezing the entire FaaS Node.js process. +**Fix:** Replace the random search loop with round-robin: `colorIndex % ANSICode.length`. + +### Backend Bug B — Promise Race on Worker Exit + +**File:** `faas/src/utils/deploy.ts` lines 77–92 +**Symptom:** The `proc.on('exit', ...)` handler fires for every process exit including normal ones, calling `deployReject()` on an already-settled Promise. Silently ignored by JS but hides real crash errors. +**Fix:** Add a `settled` flag — only call `deployReject` if `!settled`. + +### Backend Bug C — `--serverUrl` CLI Flag Broken + +**Location:** `metacall/deploy` — `deploy/src/index.ts` +**Symptom:** The `--serverUrl` CLI flag is parsed but applied after the API client is constructed. It has no effect. +**Workaround:** Use `--dev` flag to target `localhost:9000`. + +--- + +## Open Work Items + +These are features or improvements that are not yet done and should be picked up by the incoming team: + +| Item | Priority | Description | +|---|---|---| +| Logs endpoint implementation | High | Implement `POST /api/deploy/logs` in `metacall/faas` to read from `faas/logs/app.log` | +| Increase unit test coverage | Medium | Current Vitest tests cover utilities but not hooks or page components | +| Expand E2E test coverage | Medium | Auth + smoke flows exist; deployment create/delete flows need tests | +| Server-side token validation on startup | Low | Call `GET /validate` on app load to verify stored token is still valid | +| Deployment suffix-based API filter | Low | Add `GET /api/inspect/:suffix` to FaaS to avoid full list fetch for detail lookups | +| Mobile layout polish | Low | Layout is responsive but some pages have minor wrapping issues on small screens | + +--- + +*Next: [Testing Guide →](./07-testing.md)* diff --git a/docs/07-testing.md b/docs/07-testing.md new file mode 100644 index 0000000..44059b5 --- /dev/null +++ b/docs/07-testing.md @@ -0,0 +1,188 @@ +# 07 — Testing Guide + +Launchpad has two layers of testing: + +| Layer | Tool | Command | +|---|---|---| +| Unit tests | [Vitest](https://vitest.dev/) | `npm run unit` | +| End-to-end tests | [Playwright](https://playwright.dev/) | `npm run test` | + +--- + +## Unit Tests (Vitest) + +### Location + +Unit tests live alongside the source code in `src/tests/`. They cover utilities, hooks, and isolated components. + +### Running + +```bash +# Run once +npm run unit + +# Watch mode (re-runs on file change) +npm run unit:watch + +# Coverage report +npm run unit:coverage +``` + +Coverage output is written to `coverage/` (not committed). + +### Writing a Unit Test + +Create a `.test.ts` or `.test.tsx` file next to the code it tests: + +```typescript +// src/tests/example.test.ts +import { describe, it, expect } from 'vitest'; +import { formatStatus } from '../shared/utils/formatStatus'; + +describe('formatStatus', () => { + it('returns "Ready" for status "ready"', () => { + expect(formatStatus('ready')).toBe('Ready'); + }); + + it('returns "Building" for status "create"', () => { + expect(formatStatus('create')).toBe('Building'); + }); +}); +``` + +### Configuration + +Vitest is configured inside `vite.config.ts`. The test environment is `jsdom` (simulates a browser DOM). + +--- + +## E2E Tests (Playwright) + +### Location + +``` +tests/ +├── e2e/ # Test specs +│ ├── auth/ # Login, signup, logout flows +│ ├── deployments/ # Deploy, inspect, delete flows +│ ├── logs/ # Log viewer +│ └── smoke/ # Quick sanity checks +├── pages/ # Page object models (POM) +├── fixtures/ # Shared test fixtures +├── mocks/ # API mock data +├── utils/ # Helper functions +├── storage/ # Auth state snapshots for reuse +└── global/ # globalSetup.ts / globalTeardown.ts +``` + +### Running + +```bash +# Full E2E suite (requires FaaS running on localhost:9000) +npm run test + +# Smoke tests only +npm run test:smoke + +# Interactive Playwright UI +npm run test:ui + +# Debug mode (step through tests) +npm run test:debug + +# Headed mode (watch browser run) +npm run test:headed +``` + +### Prerequisites + +The Playwright suite expects: +- Launchpad dev server running on port `5173` +- MetaCall FaaS running on port `9000` + +Playwright **auto-starts** the Vite dev server for you (configured in `playwright.config.ts`). + +### Page Object Model + +Tests use the Page Object Model pattern. Each logical page has a corresponding class in `tests/pages/`. + +```typescript +// tests/pages/LoginPage.ts +import { Page } from '@playwright/test'; + +export class LoginPage { + constructor(private page: Page) {} + + async goto() { + await this.page.goto('/login'); + } + + async login(email: string, password: string) { + await this.page.fill('[data-testid="email"]', email); + await this.page.fill('[data-testid="password"]', password); + await this.page.click('[data-testid="submit"]'); + } +} +``` + +### Writing an E2E Test + +```typescript +// tests/e2e/auth/login.spec.ts +import { test, expect } from '@playwright/test'; +import { LoginPage } from '../../pages/LoginPage'; + +test('user can log in with valid credentials', async ({ page }) => { + const login = new LoginPage(page); + await login.goto(); + await login.login('user@example.com', 'password123'); + await expect(page).toHaveURL('/'); +}); +``` + +### Reusing Auth State + +To skip logging in before every test, save a logged-in browser state and reuse it: + +```typescript +// tests/global/globalSetup.ts +// Logs in once and saves the storage state to tests/storage/auth.json +``` + +Then reference it in `playwright.config.ts`: + +```typescript +use: { + storageState: 'tests/storage/auth.json', +} +``` + +### Configuration + +See `playwright.config.ts` in the `Dashboard/` root for: +- Base URL +- Browser targets (Chromium by default) +- Dev server auto-start settings +- Retry counts and timeouts + +--- + +## Test Data IDs + +Launchpad uses `data-testid` attributes on interactive elements so tests are not coupled to CSS classes or text content. When adding new UI, include a `data-testid` on key elements: + +```tsx + + +``` + +--- + +## CI Considerations + +- Unit tests: run on every push, no external dependencies +- E2E tests: require a running FaaS instance — best run in a pre-configured CI environment with a FaaS container + +--- + +*Next: [Contributing →](./08-contributing.md)* diff --git a/docs/08-contributing.md b/docs/08-contributing.md new file mode 100644 index 0000000..b02540a --- /dev/null +++ b/docs/08-contributing.md @@ -0,0 +1,166 @@ +# 08 — Contributing + +Thank you for contributing to Launchpad. This guide covers everything you need to get your changes merged cleanly. + +--- + +## Before You Start + +1. Read [Getting Started](./getting-started.md) to ensure you can run the project locally +2. Read [Architecture](./architecture.md) to understand where code belongs +3. Check the open issues on GitHub before starting work on a large feature + +--- + +## Branch Strategy + +| Branch | Purpose | +|---|---| +| `main` | Stable, always deployable | +| `redesign` | Active development branch for Launchpad | +| `feat/` | New features | +| `fix/` | Bug fixes | +| `docs/` | Documentation changes | +| `chore/` | Tooling, dependency updates, config | + +### Workflow + +```bash +# Start from the active development branch +git checkout redesign +git pull origin redesign + +# Create your feature branch +git checkout -b feat/deployment-filter + +# Work, commit, push +git push origin feat/deployment-filter + +# Open a PR targeting redesign (not main) +``` + +--- + +## Commit Style + +Use [Conventional Commits](https://www.conventionalcommits.org/): + +``` +(): +``` + +**Types:** + +| Type | Use for | +|---|---| +| `feat` | New feature | +| `fix` | Bug fix | +| `docs` | Documentation only | +| `style` | Formatting, no logic change | +| `refactor` | Code change with no feature or bug | +| `test` | Adding or updating tests | +| `chore` | Build, tooling, dependencies | + +**Examples:** + +``` +feat(deployments): add filter by runtime language +fix(auth): redirect to login on token expiry +docs(contributing): add branch strategy section +test(e2e): add smoke test for deployment wizard +``` + +--- + +## Code Standards + +### TypeScript + +- Prefer explicit types over `any` +- Use `interface` for object shapes, `type` for unions and aliases +- Export types from `index.ts` files where appropriate + +### React + +- Use functional components only +- Keep components small and focused — one clear responsibility +- Extract repeated logic into custom hooks in the nearest `hooks/` directory +- Use `data-testid` attributes on key interactive elements + +### File Placement + +| What | Where | +|---|---| +| Feature-specific component | `src/features//components/` | +| Reusable UI primitive | `src/shared/ui/` | +| Reusable hook | `src/shared/hooks/` or `src/features//hooks/` | +| API call | `src/features//services/` | +| Shared type | `src/shared/types/` | +| Global constant | `src/shared/constants/` | + +### Naming + +| Item | Convention | Example | +|---|---|---| +| Component | PascalCase | `DeploymentCard.tsx` | +| Hook | camelCase prefixed `use` | `useDeployments.ts` | +| Service file | camelCase | `deployments.api.ts` | +| Type file | camelCase | `deployment.types.ts` | +| Test file | Same name + `.test` | `DeploymentCard.test.tsx` | + +--- + +## Linting and Formatting + +Run before every commit: + +```bash +npm run lint +npm run format:check +npm run typecheck +``` + +Auto-fix: + +```bash +npm run lint:fix +npm run format +``` + +The project uses: +- **ESLint** with `typescript-eslint`, `react-hooks`, `react-refresh` plugins +- **Prettier** for formatting (config in `.prettierrc`) +- **TypeScript strict mode** (see `tsconfig.app.json`) + +--- + +## Pull Request Checklist + +Before opening a PR, confirm: + +- [ ] Code follows the architecture conventions +- [ ] No TypeScript errors (`npm run typecheck`) +- [ ] No lint errors (`npm run lint`) +- [ ] Code is formatted (`npm run format:check`) +- [ ] Unit tests pass (`npm run unit`) +- [ ] Smoke tests pass (`npm run test:smoke`) +- [ ] New interactive elements have `data-testid` attributes +- [ ] PR targets the correct branch (`redesign`, not `main`) +- [ ] PR description explains **what** changed and **why** + +--- + +## Reporting Issues + +When filing a bug report, include: + +1. Steps to reproduce +2. Expected behavior +3. Actual behavior +4. Browser and OS +5. FaaS version (output of `npm list` in the `faas/` directory) +6. Relevant console errors or screenshots + +--- + +*Next: [Environment Variables →](./environment.md)* diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..b7360cf --- /dev/null +++ b/docs/README.md @@ -0,0 +1,66 @@ +# Launchpad — Developer Handover Documentation + +> **Launchpad** is the open-source local dashboard for [MetaCall FaaS](https://github.com/metacall/faas). +> It gives developers a visual interface to deploy, inspect, test, and manage polyglot serverless functions +> running on a local MetaCall FaaS server — replacing the need to use raw `curl` commands or the CLI. + +This documentation is written for **incoming developers, org reviewers, and GSoC mentors** taking over the project. It is designed so a new contributor can understand the *why*, the *how*, and the *current state* without needing to ask anyone. + +--- + +## How to Read These Docs + +Read them in this order for the fastest ramp-up: + +| # | Document | What you will understand | +|---|---|---| +| 1 | [Project Context](./01-project-context.md) | Why this project exists, what problem it solves, where it fits in the MetaCall ecosystem | +| 2 | [Architecture](./02-architecture.md) | How the codebase is structured, every file's purpose, and how data flows | +| 3 | [Getting Started](./03-getting-started.md) | How to run the project locally, step by step | +| 4 | [API Reference](./04-api-reference.md) | Every HTTP endpoint the dashboard talks to, with request/response shapes | +| 5 | [Key Decisions](./05-decisions.md) | Why specific technical choices were made (auth, polling, routing, etc.) | +| 6 | [Current Status & Known Issues](./06-status-and-issues.md) | What is done, what is incomplete, and all known bugs | +| 7 | [Testing Guide](./07-testing.md) | How to run and write unit + E2E tests | +| 8 | [Contributing](./08-contributing.md) | Code standards, branch strategy, PR process | + +--- + +## Repository Layout + +``` +Metacall_dashboard/ +├── Dashboard/ ← Launchpad frontend (React 19 + Vite + TypeScript) +├── nodejs-base-app/ ← Minimal MetaCall FaaS worker example app +├── docs/ ← You are here (developer handover documentation) +└── req_docs/ ← Internal research, proposals, and reference notes +``` + +--- + +## Quick Command Reference + +```bash +cd Dashboard + +npm install # install dependencies +cp .env.example .env # configure environment + +npm run dev # start dev server at http://localhost:5173 +npm run typecheck # check TypeScript +npm run lint # run ESLint +npm run unit # run Vitest unit tests +npm run test # run full Playwright E2E suite +npm run test:smoke # run smoke tests only +npm run build # production build +``` + +--- + +## Who Built This + +This project was developed as part of **GSoC 2026** in collaboration with the MetaCall open-source organization. + +- **MetaCall FaaS repository:** https://github.com/metacall/faas +- **MetaCall Deploy CLI:** https://github.com/metacall/deploy +- **MetaCall Protocol:** https://github.com/metacall/protocol +- **Production dashboard (closed-source):** https://dashboard.metacall.io