> Source URL: /unit-3/project-paths/luke-p/luke-p-2026-04-18.guide
# Luke's Project Guide

**Project:** Study Buddy Tracker
**Category:** Web Development (Flask)
**Last updated:** April 18

---

> Note: This guide reflects the latest state of your project repo. It may not match the most up-to-date version if you've worked since.

## Where You Are

You're ahead. Your Flask app already has:

- A dashboard that groups assignments (overdue / due soon / this week / later)
- An assignments list
- A new-assignment form with validation in `helpers.py`
- A real SQLite layer with schema init
- A mark-complete route (`POST /assignments/<id>/complete`)

What's left: a button on the dashboard that calls the mark-complete route, edit and delete routes, a show/hide completed toggle, and a route audit.

---

## Project Structure

Your project already has a clean split — let's name it:

- **Business logic — you handwrite this.** `helpers.py` (validation, date parsing, grouping logic) and the SQL in `db.py` / `schema.sql`. This is what makes your tracker useful: *how* assignments are validated and *how* they're grouped by urgency.
- **Library / view code — agent-assisted is fine.** Flask routes in `app.py`, HTML templates, Bootstrap classes. These are the same for any CRUD Flask app.

Current layout:

```
final-project-Pattlu3/
├── app.py                  ← Flask routes — agent-assisted OK
├── db.py                   ← DB connection — library code
├── helpers.py              ← business logic — handwrite (yours to own)
├── schema.sql              ← SQL — yours (product decision)
├── pyproject.toml
├── templates/              ← HTML — agent-assisted OK
└── instance/               ← generated DB (gitignored)
```

Why the split? From [Lecture 1: The MVP](../../lectures/01-the-mvp/01-the-mvp.lecture.md) — on demo day, the question is "how does your tracker decide what's 'due soon'?" That's in `helpers.py` (`group_assignments`). The Flask routing that plumbs data from DB to page is not the interesting part.

**`helpers.py` should not import `flask`.** It's pure logic — you can test it without spinning up a server.

---

## Phase 1: Route Audit

> **No code — research phase.** Walk your app like a user would. Find the gaps.

### Objective

Before adding anything new, walk every URL in your app. Know what works, what's half-built, and what's missing.

### Instructions

- [ ] Run `uv run flask --app app run --debug`
- [ ] Visit each route and note what happens:
  - `/` (dashboard)
  - `/assignments` (list)
  - `/assignments/new` (GET — form)
  - `/assignments/new` (POST — submit)
  - `/assignments/<id>/complete` (POST — triggered how?)
- [ ] Write one line per route: ✅ works / ⚠️ half-built / ❌ missing

### Sample Notes

```
GET /                              → ✅ dashboard renders with groups
GET /assignments                   → ✅ list shows uncompleted
GET /assignments/new               → ✅ form renders, empty
POST /assignments/new              → ✅ saves, redirects to list
POST /assignments/<id>/complete    → ⚠️ route exists, no button triggers it
GET /assignments/<id>/edit         → ❌ doesn't exist
POST /assignments/<id>/delete      → ❌ doesn't exist
```

The audit sets your phase-by-phase work order for the rest of the week.

> **Optional — get help from your agent:**
>
> ```text
> Here are my current Flask routes. Help me spot any missing pieces
> to hit my MVP (add / list / mark complete / edit / delete). Just
> list what's missing, no code yet.
> ```

---

## Phase 2: Wire the Mark-Complete Button

> **Agent-assisted is fine here.** A tiny HTML form inside a list row is pure template markup.

### Objective

Your mark-complete route exists but nothing in the UI triggers it. Add a button that POSTs to it.

### Instructions

- [ ] In `templates/assignments_list.html` (and/or `dashboard.html`), add a small `<form>` per assignment row
- [ ] Form POSTs to `/assignments/<id>/complete`
- [ ] Submit button labeled "Mark Done" or ✓
- [ ] Confirm: click it, assignment disappears from the list

### Hints

**Inline form inside a list row:**

```html
{% for a in assignments %}
  <li>
    {{ a.title }} — due {{ a.due_date }}
    <form action="/assignments/{{ a.id }}/complete" method="post" style="display: inline">
      <button type="submit">✓ Done</button>
    </form>
  </li>
{% endfor %}
```

**Why a form, not a link?** State-changing actions should be POST, not GET. Links are GET. A tiny form is the plain-HTML way to POST without JavaScript.

> **Optional — get help from your agent:**
>
> ```text
> Show me the cleanest way to add a "Mark Done" button next to each
> assignment in assignments_list.html. My route is
> POST /assignments/<int:assignment_id>/complete. Don't change app.py.
> ```

---

## Phase 3: Edit Route

> **Mixed phase.** The route itself is Flask code (agent-assisted OK). The validation call reuses YOUR `validate_assignment_form` from `helpers.py` — that's your business logic doing the real work.

### Objective

Users can add but not edit. Add a flow: GET shows form pre-filled, POST updates the row.

### Instructions

- [ ] In `app.py`, add two routes:
  - `GET /assignments/<int:assignment_id>/edit` — fetch row, render form pre-filled
  - `POST /assignments/<int:assignment_id>/edit` — validate, `UPDATE` row, redirect
- [ ] Reuse `assignment_form.html` (already takes a `form` dict)
- [ ] Reuse `validate_assignment_form` from `helpers.py` — don't duplicate the validation logic
- [ ] Add an "Edit" link next to each assignment in the list

### Hints

**GET handler:**

```python
@app.get("/assignments/<int:assignment_id>/edit")
def assignments_edit_form(assignment_id: int):
    db = get_db()
    row = db.execute(
        "SELECT id, title, due_date FROM assignments WHERE id = ?",
        (assignment_id,),
    ).fetchone()
    if row is None:
        return "Not found", 404
    return render_template(
        "assignment_form.html",
        title="Edit Assignment",
        form=dict(row),
        errors={},
        assignment_id=assignment_id,
    )
```

**POST handler:**

```python
@app.post("/assignments/<int:assignment_id>/edit")
def assignments_update(assignment_id: int):
    form = dict(request.form)
    cleaned, errors = validate_assignment_form(form)   # ← reuses YOUR logic
    if errors:
        return render_template("assignment_form.html",
                               title="Edit Assignment",
                               form=form, errors=errors,
                               assignment_id=assignment_id), 400
    db = get_db()
    db.execute(
        "UPDATE assignments SET title = ?, due_date = ? WHERE id = ?",
        (cleaned["title"], cleaned["due_date"], assignment_id),
    )
    db.commit()
    return redirect(url_for("assignments_list"))
```

**Make the form action conditional in the template:**

```html
<form method="post"
      action="{% if assignment_id %}/assignments/{{ assignment_id }}/edit{% else %}/assignments/new{% endif %}">
```

> **Optional — get help from your agent:**
>
> ```text
> Read my assignment_form.html and app.py. Help me adapt the form
> to work for both "new" and "edit" based on whether assignment_id
> is passed in. Show me the diff only.
> ```

---

## Phase 4: Delete Route

> **Agent-assisted is fine here.** Single SQL `DELETE` + one button. Near-identical to the mark-complete pattern.

### Objective

One POST route, no GET (prevents accidental deletion via a link).

### Instructions

- [ ] Add `POST /assignments/<int:assignment_id>/delete` in `app.py`
- [ ] Add a delete button next to each assignment (tiny inline form)
- [ ] Wrap the delete button in `onsubmit="return confirm('Delete?')"` so users don't delete by accident

### Hints

**The route:**

```python
@app.post("/assignments/<int:assignment_id>/delete")
def assignments_delete(assignment_id: int):
    db = get_db()
    db.execute("DELETE FROM assignments WHERE id = ?", (assignment_id,))
    db.commit()
    return redirect(url_for("assignments_list"))
```

**Delete button with confirmation:**

```html
<form action="/assignments/{{ a.id }}/delete" method="post" style="display: inline"
      onsubmit="return confirm('Delete this assignment?')">
  <button type="submit">🗑</button>
</form>
```

> **Optional — get help from your agent:**
>
> Skip — mirrors the mark-complete pattern you already have.

---

## Phase 5: Show/Hide Completed Toggle

> **Mixed phase.** The SQL conditional is business logic (you're deciding what the list contains). The URL-param plumbing is library code.

### Objective

Your list currently filters `WHERE completed = 0`. Let the user flip to showing completed.

### Instructions

- [ ] In `/assignments`, read an optional query param `show_completed` from `request.args`
- [ ] If present, drop the `WHERE completed = 0` filter
- [ ] In the template, add a toggle link

### Hints

**Route with the toggle:**

```python
@app.get("/assignments")
def assignments_list():
    show_completed = request.args.get("show_completed") == "1"
    db = get_db()
    if show_completed:
        sql = "SELECT id, title, due_date, completed FROM assignments ORDER BY due_date ASC"
    else:
        sql = "SELECT id, title, due_date, completed FROM assignments WHERE completed = 0 ORDER BY due_date ASC"
    rows = db.execute(sql).fetchall()
    return render_template("assignments_list.html",
                           title="Assignments",
                           assignments=[dict(r) for r in rows],
                           show_completed=show_completed)
```

**Toggle link in the template:**

```html
{% if show_completed %}
  <a href="/assignments">Hide completed</a>
{% else %}
  <a href="/assignments?show_completed=1">Show completed</a>
{% endif %}
```

> **Optional — get help from your agent:**
>
> ```text
> My assignments_list page filters out completed items. Walk me
> through adding a URL-param-based toggle. I've sketched the route —
> check my logic before I commit.
> ```

---

## Checkpoint 2 Readiness

By Thursday April 23 at 3pm:

- [ ] Audit written up in the journal
- [ ] "Mark Done" button works from the list/dashboard
- [ ] Edit route works (GET pre-filled, POST updates row, reuses `validate_assignment_form`)
- [ ] Delete route works with a confirm dialog
- [ ] Show/hide completed toggle works
- [ ] `helpers.py` does **not** import `flask`
- [ ] Checkpoint 2 entry in `project.journal.md`
- [ ] Committed and pushed

## Helpful Resources

- [Checkpoint 2 Instructions](../../projects/final-project-checkpoint-2.project.md)
- [Lecture 1: The MVP](../../lectures/01-the-mvp/01-the-mvp.lecture.md)
- [Flask Setup Guide](../../resources/flask-setup.guide.md)


---

## Backlinks

The following sources link to this document:

- [April 18 -- Checkpoint 2 (Working MVP)](/unit-3/project-paths/luke-p/luke-p.path.llm.md)
