The Index Pattern

Separation of concerns without the complexity. Use index.py for server-side logic and index.html for reactive markup.

index.py

The Controller. Handles database queries, metadata definitions, and heavy lifting. It executes strictly on the server.

  • Defines Page Metadata
  • Fetches Data (Async Prisma)
  • Smart Argument Binding

index.html

The View. Defines the layout and interactivity using PulsePoint. It receives data implicitly from the Python controller.

  • Reactive UI (pp.state, pp.effect)
  • Tailwind CSS Styling
  • Zero-Build Component Imports

File-System Routing

Routes are defined automatically by your folder structure. A folder becomes a public URL route if it contains either an index.html or an index.py.

! Routing Hierarchy: Python Takes Precedence

If a folder contains both files, the server designates index.py as the primary entry point. The HTML file is ignored by the router and will only be rendered if the Python script explicitly loads it.

Project Structure
app/index.py
/
└── app/dashboard/index.py
/dashboard
└── app/dashboard/settings/index.py
/dashboard/settings

1. The Logic Layer

src/app/index.py

The controller supports Async/Await and Smart Argument Binding. Arguments are fully optional—simply define what you need (like request or specific query parameters) and the server injects them.

page examples
from typing import Optional
from casp.page import render_page

# --- Pattern 1: Minimal ---
# No arguments needed? Just define page().
def page():
    return render_page(__file__)


# --- Pattern 2: Async & Request ---
# Need headers/session? Ask for 'request'.
async def page(request):
    print(request.headers.get("User-Agent"))
    return render_page(__file__)


# --- Pattern 3: Typed Query Params ---
# Arguments match URL query params (e.g., ?search=foo).
async def page(search: Optional[str] = None):
    if search:
        # DB logic here...
        pass
    return render_page(__file__, {"query": search})


# --- Pattern 4: Mixed (Power User) ---
# Combine request access with specific params.
async def page(
    request, 
    sort: str = "desc"
):
    user = request.session.get("user")
    return render_page(__file__)

2. The Logic Layer

src/app/index.py

The Python file exports a page() function. Use the Metadata class (imported from casp.layout) to define headers and inject data.

from casp.page import render_page
from casp.layout import Metadata

# 1. Define Metadata
Metadata(
    title="Documentation | Caspian",
    description="Comprehensive documentation for Caspian.",
)

# 2. The entry point function
def page():
    # Logic: Fetch data from DB
    todos = prisma.todo.find_many()

    # Return view with injected data
    return render_page(__file__, {
        "todos": [todo.to_dict() for todo in todos]
    })

2. The Reactive View

src/app/index.html

The HTML file contains your UI layout. Use <template pp-for> for lists and initialize state using the [[var|json]] syntax.

<!-- Import Icons -->
<!-- @import { CheckCircle } from ../../lib/ppicons -->

<div class="p-10 space-y-4">
  <h1 class="text-2xl font-bold">My Todos</h1>
  
  <ul class="space-y-2">
    <template pp-for="todo in todos">
      <li key="{todo.id}" class="flex items-center gap-2">
        <CheckCircle class="size-4 text-primary" />
        <span>{todo.title}</span>
      </li>
    </template>
  </ul>
</div>

<script>
  // Initialize state with data injected from Python
  const [todos, setTodos] = pp.state([[todos|json]]);
</script>