GitHub

Project Structure

Pulse enforces a single convention: one spec per page, one directory per concern. The structure is not a recommendation — it is how the framework discovers and registers your pages. There is nothing to configure because there is nothing to decide.

Layout

my-app/
├── package.json
├── public/                # static assets served directly
│   ├── app.css
│   └── dist/              # generated bundles (pulse build)
│       ├── manifest.json
│       ├── runtime-[hash].js
│       └── [name].boot-[hash].js
└── src/
    ├── pages/             # one file per page — auto-discovered
    │   ├── home.js
    │   ├── about.js
    │   └── contact.js
    ├── lib/               # shared helpers
    │   └── db.js
    └── components/        # shared view fragments (optional)
        └── card.js

src/pages/

Each file in src/pages/ exports a single spec as the default export. pulse dev auto-discovers every file in this directory and registers it as a route. There is no route registry to maintain — the file is the registration.

Routes are derived from filenames by default (about.js/about). For dynamic segments, route is set explicitly in the spec.

public/

Static files served directly — CSS, fonts, images. Referenced via meta.styles in the spec. The dist/ subdirectory is generated by pulse build. Its contents are content-hashed and must not be edited manually.

PathPurpose
public/app.cssYour global stylesheet — reference it in spec meta.styles
public/dist/Generated bundles from npm run build — do not edit manually
public/dist/manifest.jsonMaps spec hydrate paths to hashed bundle filenames

src/lib/

Shared helpers used across multiple pages — database clients, API wrappers, utility functions. Imported directly into specs. Plain JavaScript modules with no framework coupling — they work identically in tests, scripts, or other contexts.

src/components/ (optional)

Reusable view fragments. Since views are just functions that return HTML strings, a component is simply a function:

src/components/card.js
// src/components/card.js
export function card({ title, body }) {
  return `
    <div class="card">
      <h2>${title}</h2>
      <p>${body}</p>
    </div>
  `
}
src/pages/home.js
// src/pages/home.js
import { card } from '../components/card.js'

export default {
  route: '/',
  view: () => card({ title: 'Hello', body: 'Welcome to Pulse' }),
}

One file per page

Pulse enforces one spec per file. Every page is self-contained — state, view, mutations, actions, and validation in one place. This is not a style preference. It is how the framework eliminates the question of where things live.

This holds for dynamic routes too: route: '/blog/:slug' is one spec serving every matching URL — the :param does the multiplying, so a thousand blog posts are still a single file. A site is therefore a small collection of specs, one per route, sharing layouts and sections from src/components/ rather than repeating them.

For large pages, view fragments and helpers can be imported from other modules. The spec file remains the coordination point — the structure is always clear regardless of how the implementation is organised.