# Project structure

> Every special file in a Koze app and what it's for

Package: Koze
Canonical: https://kuratchi.dev/docs/koze/project-structure
Markdown: https://kuratchi.dev/docs/koze/project-structure.md

Koze is convention-driven. Drop a file in the right place with the right name and the compiler wires it into the Worker automatically — no explicit registration.

## Canonical project shape

```text
my-app/
├─ package.json
├─ vite.config.ts              plugin install point + plugin options
├─ wrangler.jsonc              Cloudflare deployment config (auto-synced)
├─ tsconfig.json
└─ src/
   ├─ worker.ts                one-line re-export of koze:worker
   ├─ middleware.ts            request/route/response/error hooks
   ├─ app.koze             document shell (<!DOCTYPE>, <html>, <head>)
   ├─ app.css                  global stylesheet (optional; auto-linked)
   ├─ assets/                  static files served verbatim at the URL root
   ├─ routes/
   │  ├─ layout.koze       shared fragment wrapping every page
   │  ├─ index.koze        /
   │  └─ blog/[slug]/index.koze   /blog/:slug
   ├─ server/
   │  ├─ *.ts                  private server-only modules
   │  ├─ *.do.ts               Durable Object classes
   │  ├─ *.workflow.ts         Workflow classes
   │  ├─ *.queue.ts            Queue consumers
   │  ├─ *.sandbox.ts          Sandbox classes
   │  ├─ *.container.ts        Container classes
   │  ├─ *.agent.ts            Agent classes
   │  └─ schemas/              ORM schema modules
   └─ lib/                     shared browser-safe utilities and components (imported via $lib/*)
```

There is **no project-level config file**. Build-time options (security headers, directory overrides) are passed to the `koze()` Vite plugin in `vite.config.ts`. Request-time concerns (auth, ORM auto-migration, custom steps) live in `src/middleware.ts`. Anything generated (the `.koze/` directory, `dist/`) is build output. Never commit edits there.

## Route files

### `src/app.koze` — document shell

The outer HTML document. Owns `<!DOCTYPE html>`, `<html>`, `<head>`, and `<body>`. Exactly one `<slot></slot>` where the layout+page stream renders.

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>My App</title>
</head>
<body>
  <slot></slot>
</body>
</html>
```

- Optional. When absent, the framework synthesizes a minimal default shell.
- A top `<script>` is allowed for per-request shell data (e.g. CSP nonces, `<html lang>` selection). It follows the same client-first rules as route and layout top scripts, and imports from `koze:request` work just like in a route.
- The app shell should stay thin — app-wide chrome (navigation, headers, footers) belongs in `layout.koze`.

### `src/app.css` — global stylesheet

Optional. When present, Koze registers it as a Vite client entry (so Tailwind, PostCSS, CSS Modules, Lightning CSS, and every other Vite CSS plugin runs on it) and auto-injects `<link rel="stylesheet" href={...}>` into the compiled shell before `</head>`. You never write the `<link>` tag yourself.

```css
/* src/app.css */
@import "tailwindcss";
@plugin "daisyui";
@source "./routes/**/*.koze";

body {
  font-family: system-ui, sans-serif;
}
```

In dev the file is served through Vite's module graph (live plugin transforms on save). In prod it emits a content-hashed `.css` asset.

See [Styling](/docs/koze/styling) for Tailwind, CSS frameworks, and migration from the legacy `css` config.

### `src/routes/layout.koze` — shared fragment

A fragment that wraps every page. **Not** a document shell — no `<!DOCTYPE>`, `<html>`, or `<body>`. Contains a single `<slot></slot>` where each page renders.

```html
<script>
  import { url } from 'koze:request';

  const currentPath = url.pathname;
  function navClass(href) {
    return `nav-link${currentPath === href ? ' active' : ''}`;
  }
</script>

<header class="top-bar">
  <a href="/" class={navClass('/')}>Home</a>
  <a href="/about" class={navClass('/about')}>About</a>
</header>

<main>
  <slot></slot>
</main>
```

- Fragment, not a document. Think "wrapper component".
- Works like any other route file: top `<script>` is authored as client-first code, the SSR-needed parts run on the server, and `$server/*` imports become client RPC stubs in the browser copy.
- Nested layouts (`src/routes/admin/layout.koze`) wrap pages under that segment only.

### `src/routes/<path>/index.koze` — page

A regular page. Path becomes the URL pattern.

```html
<script>
  import { listItems } from '$server/items';
  const items = await listItems();
</script>

<h1>Items ({items.length})</h1>
<ul>
  for (const item of items) {
    <li>{item.title}</li>
  }
</ul>
```

- `index.koze` is the default file per segment.
- Dynamic segments use `[brackets]`: `[slug]` → `:slug`, `[...rest]` → catchall.
- See [Routing](/docs/koze/routing) for the full template syntax.

### Render chain

When a user hits `/blog/hello`:

```text
app.render(data,
  layout.render(data,
    page.render(data)))
```

All three files use the same `<script>` + template + `<slot>` shape. Only their role differs.

## Framework entrypoints

### `vite.config.ts`

Installs the Koze Vite plugin. That single plugin owns route discovery, virtual modules, `wrangler.jsonc` auto-sync, and the dev/build pipeline.

```ts
import { defineConfig } from 'vite';
import { koze } from '@kuratchi/koze/vite';

export default defineConfig({
  plugins: [koze()],
});
```

### `wrangler.jsonc`

Cloudflare deployment config. Koze auto-maintains the following fields at build time — do not hand-edit them:

- `durable_objects.bindings`
- `migrations[].new_sqlite_classes`
- `workflows[]`
- `containers[]`
- `pipelines[]`
- `assets.directory`, `assets.binding`
- `queues.consumers`

Everything else (secrets, custom bindings, `compatibility_date`, routes) is yours.

### `src/worker.ts`

A one-line re-export. Koze generates the real Worker entry as a virtual module.

```ts
export { default } from 'koze:worker';
```

### `src/middleware.ts`

Where ORM auto-migration, auth, security guards, and your own custom steps compose. Each step hooks into the request lifecycle (`request`, `route`, `response`, `error`).

```ts
import { defineMiddleware } from 'koze:middleware';
import { autoMigrate } from '@kuratchi/kunii';
import { kyzenAuthMiddleware } from '@kuratchi/kyzen/middleware';
import { adminSchema } from './server/schemas/admin';
import { authConfig } from './server/auth-config';

export default defineMiddleware({
  // Apply pending D1 migrations on cold start.
  migrate: autoMigrate({ DB: adminSchema }),

  // Sessions, guards, OAuth, rate-limit, turnstile.
  auth: kyzenAuthMiddleware(authConfig),

  // Your own steps:
  // logging: { async request(ctx, next) { ... } },
});
```

Both `autoMigrate` and `kyzenAuthMiddleware` return ordinary `MiddlewareStep`s — there is no special framework slot for either. The same shape lets you swap in any third-party auth / ORM / observability tool. See [Middleware](/docs/koze/middleware).

## Special directories

### `src/assets/`

Static files served **verbatim** by Wrangler's ASSETS binding. Vite plugins never touch these — use this directory for finished assets only.

- `src/assets/favicon.ico` → `/favicon.ico`
- `src/assets/hero.png` → `/hero.png`
- `src/assets/fonts/inter.woff2` → `/fonts/inter.woff2`

Koze keeps Wrangler's `assets.directory` in sync automatically.

> **Tip:**
**Processed → `src/app.css`. Verbatim → `src/assets/`.** If Tailwind/PostCSS needs to see it, put it in (or import it from) `src/app.css`. If it's a finished file you want at `/<path>`, drop it in `src/assets/`.

### `src/content/`

Markdown content lives in named folders under `src/content/`. Each direct child folder becomes a property on the `koze:content` virtual module.

```text
src/content/docs/getting-started.md      → content.docs.render('getting-started')
src/content/docs/settings/api-keys.md    → content.docs.render('settings/api-keys')
src/content/changelog/first-release.md   → content.changelog.render('first-release')
```

Use this for app-owned docs, help text, changelogs, and other Markdown-backed content that should live with the project. See [Content](/docs/koze/content).

### `src/server/`

Server-only code. Nothing here is shipped to the browser.

- Route files import server functions via `$server/<name>` (aliased to this directory). In top `<script>` blocks, Koze auto-rewrites those imports to RPC stubs on the client side.
- Convention-based files are auto-discovered by singular filename suffix. Plural suffixes such as `*.agents.ts`, `*.workflows.ts`, or `*.queues.ts` are rejected with a fix-it error:

| Suffix | Purpose | Binding style |
| --- | --- | --- |
| `*.do.ts` | Durable Object handler | One `durable_objects.bindings` entry |
| `*.workflow.ts` | Cloudflare Workflow class | `workflows[]` |
| `*.queue.ts` | Queue consumer | `queues.consumers[]` |
| `*.pipeline.ts` | Cloudflare Pipelines binding | `pipelines[]` |
| `*.sandbox.ts` | Sandbox (Durable Object + container) | `durable_objects`, `containers` |
| `*.container.ts` | Container class | `containers[]` |
| `*.agent.ts` | Agents SDK class | Manual Wrangler Durable Object binding |

### `src/lib/`

Shared browser-safe utilities and components. Imported via `$lib/<name>`.

- Safe to use from top `<script>` blocks when the module can execute during SSR.
- Safe for DOM helpers when the DOM work runs inside a browser-called function.
- Do not import `$server/*` into `$lib/*` modules that are meant to stay browser-safe.

```ts
// src/lib/copy-buttons.ts
export function mountCopyButtons() {
  /* ... */
}
```

```html
<script>
  import { mountCopyButtons } from '$lib/copy-buttons';
  mountCopyButtons();
</script>
```

### `src/server/schemas/`

ORM schema modules. Imported by `src/middleware.ts` and passed to `autoMigrate({ DB: schema })` to keep the live database in sync with the declared schema. Same modules feed `kunii(env.DB, schema)` for query-time type safety.

### `.koze/` (generated)

Build output — compiled routes, Worker module, DO proxies, transformed server modules, public asset mirror. Add it to `.gitignore`. Never edit.

## Virtual modules

Imported as `'koze:*'`. The Vite plugin synthesizes them at build time — they are not real files.

| Module | What it provides |
| --- | --- |
| `koze:request` | `url`, `pathname`, `searchParams`, `params`, `slug`, `method` for the current request |
| `koze:environment` | `dev` (compiled to a literal boolean) |
| `koze:workflow` | `workflowStatus()` and typed workflow helpers |
| `koze:pipeline` | `pipelines`, `pipeline()`, and `sendPipeline()` for discovered Pipeline bindings |
| `koze:content` | `content.<name>.list()` and `content.<name>.render(id)` for Markdown under `src/content/<name>` |
| `koze:navigation` | `redirect()` and related helpers |
| `koze:worker` | The full Worker module (`fetch` + `queue` + DO exports) |
| `koze:middleware` | Resolves your `src/middleware.ts` |
| `koze:app`, `koze:layout` | Compiled app shell + layout renderers |

You rarely need to import the last two directly — the compiler wires them into every route.
