Add player_name column to games (migration included), with regex-based validation and anti-abuse sanitization. New /scoreboard page shows recent games from localStorage, popular categories with play counts, and top-10 per-category leaderboards sorted by completion time. Also includes two-click reverse word matching, base URL prefix support for reverse-proxy hosting, BasePageHandler refactoring, themed table CSS for all 5 themes, and comprehensive test coverage for player names and scoreboard API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
20 KiB
Website Redesign 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: Redesign the word search frontend with a dark indigo palette, pill-style navbar, footer, two new content pages (How to Play, About), and emoji/icon accents.
Architecture: Enhance existing Tornado template hierarchy in place. base.html gets navbar restructure, Bootstrap Icons CDN, semantic HTML, and a footer. Child templates set an active_page block for nav highlighting. Two new handlers + templates for the new pages. CSS swaps teal variables for indigo palette.
Tech Stack: Python/Tornado, Bootstrap 5.3 (dark mode), Bootstrap Icons (CDN), pytest + pytest-tornasync
Spec: docs/superpowers/specs/2026-05-06-website-redesign-design.md
File Map
Modified
| File | Responsibility |
|---|---|
tests/e2e/test_hello.py |
E2e tests for all routes including new ones |
app.py |
Route registration — add /howtoplay and /about |
static/css/theme.css |
Full palette swap + new component styles |
templates/base.html |
Shared layout: navbar, footer, Bootstrap Icons, active page logic |
templates/main.html |
Homepage hero with emoji and coral CTA |
templates/game.html |
Game board card restyled for new palette |
Created
| File | Responsibility |
|---|---|
handlers/howtoplay.py |
HowToPlayHandler — renders howtoplay.html |
handlers/about.py |
AboutHandler — renders about.html |
templates/howtoplay.html |
How to Play instructions page |
templates/about.html |
About page with app description and authors |
Task 1: Add e2e tests for new routes
Files:
-
Modify:
tests/e2e/test_hello.py -
Step 1: Add tests for
/howtoplayand/aboutroutes
Replace the contents of tests/e2e/test_hello.py with:
import pytest
from app import make_app
@pytest.fixture
def app():
return make_app()
async def test_homepage_returns_200(http_server_client):
response = await http_server_client.fetch("/")
assert response.code == 200
async def test_game_returns_200(http_server_client):
response = await http_server_client.fetch("/game")
assert response.code == 200
async def test_howtoplay_returns_200(http_server_client):
response = await http_server_client.fetch("/howtoplay")
assert response.code == 200
async def test_about_returns_200(http_server_client):
response = await http_server_client.fetch("/about")
assert response.code == 200
- Step 2: Run tests to verify new tests fail
Run: uv run pytest tests/e2e/ -v
Expected: test_homepage_returns_200 PASS, test_game_returns_200 PASS, test_howtoplay_returns_200 FAIL (404), test_about_returns_200 FAIL (404)
Task 2: Create handlers and register routes
Files:
-
Create:
handlers/howtoplay.py -
Create:
handlers/about.py -
Create:
templates/howtoplay.html(minimal placeholder) -
Create:
templates/about.html(minimal placeholder) -
Modify:
app.py -
Step 1: Create HowToPlayHandler
Create handlers/howtoplay.py:
import tornado.web
class HowToPlayHandler(tornado.web.RequestHandler):
def get(self):
self.render("howtoplay.html")
- Step 2: Create AboutHandler
Create handlers/about.py:
import tornado.web
class AboutHandler(tornado.web.RequestHandler):
def get(self):
self.render("about.html")
- Step 3: Create placeholder templates
Create templates/howtoplay.html:
{% extends "base.html" %}
{% block title %}Word Search — How to Play{% end %}
{% block content %}
<h2>How to Play</h2>
<p>Instructions coming soon.</p>
{% end %}
Create templates/about.html:
{% extends "base.html" %}
{% block title %}Word Search — About{% end %}
{% block content %}
<h2>About</h2>
<p>About page coming soon.</p>
{% end %}
- Step 4: Register routes in app.py
In app.py, add imports after the existing handler imports:
from handlers.howtoplay import HowToPlayHandler
from handlers.about import AboutHandler
Add routes to the make_app() route list, after the /game route:
(r"/howtoplay", HowToPlayHandler),
(r"/about", AboutHandler),
- Step 5: Run tests to verify all pass
Run: uv run pytest tests/e2e/ -v
Expected: All 4 tests PASS
- Step 6: Lint and commit
Run: uv run black . && uv run flake8
git add handlers/howtoplay.py handlers/about.py templates/howtoplay.html templates/about.html app.py tests/e2e/test_hello.py
git commit -m "feat: add howtoplay and about routes with placeholder templates"
Task 3: Swap CSS palette and add component styles
Files:
-
Modify:
static/css/theme.css -
Step 1: Replace full contents of
static/css/theme.css
:root {
--indigo-darkest: #1a1a2e;
--indigo-dark: #16213e;
--indigo-mid: #0f3460;
--coral: #e94560;
--coral-light: #ff6b81;
--gold: #f5c518;
--text-primary: #e0e0e0;
--text-muted: #a0aec0;
}
body {
background-color: var(--indigo-darkest);
color: var(--text-primary);
}
/* Navbar */
.navbar {
background-color: var(--indigo-dark);
border-bottom: 1px solid var(--indigo-mid);
}
.navbar-brand {
color: var(--coral);
font-weight: 700;
display: flex;
align-items: center;
gap: 0.4rem;
}
.navbar-brand:hover {
color: var(--coral-light);
}
.navbar-nav .nav-link {
color: var(--text-muted);
border-radius: 6px;
padding: 0.4rem 1rem;
margin: 0 0.15rem;
font-weight: 500;
transition: background-color 0.2s, color 0.2s;
}
.navbar-nav .nav-link:hover {
color: #ffffff;
background-color: rgba(15, 52, 96, 0.5);
}
.navbar-nav .nav-link.active {
color: #ffffff;
background-color: var(--indigo-mid);
}
/* Hero */
.hero {
text-align: center;
padding: 3rem 1rem;
}
.hero-emoji {
font-size: 3rem;
margin-bottom: 0.75rem;
}
.hero h1 {
color: #ffffff;
font-weight: 800;
}
.hero .lead {
color: var(--text-muted);
max-width: 500px;
margin-left: auto;
margin-right: auto;
}
/* Buttons */
.btn-accent {
background-color: var(--coral);
border-color: var(--coral);
color: #ffffff;
font-weight: 600;
}
.btn-accent:hover {
background-color: var(--coral-light);
border-color: var(--coral-light);
color: #ffffff;
}
/* Cards */
.card {
background-color: var(--indigo-dark);
border-color: var(--indigo-mid);
}
/* Content pages */
.content-section {
background-color: var(--indigo-dark);
border: 1px solid var(--indigo-mid);
border-radius: 8px;
padding: 2rem;
margin-bottom: 1.5rem;
}
.step-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
background-color: var(--indigo-mid);
color: var(--gold);
border-radius: 50%;
font-weight: 700;
font-size: 0.875rem;
flex-shrink: 0;
}
.step-item {
display: flex;
gap: 1rem;
align-items: flex-start;
margin-bottom: 1.25rem;
}
.step-item p {
margin: 0;
}
.tip-card {
background-color: rgba(15, 52, 96, 0.4);
border-left: 3px solid var(--gold);
border-radius: 4px;
padding: 1rem 1.25rem;
margin-bottom: 0.75rem;
}
/* Footer */
.site-footer {
border-top: 1px solid var(--indigo-mid);
padding: 1.25rem 0;
text-align: center;
color: var(--text-muted);
font-size: 0.85rem;
margin-top: 3rem;
}
- Step 2: Verify the app still renders
Run: uv run pytest tests/e2e/ -v
Expected: All 4 tests PASS (CSS changes don't break templates)
- Step 3: Commit
git add static/css/theme.css
git commit -m "style: swap teal palette for dark indigo with coral accents"
Task 4: Restructure base.html
Files:
-
Modify:
templates/base.html -
Step 1: Replace full contents of
templates/base.html
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Word Search{% end %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YcnS/1WR6zNiELmFp5mGg7gCjSP54e0TpoS"
crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
rel="stylesheet">
<link rel="stylesheet" href="{{ static_url('css/theme.css') }}">
</head>
<body>
{% block active_page %}{% end %}
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand" href="/">
<i class="bi bi-search"></i> Word Search
</a>
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link {% if active_page == 'home' %}active{% end %}"
href="/">
<i class="bi bi-house-door"></i> Home
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_page == 'play' %}active{% end %}"
href="/game">
<i class="bi bi-controller"></i> Play
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_page == 'howtoplay' %}active{% end %}"
href="/howtoplay">
<i class="bi bi-question-circle"></i> How to Play
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if active_page == 'about' %}active{% end %}"
href="/about">
<i class="bi bi-info-circle"></i> About
</a>
</li>
</ul>
</div>
</div>
</nav>
<main class="container py-4">
{% block content %}{% end %}
</main>
<footer class="site-footer">
<div class="container">
© 2026 Word Search
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
crossorigin="anonymous"></script>
</body>
</html>
Note on active page logic: Tornado templates don't support {% set %} like Jinja2. The {% block active_page %}{% end %} block is intentionally empty in the base. Each child template overrides it with {% block active_page %}{% set active_page = "home" %}{% end %}. Tornado's {% set %} creates a template variable accessible in the rest of the template including the base.
- Step 2: Update child templates to set active_page
In templates/main.html, add after the {% extends %} line:
{% block active_page %}{% set active_page = "home" %}{% end %}
In templates/game.html, add after the {% extends %} line:
{% block active_page %}{% set active_page = "play" %}{% end %}
In templates/howtoplay.html, add after the {% extends %} line:
{% block active_page %}{% set active_page = "howtoplay" %}{% end %}
In templates/about.html, add after the {% extends %} line:
{% block active_page %}{% set active_page = "about" %}{% end %}
- Step 3: Run tests
Run: uv run pytest tests/e2e/ -v
Expected: All 4 tests PASS
- Step 4: Commit
git add templates/base.html templates/main.html templates/game.html templates/howtoplay.html templates/about.html
git commit -m "feat: restructure base template with navbar pills, footer, and active page logic"
Task 5: Restyle homepage hero
Files:
-
Modify:
templates/main.html -
Step 1: Replace content block in
templates/main.html
Full file contents:
{% extends "base.html" %}
{% block active_page %}{% set active_page = "home" %}{% end %}
{% block title %}Word Search — Home{% end %}
{% block content %}
<header class="hero">
<div class="hero-emoji">🧩</div>
<h1 class="display-4 fw-bold mb-3">Word Search</h1>
<p class="lead mb-4">
Find hidden words in a grid of letters. Words can appear horizontally,
vertically, or diagonally. How fast can you find them all?
</p>
<a href="/game" class="btn btn-accent btn-lg px-5">🎮 Start Game</a>
</header>
{% end %}
- Step 2: Run tests
Run: uv run pytest tests/e2e/ -v
Expected: All 4 tests PASS
- Step 3: Commit
git add templates/main.html
git commit -m "style: restyle homepage hero with emoji and coral CTA"
Task 6: Update game page for new palette
Files:
-
Modify:
templates/game.html -
Step 1: Replace full contents of
templates/game.html
{% extends "base.html" %}
{% block active_page %}{% set active_page = "play" %}{% end %}
{% block title %}Word Search — Play{% end %}
{% block content %}
<h2 class="mb-4">🎯 Game Board</h2>
<div class="card">
<div class="card-body text-center py-5">
<p class="text-muted">Game board coming soon.</p>
</div>
</div>
{% end %}
- Step 2: Run tests
Run: uv run pytest tests/e2e/ -v
Expected: All 4 tests PASS
- Step 3: Commit
git add templates/game.html
git commit -m "style: update game page with active nav and emoji heading"
Task 7: Create How to Play page content
Files:
-
Modify:
templates/howtoplay.html -
Step 1: Replace full contents of
templates/howtoplay.html
{% extends "base.html" %}
{% block active_page %}{% set active_page = "howtoplay" %}{% end %}
{% block title %}Word Search — How to Play{% end %}
{% block content %}
<h2 class="mb-4">📖 How to Play</h2>
<div class="content-section">
<h3 class="mb-4">Step-by-Step Instructions</h3>
<div class="step-item">
<span class="step-number">1</span>
<p>
📋 <strong>Check the word list</strong> — Look at the list of words
displayed alongside the grid. These are the words you need to find.
</p>
</div>
<div class="step-item">
<span class="step-number">2</span>
<p>
🔍 <strong>Scan for the first letter</strong> — Pick a word from the
list and scan the grid for its first letter. This gives you a starting
point.
</p>
</div>
<div class="step-item">
<span class="step-number">3</span>
<p>
↗️ <strong>Look in all directions</strong> — Words can run
horizontally, vertically, or diagonally — and they can go forward or
backward. Check all eight directions from your starting letter.
</p>
</div>
<div class="step-item">
<span class="step-number">4</span>
<p>
👆 <strong>Select the word</strong> — Click or tap the first letter of
the word, then click the last letter to highlight the entire word.
</p>
</div>
<div class="step-item">
<span class="step-number">5</span>
<p>
✅ <strong>Words get crossed off</strong> — When you correctly find a
word, it gets crossed off the list so you can track your progress.
</p>
</div>
<div class="step-item">
<span class="step-number">6</span>
<p>
🏆 <strong>Find them all to win!</strong> — The puzzle is complete when
every word on the list has been found. Try to beat your best time!
</p>
</div>
</div>
<h3 class="mb-3">💡 Tips</h3>
<div class="tip-card">
<strong>Start with uncommon letters</strong> — Letters like Q, X, Z, and J
appear less often in the grid, making words with those letters easier to spot.
</div>
<div class="tip-card">
<strong>Scan systematically</strong> — Work through the grid row by row or
column by column rather than jumping around randomly.
</div>
<div class="tip-card">
<strong>Look for word shapes</strong> — Sometimes you can spot diagonal words
by the distinctive slanted pattern they make in the grid.
</div>
{% end %}
- Step 2: Run tests
Run: uv run pytest tests/e2e/ -v
Expected: All 4 tests PASS
- Step 3: Commit
git add templates/howtoplay.html
git commit -m "feat: add How to Play page with step-by-step instructions and tips"
Task 8: Create About page content
Files:
-
Modify:
templates/about.html -
Step 1: Replace full contents of
templates/about.html
{% extends "base.html" %}
{% block active_page %}{% set active_page = "about" %}{% end %}
{% block title %}Word Search — About{% end %}
{% block content %}
<h2 class="mb-4">ℹ️ About</h2>
<div class="content-section">
<h3>🧩 What is Word Search?</h3>
<p>
Word Search is a browser-based puzzle game where you find hidden words in a
grid of letters. Words can be placed horizontally, vertically, or
diagonally — forwards or backwards. The backend generates puzzles,
validates your selections, and tracks your progress in real time.
</p>
</div>
<div class="content-section">
<h3>🛠️ Built With</h3>
<ul>
<li><strong>Python</strong> — core language</li>
<li><strong>Tornado</strong> — async web framework</li>
<li><strong>Bootstrap 5</strong> — responsive UI</li>
<li><strong>Bootstrap Icons</strong> — interface icons</li>
</ul>
</div>
<div class="content-section">
<h3>✍️ Authors</h3>
<ul>
<li><strong>Mathew Guest</strong> — creator and developer</li>
<li><strong>🤖 Claude</strong> (Anthropic) — AI co-author</li>
</ul>
</div>
<div class="content-section">
<h3>📂 Source Code</h3>
<p>
The source code for this project will be available on GitHub.
<em>Link coming soon.</em>
</p>
</div>
{% end %}
- Step 2: Run tests
Run: uv run pytest tests/e2e/ -v
Expected: All 4 tests PASS
- Step 3: Commit
git add templates/about.html
git commit -m "feat: add About page with app description, tech stack, and authors"
Task 9: Final verification
- Step 1: Run full test suite
Run: uv run pytest -v
Expected: All tests PASS (unit + e2e)
- Step 2: Lint and format
Run: uv run black . && uv run flake8
Expected: No errors
- Step 3: Start the server and visually verify with Playwright
Start the app: uv run python app.py &
Use Playwright MCP tools to:
- Navigate to
http://localhost:8888— verify indigo palette, navbar with 4 pill-style links, "Home" is active, hero section with puzzle emoji, coral "Start Game" button, footer with copyright - Navigate to
http://localhost:8888/game— verify "Play" is active in navbar, game card renders - Navigate to
http://localhost:8888/howtoplay— verify "How to Play" is active, 6 numbered steps visible, 3 tip cards visible - Navigate to
http://localhost:8888/about— verify "About" is active, 4 content sections visible (What is, Built With, Authors, Source Code) - Resize browser to mobile width — verify navbar collapses to hamburger menu
- Step 4: Stop the server and commit any fixes
Kill the background server process. If any visual issues were found and fixed, commit the fixes.