# 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" ```