---
name: simple-python-vanilla-js-web
description: Build small to medium web apps using a KISS architecture: one tiny Python HTTP server, static HTML/CSS, vanilla JavaScript ES modules, browser-native APIs, optional pywebview desktop shell, and minimal dependencies. Use when an agent should avoid Node, npm, package managers, bundlers, heavy frameworks, app servers, and premature abstractions so a project is useful immediately after git clone.
---

# Simple Python + Vanilla JS Web Apps

## Use This Architecture When

- The app is small or medium sized and can run mostly in the browser.
- Python is needed for local file access, native dialogs, packaging, or a tiny JSON API.
- The frontend can be static HTML, CSS, and vanilla JavaScript ES modules.
- The project should be easy to run with `python app.py` immediately after git clone, with no Node, npm, bundler, package-manager install, build step, database, or service framework unless the user explicitly needs one.
- The app may later be packaged as a desktop app with `pywebview` or PyInstaller.

## Core Methodology

Keep it simple, stupid.

Build the smallest boring system that satisfies the product:

- Prefer files over frameworks.
- Prefer browser APIs over libraries.
- Prefer the core strengths of the web platform: HTML for structure, CSS for presentation, URLs and forms where useful, ES modules for code organization, browser storage for local state, and browser APIs for interaction.
- Prefer explicit modules over generic architecture.
- Prefer a single local Python process over a web framework.
- Prefer JSON endpoints over RPC frameworks.
- Prefer readable duplication over premature abstractions.
- Prefer vendored browser assets or explicit CDN script/style references over Node, npm, package managers, and bundlers.
- Add a dependency only when it removes real complexity or enables a platform feature that would be unreasonable to hand-roll.

The goal is not minimalism for its own sake. The goal is a project another engineer or AI agent can clone, run, inspect in minutes, and modify without learning or installing a stack.

## Canonical Structure

Use this shape as the default:

```text
project/
  app.py              # tiny Python static server + optional JSON API + launcher
  index.html          # complete app shell and stable DOM anchors
  style.css           # all styling, CSS variables, responsive layout
  manifest.json       # optional PWA metadata
  sw.js               # optional service worker for offline static assets
  vendor/             # optional committed JS/CSS assets copied from CDNs
  js/
    core.js           # domain model, state containers, event bus, pure-ish helpers
    renderer.js       # drawing/rendering/projection/compositing if visual app
    tools.js          # user actions, tools, algorithms, input-mode logic
    ui.js             # panels, menus, dialogs, DOM-bound components
    app.js            # initialization, event wiring, keyboard shortcuts, I/O
  icons/              # optional PWA/desktop icons
  build.sh            # optional PyInstaller packaging script
```

Rename modules to fit the product, but keep each file responsible for one obvious layer. Avoid nested source trees until the app genuinely needs them.

## Python Server Pattern

Use the Python standard library first:

- `http.server.HTTPServer` with `SimpleHTTPRequestHandler` for static files.
- A custom handler only for `POST /api/...` JSON endpoints.
- `Path(__file__).parent` as the static directory; support `sys._MEIPASS` if packaged by PyInstaller.
- `socket.bind(("", 0))` to find a free port by default.
- `threading.Thread(..., daemon=True)` to run the server behind a browser or `pywebview` window.
- Suppress noisy request logs unless the app is in debug mode.

Keep endpoints direct and boring:

```python
def do_POST(self):
    length = int(self.headers.get("Content-Length", 0))
    body = self.rfile.read(length) if length else b""

    if self.path == "/api/ping":
        self._json_response({"ok": True})
    elif self.path == "/api/file/save":
        self._handle_file_save(body)
    else:
        self._json_response({"error": "Not found"}, 404)
```

Use framework code only when routing, middleware, authentication, streaming, or persistence complexity actually requires it.

## Desktop Shell Pattern

If the app benefits from desktop behavior:

- Launch `pywebview` around the local URL.
- Fall back to the system browser if `pywebview` is missing.
- Use `tkinter.filedialog` as a fallback for file dialogs.
- Keep browser-only fallback behavior in the frontend, such as `<input type="file">` and download links.

The app should still work as static files wherever possible. Native integration should enhance the app, not define it.

## Frontend Module Pattern

Use plain ES modules:

```html
<script type="module" src="js/app.js"></script>
```

Recommended module roles:

- `core.js`: event bus, data model, document/session state, history, reusable algorithms that do not need the DOM.
- `renderer.js`: canvas/WebGL/SVG rendering, viewport math, animation loop, dirty rendering.
- `tools.js`: pointer-driven tools, commands, hit testing, domain-specific algorithms.
- `ui.js`: DOM components, menus, panels, dialogs, form controls, status displays.
- `app.js`: composition root. Instantiate subsystems, wire browser events, call file APIs, register shortcuts.

Use classes where they map cleanly to app subsystems. Use functions for helpers and algorithms. Avoid global state except for a deliberately small event bus or app context.

## Eventing And State

For small apps, a tiny event bus is enough:

```js
export class EventBus {
  constructor() { this._listeners = {}; }
  on(event, fn) {
    (this._listeners[event] ||= []).push(fn);
    return () => this.off(event, fn);
  }
  off(event, fn) {
    const a = this._listeners[event];
    if (a) this._listeners[event] = a.filter(f => f !== fn);
  }
  emit(event, ...args) {
    for (const fn of this._listeners[event] || []) fn(...args);
  }
}

export const bus = new EventBus();
```

Use direct method calls inside a subsystem. Use events for cross-subsystem coordination such as `doc:switched`, `canvas:dirty`, `tool:changed`, or `file:saved`.

Keep state close to the thing that owns it:

- Document state belongs to a document/model object.
- Rendering state belongs to the renderer.
- UI control state belongs to the UI component.
- Persistence state belongs to a document/session manager.

## DOM And HTML

Keep `index.html` as the explicit app shell:

- Stable IDs for important anchors.
- Semantic groups for toolbar, viewport, side panels, dialogs, status bars, and menus.
- Hidden dialog templates in the page when that is simpler than dynamic generation.
- Script loading at the end of `body`.

Do not hide the whole app behind generated markup unless the interface is highly repetitive. A readable static shell is faster for agents and humans to understand.

## CSS Pattern

Use one `style.css` unless the app clearly outgrows it.

- Put tokens in `:root`: colors, borders, radii, timing, spacing.
- Use CSS Grid/Flexbox for the main layout.
- Use stable dimensions for toolbars, side panels, canvases, tabs, and status bars.
- Keep component sections marked with short comments.
- Avoid theme systems until the product needs multiple themes.

Prefer class and ID selectors that mirror `index.html`. Keep styling local to the app rather than introducing a CSS framework.

## Browser-Native Features

Reach for platform APIs before dependencies:

- HTML, CSS, and browser-native layout primitives.
- ES modules loaded directly by the browser.
- Canvas 2D, SVG, WebGL, or DOM for rendering.
- `fetch` for local JSON APIs.
- `localStorage` or IndexedDB for small local persistence.
- Clipboard API with graceful fallback.
- Drag and drop APIs.
- Pointer events for mouse/stylus/touch.
- Service worker and manifest for PWA behavior.

When offline support is useful, cache only static assets in `sw.js` and skip `/api/` requests.

Do not introduce a Node-based toolchain to access ordinary web features. If a small external browser library is justified, load it explicitly from a CDN during prototyping or commit the CDN-served JS/CSS file into `vendor/` for clone-and-run reliability.

## File I/O Pattern

Use a layered fallback:

1. Try the local Python API for native dialogs and direct filesystem access.
2. If unavailable, use browser file input, object URLs, `FileReader`, and download links.
3. Store autosave/session state in browser storage.

For project files, prefer simple portable formats:

- JSON for metadata.
- ZIP when bundling JSON plus binary assets.
- Base64 data URLs for browser/Python transfer when payloads are modest.

## Dependency Rules

Default dependency budget:

- Python: standard library only.
- Frontend: no build step, no package manager, no framework.
- No Node, npm, pnpm, Yarn, Vite, Webpack, Rollup, Babel, PostCSS, Tailwind build pipeline, or frontend package lockfile by default.
- Optional: one or two CDN libraries for narrow, high-value needs.
- Prefer committing CDN-served JS/CSS files into `vendor/` when the app should work offline or remain stable after clone.
- Optional desktop: `pywebview` and PyInstaller.

Before adding a dependency, ask:

- Does the platform already do this?
- Is this feature central enough to justify the dependency?
- Will this make the project harder for another agent to run?
- Can the dependency be isolated behind one module or script tag?
- Can the dependency be vendored as a plain JS/CSS file instead of adding a package manager?

## Build And Run

Make the source path trivial immediately after git clone:

```bash
python app.py
python app.py --browser
python app.py --port 8000
```

There should be no required `npm install`, frontend build, transpile step, asset compilation, or generated source directory for normal development. If third-party browser assets are needed, they should already be referenced by `index.html` or committed under `vendor/`.

If packaging is needed, keep it in a script:

```bash
#!/bin/bash
set -e
python3 -m venv .venv
source .venv/bin/activate
pip install -q pyinstaller pywebview
pyinstaller app.spec
```

Do not require the packaging path for normal development.

## Implementation Workflow For Agents

When building a new small web project with this skill:

1. Start with the file layout above.
2. Implement `app.py` as a static server and launcher.
3. Build the app shell in `index.html`.
4. Add CSS tokens and layout before polishing visual details.
5. Put domain state in `js/core.js`.
6. Put user-visible behavior in focused modules, not in one giant script.
7. Make `js/app.js` the only composition root.
8. If external browser JS/CSS is justified, use an explicit CDN include or commit the CDN-served file into `vendor/`; do not add Node/npm for this.
9. Add persistence and file fallbacks only after the core workflow works.
10. Run the app with `python app.py --browser` and verify the first screen manually.
11. Update `README.md` with only the commands and architecture notes a maintainer needs.

## Anti-Patterns

Avoid these unless the user specifically asks for them or the product clearly requires them:

- React/Vue/Svelte for a small local utility.
- Flask/FastAPI/Django for a static app with a few local endpoints.
- Node, npm, package-manager installs, or frontend lockfiles for code that can run directly in the browser.
- Webpack/Vite/Rollup/Babel/PostCSS when ES modules and direct browser loading are enough.
- Global state spread across many files.
- Deep folder hierarchies before there are enough files to justify them.
- Generated DOM for every control when static HTML is clearer.
- A database for local session state that fits in browser storage or a JSON file.
- Complex plugin systems, dependency injection, or state machines before the app has real complexity.

## Quality Bar

The result should be:

- Runnable from source with one Python command.
- Understandable from the top-level file list.
- Modifiable by editing ordinary Python, HTML, CSS, and JavaScript files.
- Functional without network access except for explicitly documented CDN assets.
- Graceful when optional desktop dependencies are missing.
- Small enough that an agent can inspect the whole architecture before changing behavior.
