# Landing Page & Shared Layout Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build a shared Jinja2/Tailwind CSS layout and a module-card landing page for the University Process Hub.
**Architecture:** FastAPI serves HTML via `Jinja2Templates`. `base.html` defines the red navbar (`#e2001a`), a `{% block content %}` slot, and a footer. `index.html` extends it with a hero section, responsive 3-column module-card grid, and an info strip. Module metadata is a static list in `main.py` — no DB access on the landing page.
**Tech Stack:** FastAPI, Jinja2, Tailwind CSS Play CDN (no build step), pytest, httpx
---
### Task 1: Add dependencies and update .gitignore
**Files:**
- Modify: `requirements.txt`
- Modify: `.gitignore`
- [ ] **Step 1: Update `requirements.txt`**
```
fastapi
uvicorn[standard]
pydantic-settings
sqlalchemy
jinja2
pytest
httpx
```
- [ ] **Step 2: Add `.superpowers/` to `.gitignore`**
Append to the `# Project specific` section:
```
.superpowers/
```
- [ ] **Step 3: Install new dependencies**
```bash
.venv/bin/pip install -r requirements.txt
```
Expected: `jinja2`, `pytest`, `httpx` installed without error.
- [ ] **Step 4: Commit**
```bash
git add requirements.txt .gitignore
git commit -m "chore: add jinja2 and test dependencies"
```
---
### Task 2: Create base layout template
**Files:**
- Create: `app/templates/base.html`
- Create: `app/static/.gitkeep`
- [ ] **Step 1: Create directories**
```bash
mkdir -p app/templates app/static
touch app/static/.gitkeep
```
- [ ] **Step 2: Write `app/templates/base.html`**
```html
{% block title %}EFI Hub{% endblock %}
{% block content %}{% endblock %}
```
- [ ] **Step 3: Commit**
```bash
git add app/templates/base.html app/static/.gitkeep
git commit -m "feat: add base layout template with red navbar and footer"
```
---
### Task 3: Create landing page template
**Files:**
- Create: `app/templates/index.html`
- [ ] **Step 1: Write `app/templates/index.html`**
```html
{% extends "base.html" %}
{% block title %}University Process Hub{% endblock %}
{% block content %}
Fakultät EFI · TH Nürnberg
University Process Hub
Modulares Informationssystem für Fakultätsprozesse.
Startpunkt für studentische Arbeiten und Schnittstelle für interne Dienste.
Verfügbare Module
{% for module in modules %}
{{ module.icon }}
{{ module.name }}
{{ module.description }}
{% if module.status == "active" %}
Aktiv
{% else %}
Geplant
{% endif %}
{% endfor %}
➕
Neues Modul
Erweiterbar durch studentische Arbeiten.
API: FastAPI
Datenbank: {{ db_mode }}
WebSocket: aktiv
● System online
{% endblock %}
```
- [ ] **Step 2: Commit**
```bash
git add app/templates/index.html
git commit -m "feat: add landing page template with module grid and info strip"
```
---
### Task 4: Wire templates into FastAPI and write tests
**Files:**
- Modify: `app/main.py`
- Create: `tests/__init__.py`
- Create: `tests/test_landing.py`
- [ ] **Step 1: Write the failing tests**
```bash
mkdir -p tests && touch tests/__init__.py
```
Schreibe `tests/test_landing.py`:
```python
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
def test_landing_returns_html():
response = client.get("/")
assert response.status_code == 200
assert "text/html" in response.headers["content-type"]
def test_landing_contains_title():
response = client.get("/")
assert "University Process Hub" in response.text
def test_landing_contains_rss_module():
response = client.get("/")
assert "RSS-Feed Server" in response.text
def test_landing_navbar_links_present():
response = client.get("/")
assert "Übersicht" in response.text
assert "RSS-Feeds" in response.text
def test_landing_info_strip_shows_db_mode():
response = client.get("/")
assert "SQLite" in response.text or "MariaDB" in response.text
```
- [ ] **Step 2: Run tests to confirm they fail**
```bash
.venv/bin/pytest tests/test_landing.py -v
```
Expected: FAIL — `GET /` returns JSON, not HTML.
- [ ] **Step 3: Replace `app/main.py` with template-serving version**
```python
from fastapi import FastAPI, Request, WebSocket
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from app.core.config import get_settings
settings = get_settings()
app = FastAPI(
title="University Process Hub",
root_path=settings.APP_PREFIX,
)
app.mount("/static", StaticFiles(directory="app/static"), name="static")
templates = Jinja2Templates(directory="app/templates")
MODULES = [
{
"icon": "📡",
"name": "RSS-Feed Server",
"description": "Aggregiert und verteilt Neuigkeiten der Fakultät als standardkonformen RSS 2.0 Feed.",
"status": "active",
},
{
"icon": "📅",
"name": "Kalender",
"description": "Veranstaltungen, Prüfungstermine und Fristen im iCal-Format.",
"status": "planned",
},
{
"icon": "🔔",
"name": "Benachrichtigungen",
"description": "Push-Nachrichten und WebSocket-basierte Echtzeit-Alerts für Studierende.",
"status": "planned",
},
{
"icon": "📊",
"name": "Auslastung",
"description": "Raum- und Ressourcenauslastung der Fakultät in Echtzeit.",
"status": "planned",
},
{
"icon": "📚",
"name": "Lehrveranstaltungen",
"description": "Stundenplan-API und Kursinformationen aus dem Campus-System.",
"status": "planned",
},
]
NAV_ITEMS = [
{"label": "Übersicht", "url": "/", "active": True},
{"label": "RSS-Feeds", "url": "/rss", "active": False},
{"label": "Kalender", "url": "/kalender", "active": False},
{"label": "Docs", "url": "/docs", "active": False},
]
def _db_mode() -> str:
url = settings.DATABASE_URL
return "SQLite (dev)" if url.startswith("sqlite") else "MariaDB (prod)"
@app.get("/", response_class=HTMLResponse)
async def root(request: Request):
return templates.TemplateResponse(
request,
"index.html",
{
"nav_items": NAV_ITEMS,
"modules": MODULES,
"db_mode": _db_mode(),
"app_version": "0.1.0",
},
)
@app.websocket("/ws/hello/{name}")
async def websocket_hello(websocket: WebSocket, name: str):
await websocket.accept()
await websocket.send_text(f"Hello, {name}!")
await websocket.close()
```
- [ ] **Step 4: Run tests to confirm they pass**
```bash
.venv/bin/pytest tests/test_landing.py -v
```
Expected: All 5 tests PASS.
- [ ] **Step 5: Verify in browser**
```bash
.venv/bin/uvicorn app.main:app --reload
```
Öffne `http://localhost:8000` — Navbar rot, Hero-Bereich, 5 Modul-Karten + Placeholder-Karte, Info-Strip, Footer.
- [ ] **Step 6: Commit**
```bash
git add app/main.py tests/
git commit -m "feat: serve landing page via Jinja2 with module grid"
```