# BloomBase UI 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:** Replace Bootstrap 5 with Tailwind CSS, add a 5-tab bottom navigation, extend `Location` with a cover photo field, restyle all existing screens, and add a new Area Detail page — producing the vibrant organic aesthetic from Naomi's mockup. **Architecture:** New `bloombase.css` holds fonts + CSS blob/flower decorations; `base.html` is fully rewritten with Tailwind CDN + bottom nav; all child templates are rewritten to use Tailwind utilities; one new view + template (`location_detail`) handles the Area Detail screen; `Location` model gains a nullable `cover_photo` ImageField with its own migration. **Tech Stack:** Django 5.x, HTMX 1.9, Tailwind CSS Play CDN (development), Google Fonts (Great Vibes + Nunito + Pacifico), SQLite --- ## File Map | File | Action | |---|---| | `plants/static/plants/css/bloombase.css` | **Create** — fonts, blob shapes, canvas flowers | | `plants/templates/plants/base.html` | **Rewrite** — Tailwind, bottom nav, decorations | | `plants/models.py` | **Modify** — add `cover_photo` to `Location` | | `plants/migrations/XXXX_location_cover_photo.py` | **Create** via `makemigrations` | | `plants/forms.py` | **Modify** — add `LocationForm` with `cover_photo` | | `plants/views/locations.py` | **Modify** — use `LocationForm`; add `location_detail` view | | `plants/urls.py` | **Modify** — add `location_detail` URL | | `plants/templates/plants/locations.html` | **Rewrite** — My Garden screen | | `plants/templates/plants/location_detail.html` | **Create** — Area Detail screen | | `plants/templates/plants/location_edit.html` | **Rewrite** — add cover photo field | | `plants/templates/plants/dashboard.html` | **Rewrite** — camera-first Home screen | | `plants/templates/plants/plant_list.html` | **Rewrite** — search + filter chips | | `plants/templates/plants/plant_detail.html` | **Rewrite** — hero + care + CTA | | `plants/templates/plants/identify_upload.html` | **Rewrite** — viewfinder camera UI | | `plants/templates/plants/identify_confirm.html` | **Rewrite** — match list restyle | | `plants/tests/test_location_redesign.py` | **Create** — model + view tests | --- ## Task 1: Static CSS — bloombase.css Creates the custom stylesheet: Google Fonts import, CSS blob background shapes, and fixed-position SVG flower decorations. **Files:** - Create: `plants/static/plants/css/bloombase.css` - [ ] **Step 1.1: Create the static directory and file** ```bash mkdir -p plants/static/plants/css ``` - [ ] **Step 1.2: Write bloombase.css** ```css /* plants/static/plants/css/bloombase.css */ @import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800;900&family=Great+Vibes&family=Pacifico&display=swap'); /* ── Body canvas ── */ body { background-color: #FFF8F2; font-family: 'Nunito', sans-serif; min-height: 100vh; position: relative; overflow-x: hidden; } /* ── Background blob shapes ── */ .bb-blob-tl { position: fixed; top: -100px; left: -80px; width: 380px; height: 380px; background: radial-gradient(ellipse at 45% 45%, #F9C090 0%, #F07040 55%, #E05020 100%); border-radius: 62% 38% 54% 46% / 58% 44% 56% 42%; opacity: 0.18; pointer-events: none; z-index: 0; } .bb-blob-br { position: fixed; bottom: -80px; right: -60px; width: 340px; height: 340px; background: radial-gradient(ellipse at 55% 55%, #FFD080 0%, #F8A040 60%, #F07040 100%); border-radius: 44% 56% 38% 62% / 52% 48% 52% 48%; opacity: 0.16; pointer-events: none; z-index: 0; } /* ── Canvas SVG flowers ── */ .bb-flower { position: fixed; pointer-events: none; z-index: 0; } .bb-flower-tr { top: 16px; right: 24px; } .bb-flower-ml { top: 38%; left: -4px; } .bb-flower-br { bottom: 72px; right: 48px; } .bb-flower-bl { bottom: 88px; left: 32px; } /* ── Brand fonts ── */ .font-script { font-family: 'Great Vibes', cursive; } .font-pacifico { font-family: 'Pacifico', cursive; } .font-nunito { font-family: 'Nunito', sans-serif; } /* ── Bottom nav stays above content ── */ #bottom-nav { position: fixed; bottom: 0; left: 0; right: 0; background: #fff; border-top: 1px solid #F0E8DF; height: 62px; display: flex; align-items: center; justify-content: space-around; z-index: 50; padding-bottom: env(safe-area-inset-bottom, 0); } /* ── FAB inside nav ── */ .bb-fab { width: 44px; height: 44px; border-radius: 50%; background: linear-gradient(145deg, #F58040, #E84020); box-shadow: 0 4px 16px rgba(240, 80, 40, 0.5); display: flex; align-items: center; justify-content: center; color: #fff; font-size: 24px; line-height: 1; font-weight: 300; flex-shrink: 0; } /* ── Gradient CTA button ── */ .btn-bloom { display: block; width: 100%; background: linear-gradient(135deg, #F58040, #E84020); color: #fff; border: none; border-radius: 50px; padding: 12px 24px; font-family: 'Nunito', sans-serif; font-weight: 800; font-size: 15px; text-align: center; box-shadow: 0 4px 14px rgba(240,80,40,0.4); cursor: pointer; text-decoration: none; } .btn-bloom-outline { display: block; width: 100%; background: transparent; color: #1A1208; border: 1.5px solid #E0D4C8; border-radius: 50px; padding: 11px 24px; font-family: 'Nunito', sans-serif; font-weight: 700; font-size: 15px; text-align: center; cursor: pointer; text-decoration: none; } /* ── White pill button (hero overlays) ── */ .btn-pill { background: rgba(255,255,255,0.88); border: none; border-radius: 50px; padding: 6px 14px; font-size: 13px; font-weight: 700; color: #1A1208; backdrop-filter: blur(4px); cursor: pointer; text-decoration: none; } /* ── Confidence bar ── */ .conf-track { height: 5px; background: #F0E4D8; border-radius: 3px; overflow: hidden; } .conf-fill { height: 100%; background: linear-gradient(90deg, #F8A060, #E84020); border-radius: 3px; } /* ── Area thumbnail gradient placeholders ── */ .at-placeholder-1 { background: linear-gradient(135deg, #88C860, #E85878, #A0D060); } .at-placeholder-2 { background: linear-gradient(150deg, #B8E060, #F0A840, #E87830); } .at-placeholder-3 { background: linear-gradient(120deg, #F8B840, #88C060, #F0C860); } .at-placeholder-4 { background: linear-gradient(140deg, #D0A8E8, #68B858, #C888E0); } .at-placeholder-5 { background: linear-gradient(130deg, #78D8C8, #E8F0A0, #60C8A8); } .at-placeholder-6 { background: linear-gradient(125deg, #F8A0B0, #B8E060, #F09060); } /* ── Viewfinder corners ── */ .vf-corner { position: absolute; width: 18px; height: 18px; border-color: rgba(255,255,255,0.9); border-style: solid; } .vf-tl { top: 10px; left: 10px; border-width: 2.5px 0 0 2.5px; border-radius: 2px 0 0 0; } .vf-tr { top: 10px; right: 10px; border-width: 2.5px 2.5px 0 0; border-radius: 0 2px 0 0; } .vf-bl { bottom: 10px; left: 10px; border-width: 0 0 2.5px 2.5px; border-radius: 0 0 0 2px; } .vf-br { bottom: 10px; right: 10px; border-width: 0 2.5px 2.5px 0; border-radius: 0 0 2px 0; } ``` - [ ] **Step 1.3: Commit** ```bash git add plants/static/plants/css/bloombase.css git commit -m "feat: add bloombase.css — fonts, blobs, canvas flowers, shared components" ``` --- ## Task 2: Rewrite base.html Full replacement of the Bootstrap top-nav shell with Tailwind CDN + 5-tab bottom nav + background canvas decorations. **Files:** - Modify: `plants/templates/plants/base.html` - [ ] **Step 2.1: Replace base.html** ```html {# plants/templates/plants/base.html #} {% block title %}BloomBase{% endblock %} {% load static %} {% block extra_head %}{% endblock %}
{% block content %}{% endblock %}
``` - [ ] **Step 2.2: Verify the app still loads (no crash)** ```bash cd /home/stephan/devel/python/plantdb python manage.py check ``` Expected: `System check identified no issues (0 silenced).` - [ ] **Step 2.3: Commit** ```bash git add plants/templates/plants/base.html git commit -m "feat: rewrite base.html — Tailwind CDN, 5-tab bottom nav, canvas decorations" ``` --- ## Task 3: Location model — add cover_photo + migration **Files:** - Modify: `plants/models.py` - Create: migration (via `makemigrations`) - Create: `plants/tests/test_location_redesign.py` - [ ] **Step 3.1: Write failing test first** ```python # plants/tests/test_location_redesign.py import pytest from django.core.files.uploadedfile import SimpleUploadedFile from plants.models import Location @pytest.mark.django_db def test_location_cover_photo_field_exists(): """Location can be saved with and without a cover_photo.""" loc = Location.objects.create(name='Test Garden') assert loc.cover_photo.name == '' or loc.cover_photo.name is None fake_img = SimpleUploadedFile('cover.jpg', b'fake-image-data', content_type='image/jpeg') loc.cover_photo = fake_img loc.save() loc.refresh_from_db() assert loc.cover_photo.name.startswith('locations/') ``` - [ ] **Step 3.2: Run test — expect failure** ```bash python -m pytest plants/tests/test_location_redesign.py::test_location_cover_photo_field_exists -v ``` Expected: `FAILED` — `AttributeError: 'Location' object has no attribute 'cover_photo'` - [ ] **Step 3.3: Add cover_photo to Location model** In `plants/models.py`, change: ```python class Location(models.Model): name = models.CharField(max_length=200, unique=True) ``` to: ```python class Location(models.Model): name = models.CharField(max_length=200, unique=True) cover_photo = models.ImageField(upload_to='locations/', blank=True, null=True) ``` - [ ] **Step 3.4: Create migration** ```bash python manage.py makemigrations plants --name location_cover_photo ``` Expected output: `Migrations for 'plants': plants/migrations/XXXX_location_cover_photo.py` - [ ] **Step 3.5: Apply migration** ```bash python manage.py migrate ``` Expected: `Applying plants.XXXX_location_cover_photo... OK` - [ ] **Step 3.6: Run test — expect pass** ```bash python -m pytest plants/tests/test_location_redesign.py::test_location_cover_photo_field_exists -v ``` Expected: `PASSED` - [ ] **Step 3.7: Commit** ```bash git add plants/models.py plants/migrations/ plants/tests/test_location_redesign.py git commit -m "feat: add Location.cover_photo ImageField + migration + test" ``` --- ## Task 4: LocationForm + updated views + new location_detail + URL **Files:** - Modify: `plants/forms.py` - Modify: `plants/views/locations.py` - Modify: `plants/urls.py` - Modify: `plants/tests/test_location_redesign.py` - [ ] **Step 4.1: Write failing tests** Append to `plants/tests/test_location_redesign.py`: ```python from django.test import Client from django.urls import reverse @pytest.fixture def client(): return Client() @pytest.mark.django_db def test_location_detail_view_returns_200(client): loc = Location.objects.create(name='My Terrace') resp = client.get(reverse('location_detail', kwargs={'pk': loc.pk})) assert resp.status_code == 200 assert b'My Terrace' in resp.content @pytest.mark.django_db def test_location_edit_with_cover_photo(client): loc = Location.objects.create(name='Balcony') fake_img = SimpleUploadedFile('cover.jpg', b'data', content_type='image/jpeg') resp = client.post( reverse('location_edit', kwargs={'pk': loc.pk}), {'name': 'Balcony Updated', 'cover_photo': fake_img}, ) assert resp.status_code == 302 loc.refresh_from_db() assert loc.name == 'Balcony Updated' assert loc.cover_photo.name.startswith('locations/') ``` - [ ] **Step 4.2: Run tests — expect failure** ```bash python -m pytest plants/tests/test_location_redesign.py -v -k "detail or edit_with_cover" ``` Expected: `FAILED` — `NoReverseMatch: location_detail` and related errors. - [ ] **Step 4.3: Add LocationForm to forms.py** Append to `plants/forms.py`: ```python class LocationForm(forms.ModelForm): class Meta: model = Location fields = ['name', 'cover_photo'] ``` - [ ] **Step 4.4: Rewrite views/locations.py** ```python # plants/views/locations.py from django.shortcuts import render, get_object_or_404, redirect from django.views.decorators.http import require_POST from django.db.models import Count from plants.models import Location, Plant from plants.forms import LocationForm def location_list(request): if request.method == 'POST': name = request.POST.get('name', '').strip() if name: Location.objects.get_or_create(name=name) return redirect('location_list') locations = Location.objects.annotate(plant_count=Count('plants')) return render(request, 'plants/locations.html', { 'locations': locations, 'active_tab': 'garden', }) def location_detail(request, pk): location = get_object_or_404(Location, pk=pk) plants = location.plants.select_related('species').prefetch_related('photos').all() return render(request, 'plants/location_detail.html', { 'location': location, 'plants': plants, 'active_tab': 'garden', }) def location_edit(request, pk): location = get_object_or_404(Location, pk=pk) if request.method == 'POST': form = LocationForm(request.POST, request.FILES, instance=location) if form.is_valid(): form.save() return redirect('location_list') else: form = LocationForm(instance=location) return render(request, 'plants/location_edit.html', { 'location': location, 'form': form, 'active_tab': 'garden', }) @require_POST def location_delete(request, pk): location = get_object_or_404(Location, pk=pk) Plant.objects.filter(location=location).update(location=None) location.delete() return redirect('location_list') ``` - [ ] **Step 4.5: Add location_detail URL** In `plants/urls.py`, add after the existing `locations/` URL: ```python path('locations//', locations.location_detail, name='location_detail'), ``` The full relevant section of urlpatterns should look like: ```python path('locations/', locations.location_list, name='location_list'), path('locations//', locations.location_detail, name='location_detail'), path('locations//edit/', locations.location_edit, name='location_edit'), path('locations//delete/', locations.location_delete, name='location_delete'), ``` - [ ] **Step 4.6: Run tests — expect pass** ```bash python -m pytest plants/tests/test_location_redesign.py -v ``` Expected: all `PASSED` - [ ] **Step 4.7: Commit** ```bash git add plants/forms.py plants/views/locations.py plants/urls.py plants/tests/test_location_redesign.py git commit -m "feat: add LocationForm with cover_photo, location_detail view, update location_edit" ``` --- ## Task 5: locations.html — My Garden screen **Files:** - Modify: `plants/templates/plants/locations.html` - [ ] **Step 5.1: Rewrite locations.html** ```html {# plants/templates/plants/locations.html #} {% extends "plants/base.html" %} {% block title %}My Garden — BloomBase{% endblock %} {% block content %} {% load static %}

My Garden

Edit
Search plants or areas
{% for loc in locations %} {% with idx=forloop.counter0 %}
{% if loc.cover_photo %} {{ loc.name }} {% else %}
{% endif %}
{{ loc.name }}
{{ loc.plant_count }} plant{{ loc.plant_count|pluralize }}
{% endwith %} {% empty %}

No areas yet — add one below!

{% endfor %}
{% csrf_token %}
{% endblock %} ``` - [ ] **Step 5.2: Verify page renders** ```bash python manage.py runserver 0.0.0.0:8000 ``` Open http://localhost:8000/locations/ — expect My Garden screen with area rows and + form. - [ ] **Step 5.3: Commit** ```bash git add plants/templates/plants/locations.html git commit -m "feat: restyle locations.html as My Garden screen with cover photo thumbnails" ``` --- ## Task 6: location_detail.html — Area Detail screen **Files:** - Create: `plants/templates/plants/location_detail.html` - [ ] **Step 6.1: Create location_detail.html** ```html {# plants/templates/plants/location_detail.html #} {% extends "plants/base.html" %} {% block title %}{{ location.name }} — BloomBase{% endblock %} {% block content %}
{% if location.cover_photo %} {{ location.name }} {% else %}
{% endif %}
← Back Edit

{{ location.name }}

{{ plants.count }} plant{{ plants.count|pluralize }}

{% if plants %} {% else %}
🪴

No plants here yet.

Identify a plant
{% endif %}
{% endblock %} ``` - [ ] **Step 6.2: Verify page renders** Open http://localhost:8000/locations/1/ (replace 1 with a real pk) — expect hero + plant grid. - [ ] **Step 6.3: Commit** ```bash git add plants/templates/plants/location_detail.html git commit -m "feat: add location_detail.html — area detail with cover photo header + plant grid" ``` --- ## Task 7: location_edit.html — add cover photo upload **Files:** - Modify: `plants/templates/plants/location_edit.html` - [ ] **Step 7.1: Rewrite location_edit.html** ```html {# plants/templates/plants/location_edit.html #} {% extends "plants/base.html" %} {% block title %}Edit Area — BloomBase{% endblock %} {% block content %}

Edit area

{% csrf_token %}
{% if location.cover_photo %}
Current cover
{% endif %}
Cancel
{% endblock %} ``` - [ ] **Step 7.2: Verify form submits with photo** Open http://localhost:8000/locations/1/edit/ — fill name, attach image, save. Confirm redirect to /locations/ with updated thumbnail. - [ ] **Step 7.3: Commit** ```bash git add plants/templates/plants/location_edit.html git commit -m "feat: restyle location_edit.html with cover photo upload field" ``` --- ## Task 8: dashboard.html — camera-first Home screen Also update `dashboard` view to pass `active_tab`. **Files:** - Modify: `plants/views/dashboard.py` - Modify: `plants/templates/plants/dashboard.html` - [ ] **Step 8.1: Update dashboard view to pass active_tab and location count** ```python # plants/views/dashboard.py from datetime import date from django.shortcuts import render from plants.models import Plant, Location def dashboard(request): today = date.today() all_plants = list(Plant.objects.select_related('species').all()) blooming_now = [p for p in all_plants if today.month in (p.bloom_months or [])] latest_plants = Plant.objects.select_related('species', 'location').prefetch_related('photos').order_by('-pk')[:6] location_count = Location.objects.count() return render(request, 'plants/dashboard.html', { 'total_plants': len(all_plants), 'blooming_now': blooming_now, 'blooming_now_count': len(blooming_now), 'today': today, 'latest_plants': latest_plants, 'location_count': location_count, 'active_tab': 'home', }) ``` - [ ] **Step 8.2: Rewrite dashboard.html** ```html {# plants/templates/plants/dashboard.html #} {% extends "plants/base.html" %} {% load plant_extras %} {% block title %}BloomBase{% endblock %} {% block content %}

Hello, Plant Lover! 🌿

What are we growing today?

Identify a plant and add it to Bloombase.

🪴
My Garden
{{ total_plants }} Plant{{ total_plants|pluralize }} · {{ location_count }} Area{{ location_count|pluralize }}
{% if latest_plants %}

🌿 Recently added

{% endif %}
{% endblock %} ``` - [ ] **Step 8.3: Verify home screen** Open http://localhost:8000/ — expect camera viewfinder + My Garden pill + recent plants. - [ ] **Step 8.4: Commit** ```bash git add plants/views/dashboard.py plants/templates/plants/dashboard.html git commit -m "feat: restyle dashboard as camera-first Home screen" ``` --- ## Task 9: plant_list.html — search + location filter chips Also update the `plant_list` view to pass `active_tab`. **Files:** - Modify: `plants/views/plants.py` (just add `active_tab` to plant_list context) - Modify: `plants/templates/plants/plant_list.html` - [ ] **Step 9.1: Add active_tab to plant_list view** In `plants/views/plants.py`, find the `plant_list` view and add `'active_tab': 'garden'` to the context dict it passes to `render`. (Check the current return statement and add the key alongside existing context keys.) - [ ] **Step 9.2: Rewrite plant_list.html** ```html {# plants/templates/plants/plant_list.html #} {% extends "plants/base.html" %} {% block title %}Plants — BloomBase{% endblock %} {% block content %}

All Plants

+
All {% for loc in locations %} {{ loc.name }} {% endfor %}
{% include "plants/partials/plant_list_results.html" %}
{% endblock %} ``` - [ ] **Step 9.3: Update plant_list_results partial** Open `plants/templates/plants/partials/plant_list_results.html` and check its current content. Update it to use Tailwind card style: ```html {# plants/templates/plants/partials/plant_list_results.html #} {% for plant in plants %} {% if plant.species and plant.species.api_image_url %} {% elif plant.thumbnail_url %} {% else %}
🌿
{% endif %}
{{ plant.name }}
{% if plant.location %} {{ plant.location.name }} {% endif %}
{% empty %}

No plants found.

{% endfor %} ``` - [ ] **Step 9.4: Verify plant list** Open http://localhost:8000/plants/ — expect search + chips + card list. - [ ] **Step 9.5: Commit** ```bash git add plants/views/plants.py plants/templates/plants/plant_list.html plants/templates/plants/partials/plant_list_results.html git commit -m "feat: restyle plant_list with Tailwind — search, filter chips, plant cards" ``` --- ## Task 10: plant_detail.html — hero + care + CTA Also add `active_tab` to the `plant_detail` view. **Files:** - Modify: `plants/views/plants.py` (add `active_tab` to plant_detail context) - Modify: `plants/templates/plants/plant_detail.html` - [ ] **Step 10.1: Add active_tab to plant_detail view context** In `plants/views/plants.py`, find the `plant_detail` view and add `'active_tab': 'garden'` to the context. - [ ] **Step 10.2: Rewrite plant_detail.html** ```html {# plants/templates/plants/plant_detail.html #} {% extends "plants/base.html" %} {% load plant_extras %} {% block title %}{{ plant.name }} — BloomBase{% endblock %} {% block content %}
{% if plant.species and plant.species.api_image_url %} {{ plant.name }} {% elif plant.thumbnail_url %} {{ plant.name }} {% else %}
🌿
{% endif %}
← Back Edit

{{ plant.name }}

{% if plant.species %}
{{ plant.species.scientific_name }}
{% endif %} {% if plant.location %}

📍 {{ plant.location.name }}

{% endif %} {% if plant.watering or plant.sunlight or plant.species %}
{% if plant.watering %}
💧 {{ plant.get_watering_display }}
{% elif plant.species and plant.species.watering %}
💧 {{ plant.species.watering }}
{% endif %} {% if plant.sunlight %}
☀️ {{ plant.get_sunlight_display }}
{% elif plant.species and plant.species.sunlight %}
☀️ {{ plant.species.sunlight }}
{% endif %} {% if plant.species and plant.species.frost_hardiness_c is not None %}
🌡️ {{ plant.species.frost_hardiness_c }}°C min
{% endif %} {% if plant.species and plant.species.max_height_cm %}
📏 {{ plant.species.max_height_cm }} cm
{% endif %}
{% endif %} {% if plant.bloom_months %}

🌸 Bloom months

{% month_strip plant.bloom_months %}
{% endif %} {% if plant.notes %}

{{ plant.notes|linebreaksbr }}

{% endif %} {% if plant.pruning_months %} {% include "plants/partials/pruning_strip.html" %} {% endif %}
{% endblock %} ``` - [ ] **Step 10.3: Verify plant detail** Open http://localhost:8000/plants/1/ — expect hero + care row + CTA buttons. - [ ] **Step 10.4: Commit** ```bash git add plants/views/plants.py plants/templates/plants/plant_detail.html git commit -m "feat: restyle plant_detail — hero, care row, gradient CTA buttons" ``` --- ## Task 11: identify_upload.html — viewfinder camera UI Also add `active_tab` to `identify_upload` view. **Files:** - Modify: `plants/views/identify.py` (add `active_tab`) - Modify: `plants/templates/plants/identify_upload.html` - [ ] **Step 11.1: Add active_tab to identify_upload view** In `plants/views/identify.py`, find the `identify_upload` view. Add `'active_tab': 'home'` to the context dict passed to render (for the GET response). - [ ] **Step 11.2: Rewrite identify_upload.html** ```html {# plants/templates/plants/identify_upload.html #} {% extends "plants/base.html" %} {% block title %}Identify plant — BloomBase{% endblock %} {% block content %}

Identify plant

Add one or more photos, tag what each shows, then tap Identify.

{% if error %}
{{ error }}
{% endif %}
{% csrf_token %}
{% endblock %} ``` - [ ] **Step 11.3: Verify identify screen** Open http://localhost:8000/identify/ — expect viewfinder box + shutter button + organ tags. - [ ] **Step 11.4: Commit** ```bash git add plants/views/identify.py plants/templates/plants/identify_upload.html git commit -m "feat: restyle identify_upload as camera-first viewfinder screen" ``` --- ## Task 12: identify_confirm.html — match list restyle **Files:** - Modify: `plants/templates/plants/identify_confirm.html` - [ ] **Step 12.1: Rewrite identify_confirm.html** ```html {# plants/templates/plants/identify_confirm.html #} {% extends "plants/base.html" %} {% block title %}Confirm species — BloomBase{% endblock %} {% block content %}

Is this your plant?

Tap the correct species, then confirm.

{% csrf_token %}
{% for m in matches %}
{% if m.image_url %} {{ m.scientific_name }} {% else %}
🌿
{% endif %}
{{ m.scientific_name }}
{% if m.common_names %}
{{ m.common_names|join:", " }}
{% endif %}
{{ m.family }}
{% widthratio m.score 1 100 %}%
{% endfor %}
{% endblock %} ``` - [ ] **Step 12.2: Verify identify confirm** Run a real identification or view the confirm page directly — expect match cards with confidence badges and gradient confirm button. - [ ] **Step 12.3: Commit** ```bash git add plants/templates/plants/identify_confirm.html git commit -m "feat: restyle identify_confirm — match cards with confidence badges" ``` --- ## Self-Review **Spec coverage check:** | Spec item | Task | |---|---| | Replace Bootstrap 5 with Tailwind CSS | Task 2 (base.html) | | 5-tab bottom nav (Home, Garden, +, Care, Profile) | Task 2 | | `Location.cover_photo` + migration | Task 3 | | `LocationForm` with cover_photo | Task 4 | | `location_detail` view + URL | Task 4 | | locations.html My Garden screen | Task 5 | | location_detail.html Area Detail | Task 6 | | location_edit.html with cover photo | Task 7 | | dashboard.html camera-first | Task 8 | | plant_list.html | Task 9 | | plant_detail.html | Task 10 | | identify_upload.html | Task 11 | | identify_confirm.html | Task 12 | | Canvas blob decorations + SVG flowers | Task 1 + Task 2 | | Great Vibes + Nunito + Pacifico fonts | Task 1 | | Gradient CTA buttons | Task 1 (.btn-bloom) | | `active_tab` nav active state | Tasks 4, 8, 9, 10, 11 | | Care + Profile stubs | Task 2 (href="#") | **Gaps identified and resolved:** - `plant_list_results.html` partial was missing — added in Task 9.3 - `plant_list` and `plant_detail` views need `active_tab` in context — noted in Tasks 9 and 10 - `identify_upload` view needs `active_tab` — noted in Task 11 **Type/name consistency:** - `active_tab` values: `'home'` (dashboard, identify), `'garden'` (locations, plant_list, plant_detail, location_detail, location_edit) - CSS class names used consistently: `.btn-bloom`, `.btn-bloom-outline`, `.btn-pill`, `.bb-fab`, `.vf-corner .vf-tl/tr/bl/br` - `at-placeholder-N` classes 1–6 defined in bloombase.css and used in locations.html + location_detail.html **No placeholders:** All template code is complete. All CSS is complete.