> **SUPERSEDED — HISTORICAL RECORD ONLY (archived 2026-07-18).** This document is part of the original "superpowers" wayleave build, whose design diverged from the projects-module patterns it should have mirrored. Do NOT use it as a pattern source for wayleave or any new module. The wayleave module is being realigned to the projects module (meta-driven fields, OpenLayers, shared JS) — see CLAUDE.md.

# Wayleave Module V3 Updates 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:** Resolve every issue recorded in `docs/superpowers/plans/2026-04-24-wayleave-module-v2-feedback.md` so the V2 wayleave editor reaches true parity with the `projectedit` reference pattern.

**Architecture:** Three concurrent tracks — (1) backend counts / label resolution / title→UPRN derivation in `wayleave_load.php`, `wayleave_premises_load.php`, `wayleave_map_load.php`, and `wayleave_audit_log_load.php`; (2) editor shell — relocate the Save button into `project-header-bar`, add tab badges, and hide Save on non-form tabs; (3) map overhaul — collapse the Coverage tab (polygon edit lives on the Map tab, premise-management sub-panels live on the Premises tab), introduce Entity layer group + layer-ordering fix, wrap buttons in a visible container, and add an inline delete-confirmation pattern mirroring `project_edit_v2.js`.

**Tech Stack:** PHP 7+ / PDO / PostgreSQL (existing `wayleave.*` schema — see `sql/wayleave_01_core.sql` through `sql/wayleave_06_v2_refactor.sql`), vanilla JS + jQuery 3.6, Bootstrap 5, OpenLayers, Tabulator 5.

**Reference files:** `www/html/html_body_projectedit.php`, `www/js/project_edit_v2.js`, `www/js/project_edit_v2_fileuploads.js`, `www/css/project_edit_v2.css` — the agreed V2 template the wayleave module should mirror.

**Codebase Conventions to Respect:**
- No build system. Edit `.css` / `.js` directly; ignore `www/less/` and `www/scss/`.
- No test framework. Every task ends with a manual browser verification step; screenshots are optional.
- All AJAX endpoints live in `www/fn/` and follow `echo json_encode(['success'=>true/false, ...])`.
- **SQL files live in the `/sql` subdirectory.** New DDL goes in a new numbered file (do not rewrite older files) — older module schemas are off-limits (see user memory `feedback_legacy_modules.md`).
- Commit after every task with a Conventional-Commits style message (`feat(wayleave):`, `fix(wayleave):`, `refactor(wayleave):`, `chore(wayleave):`).
- Do **not** modify the wayleave save / coverage-polygon endpoints' write logic — they are already correct. Changes here are read-path and UI-layer.
- **Keep dynamic field / section rendering untouched.** All renderer helpers (`renderStaticFields`, `renderDynamicField`, `renderSectionTabsAndPanels`, `populateAllValues`) stay as-is.

---

## File Structure

### New files
- None. All DDL stays in the existing `sql/wayleave_*.sql` files (no schema changes required — titles→UPRN linkage is a read-time JOIN against `basedata.abp.title_no`).

### Modified files

| File | Reason |
|---|---|
| `www/fn/wayleave_load.php` | Return `premise_count` (union-resolved) and `project_overlap_count` (ST_Intersects) so the header/badges populate on first paint. |
| `www/fn/wayleave_premises_load.php` | Add 4th UPRN source `title`: `basedata.abp.title_no` join onto `wayleave.agreement_landregistry`. |
| `www/fn/wayleave_map_load.php` | Mirror the new `title` source so the map's point layer includes title-derived UPRNs and can be filtered. |
| `www/fn/wayleave_audit_log_load.php` | Translate saved FK / dropdown values to user-visible labels for the Previous / New Value columns (status, team, BD manager, account, parent agreement, dynamic dropdowns). |
| `www/html/html_body_wayleaveedit.php` | Move Save button into `.project-header-bar`; remove Coverage tab; move polygon draw/edit UI to Map tab; move pending-approval / direct UPRNs / stocklists / titles sub-panels to Premises tab; add badges for Projects and Attachments tabs; wrap map buttons in a `.wl-map-button-box` container. |
| `www/js/wayleave_edit_v2.js` | Header-save visibility logic; Coverage IIFE collapsed into Map + Premises IIFEs; polygon delete-confirmation swap pattern (matches `project_edit_v2.js` lines 4284-4336); Entity layer group in `createMapLayerControls`; four Entity toggle layers (boundaries / polygon-UPRNs / direct-UPRNs / stocklist-UPRNs); categorical sort order `entity > reference > base`; badge population for Projects / Attachments / Premises on page load. |
| `www/css/wayleave_edit_v2.css` | `.wl-map-button-box` container styling (translucent panel matching `#mapButtons` in projectedit); `.wl-source-tag.title` style; header-bar Save button alignment. |

### Deleted / retired at end
- None. The Coverage IIFE in `wayleave_edit_v2.js` is merged, not deleted as a file; the tab element is removed from HTML.

---

## Task 1: Server-side counts + title-derived UPRNs

**Why this task:** Feedback bugs 1-2 (header premise count + badge populate on-load) and bug 4 (titles must pull UPRNs) and the new-feature request (Projects + Attachments badges). All four items depend on backend read-path changes, so land them together before touching any client code.

**Files:**
- Modify: `www/fn/wayleave_load.php`
- Modify: `www/fn/wayleave_premises_load.php`
- Modify: `www/fn/wayleave_map_load.php`

- [ ] **Step 1: Add title-source CTE to `wayleave_premises_load.php`**

In the `$sql` CTE, add a 4th source branch after `stocklist_uprns`:

```sql
), title_uprns AS (
    SELECT abp.uprn, 'title'::text AS src, NULL::integer AS src_id
    FROM wayleave.agreement_landregistry alr
    JOIN basedata.abp abp ON abp.title_no = alr.title_number
    WHERE alr.agreement_id = :aid4
      AND alr.is_deleted = false
      AND abp.uprn IS NOT NULL
), all_uprns AS (
    SELECT * FROM poly_uprns
    UNION ALL
    SELECT * FROM direct_uprns
    UNION ALL
    SELECT * FROM stocklist_uprns
    UNION ALL
    SELECT * FROM title_uprns
)
```

Bump the bind-loop from `$i = 1; $i <= 3` to `$i <= 4` so `:aid4` binds.

- [ ] **Step 2: Mirror the title-source CTE in `wayleave_map_load.php`**

The `$sqlR` CTE uses the same union pattern — add the same branch (renumbered to `:a5` and bumped in the bind loop).

- [ ] **Step 3: Return `premise_count` and `project_overlap_count` from `wayleave_load.php`**

Extend the existing `$sqlCounts` query. Add two new subqueries:

```sql
(SELECT COUNT(DISTINCT uprn) FROM (
    SELECT uprn FROM wayleave.agreement_polygon_uprns WHERE agreement_id=:aid8 AND is_assigned=true AND is_approved=true
    UNION ALL
    SELECT uprn FROM wayleave.agreement_uprns         WHERE agreement_id=:aid9 AND is_deleted=false
    UNION ALL
    SELECT sp.uprn FROM wayleave.agreement_stocklists ast
        JOIN stocklists.stocklist_premises sp ON sp.stocklist_id = ast.stocklist_id
        WHERE ast.agreement_id=:aid10 AND ast.is_deleted=false
    UNION ALL
    SELECT abp.uprn FROM wayleave.agreement_landregistry alr
        JOIN basedata.abp abp ON abp.title_no = alr.title_number
        WHERE alr.agreement_id=:aid11 AND alr.is_deleted=false AND abp.uprn IS NOT NULL
) u) AS premise_count,
(SELECT COUNT(*) FROM projects.projects p
    JOIN wayleave.agreement_polygons ap ON ap.agreement_id=:aid12 AND ap.is_deleted=false
    WHERE p.geom IS NOT NULL AND ST_Intersects(p.geom, ap.geom)) AS project_overlap_count
```

Bump the bind loop to `<= 12`. The query is read-only and uses the same union the premises-load uses, so the numbers agree.

- [ ] **Step 4: Verify the three endpoint payloads**

Run `?do=wayleaveedit&agreement_id=<id>` against an agreement with linked titles, polygons, and stocklists. Open the Network tab and confirm:
- `wayleave_load.php` JSON has `counts.premise_count` and `counts.project_overlap_count` populated with non-null integers.
- `wayleave_premises_load.php` returns rows whose `sources` arrays include `"title"` for UPRNs whose address in `basedata.abp` carries a matching `title_no`.
- `wayleave_map_load.php` `premises.features[].properties.sources` likewise includes `"title"` where applicable.

**Commit:** `feat(wayleave): derive UPRNs from linked titles + return premise/project counts from wayleave_load`

---

## Task 2: Resolve FK / dropdown IDs to labels in the audit log

**Why this task:** Feedback bug 3 — journal log shows raw DB values (e.g. status = `2`) instead of user-facing labels (e.g. `"In Progress"`). Fixing this on the server keeps the client code untouched and gives every future consumer the correct label.

**Files:**
- Modify: `www/fn/wayleave_audit_log_load.php`

- [ ] **Step 1: Join lookups + options to resolve static-column values**

Rewrite the first `SELECT` in the CTE (the `wayleave.agreements_history` branch) so `history_field IN ('agreement_status_id','bd_manager','account_id','parent_agreement_id','wayleave_team')` are replaced with joined label values. Use a `CASE ... WHEN ... THEN ... END` that joins:

| `history_field` | Source of label |
|---|---|
| `agreement_status_id` | `wayleave.agreement_status.description` WHERE id = history_new_value::int |
| `bd_manager` | `users.users.username` WHERE id = history_new_value::int |
| `account_id` | `accounts.accounts.account_name` WHERE account_id = history_new_value::int |
| `parent_agreement_id` | `wayleave.agreements.agreement_name` WHERE agreement_id = history_new_value::int |
| `wayleave_team` | `history_new_value` (already text, no join) |
| other | `history_new_value` (already text) |

Apply the same `CASE` to `history_old_value` for the `previous_text_value` column.

The simplest shape is a pair of correlated `LEFT JOIN LATERAL (SELECT ...)` subqueries keyed off `h.history_field`, or five chained `LEFT JOIN`s with `(h.history_field = '...' AND …)` conditions. Either works — pick whichever keeps the output columns aligned with the existing UNION ALL shape.

- [ ] **Step 2: Resolve dynamic-field dropdown option values to labels**

In the five dynamic-history branches (`…_text_history`, `…_int_history`, …), left-join `wayleave.agreement_field_dropdown_options o_new ON o_new.field_id = vt.field_id AND o_new.option_value::text = vt.field_value::text` (and a matching `o_prev` for the LAG value). Where `o_new.option_label` is non-null, surface that label through the `text_value` / `previous_text_value` columns and set `field_data_type = 'text'` so the existing `formatPrevNew()` renderer picks it up. (For non-dropdown fields the join is a no-op.)

- [ ] **Step 3: Verify label resolution**

Load an agreement in the editor. Navigate to **Journal & Audit Log**. For a row whose `Field/Action` is `Status`, the **Previous Value** / **New Value** columns must read `"In Progress"` / `"Signed"` etc., never a bare integer. Same check for `BD Manager`, `Account`, `Parent Agreement`, `Wayleave Team`, and any dynamic dropdown field.

**Commit:** `fix(wayleave): resolve FK/dropdown ids to labels in audit log`

---

## Task 3: Move Save button to header bar; hide on non-form tabs

**Why this task:** Feedback bug 5 — Save should sit beside Exit in the `.project-header-bar` and only show on tabs where form data exists (Main Details + dynamic section tabs).

**Files:**
- Modify: `www/html/html_body_wayleaveedit.php`
- Modify: `www/js/wayleave_edit_v2.js`
- Modify: `www/css/wayleave_edit_v2.css`

- [ ] **Step 1: Add the header Save button**

In `html_body_wayleaveedit.php`, inside `.project-actions`, insert before the Exit Editor anchor:

```html
<button type="submit" form="wl-main-form" id="wl-header-save" class="btn btn-xs btn-success me-2" style="display:none;">
    <i class="bi bi-save me-1"></i> Save
</button>
```

Delete the two existing `<button id="wl-main-save">` / `<button class="wl-section-save">` inline footers from `#wl-tab-main` and the dynamic section template in JS. The single header button now owns all save submissions; `type="submit" form="wl-main-form"` still fires the form's submit handler.

- [ ] **Step 2: Update `wireSave()` + nav handler in `wayleave_edit_v2.js`**

Delete the `#wl-main-save, .wl-section-save` click handler. Keep only the form submit binding.

In `wireNav()`, after the `lazyLoadForTab(target)` call, toggle the header Save button:

```js
var isFormTab = target === '#wl-tab-main' || /^#wl-tab-section-/.test(target);
$('#wl-header-save').toggle(isFormTab);
```

Also show it once on initial load in `WL.loadMain().done()` (the Main Details tab is active by default).

- [ ] **Step 3: Drop the now-unused inline Save button in `renderSectionTabsAndPanels`**

Remove the `<div class="mt-3"><button type="submit" class="btn btn-sm btn-success wl-section-save">Save</button></div>` block from the per-section panel template.

- [ ] **Step 4: Verify**

- Header shows Save on `Main Details` and every dynamic section tab.
- Header hides Save on Premises, Projects, Map, Attachments, Journal & Audit Log (and the removed-in-Task-4 Coverage tab).
- Clicking header Save submits `#wl-main-form` and toast reports the changed count.

**Commit:** `refactor(wayleave): move save button into header bar, gate by form tab`

---

## Task 4: Remove Coverage tab; redistribute its sub-panels

**Why this task:** Feedback unresolved issue 1 — two maps on one page doesn't make sense. Polygon edit moves to the Map tab (previously read-only); title linking, stocklist linking, direct-UPRN add, and pending-approval UI move to the Premises tab.

**Files:**
- Modify: `www/html/html_body_wayleaveedit.php`
- Modify: `www/js/wayleave_edit_v2.js`
- Modify: `www/css/wayleave_edit_v2.css`

- [ ] **Step 1: Strip the Coverage tab button and panel**

In `html_body_wayleaveedit.php`:
- Delete the `<button … data-nav="coverage">` entry from `#wl-nav-container`.
- Delete the entire `<div … id="wl-tab-coverage">` panel (lines ~66-142).

- [ ] **Step 2: Convert the Map tab from read-only viewer to editor**

Replace the `#wl-tab-map` panel body so it matches the old Coverage-tab map markup:
- Retain the fullscreen wrapper (`height: calc(100vh - 125px)`).
- Add the Draw / Delete buttons alongside the existing Map Layers toggle inside `.wl-map-buttons` (now to be wrapped into the Task 6 container).
- Keep the `#wl-view-popup` overlay (reused for UPRN popups).
- The `id` of the map container stays as `wl-view-map` so the existing point/popup code keeps working.

**Cookie-based map position (answer to open question 3):** The global `saveMapState` / `restoreMapState` helpers in `main.js` use a global `map` variable; the wayleave map is `viewMap` (local to the IIFE). Inline the equivalent logic directly:

- **On map init** — after `new ol.Map(...)`, read the cookie:
  ```js
  var _savedState = getCookie('mapState_' + (window.userId || 'anonymous'));
  if (_savedState) {
      try {
          var _s = JSON.parse(_savedState);
          viewMap.getView().setCenter(ol.proj.fromLonLat(_s.center));
          viewMap.getView().setZoom(_s.zoom);
      } catch(e) {}
  }
  ```
  If the cookie is absent, the existing fallback (UK overview or `fit` to polygons when they exist) continues to apply.

- **On map move/zoom** — add a `moveend` listener so the position is persisted:
  ```js
  viewMap.on('moveend', function () {
      var v = viewMap.getView();
      setCookie('mapState_' + (window.userId || 'anonymous'),
          JSON.stringify({ center: ol.proj.toLonLat(v.getCenter()), zoom: v.getZoom() }), 7);
  });
  ```

This reuses the same cookie key as `map_v5.js` so the user's last position on the main map is also their starting point in the wayleave editor (and vice-versa).

- [ ] **Step 3: Inject the old Coverage side panels into the Premises tab**

In the `#wl-tab-premises` panel body, above the existing confirmed-premises table, add the four sub-panels that used to live in the Coverage right-column:
- **Pending Approval** (`#wl-pending-pane` and its buttons).
- **Direct UPRNs** (input group + `#wl-direct-uprn-list`).
- **Attached Stocklists** (autocomplete input + `#wl-stocklist-list`).
- **Linked Titles (Land Registry)** (autocomplete title number + tenure select + `#wl-landreg-list`).

Lay them out alongside the `#wl-premises-table` using a Bootstrap row (`col-lg-7` for the table, `col-lg-5` for the sub-panels), reusing the existing `col-lg-7 / col-lg-5` ratio that Coverage used.

Also add a second nav badge to the **Premises** nav button so approvals and approved counts are visible simultaneously. The approved count keeps `#wl-premise-count-badge` (light grey). The pending count uses a new `#wl-premises-pending-badge` styled warning/orange, hidden when zero:

```html
<button … data-nav="premises" …><i class="bi bi-house"></i> Premises
    <span class="badge bg-light text-dark ms-1" id="wl-premise-count-badge"></span>
    <span class="badge bg-warning text-dark ms-1" id="wl-premises-pending-badge" style="display:none"></span>
</button>
```

- [ ] **Step 4: Collapse the Coverage IIFE into the Map + Premises IIFEs**

In `wayleave_edit_v2.js`:
- Merge the map-related code (`initMap`, `startDraw`, `deleteSelected`, `savePolygon`, `loadPolygons`, `refreshPolygonSummary`, `wireMapButtons`, polygon Select/Modify interactions, `upgradeTitleInput` stays with titles) into the existing read-only map IIFE. Rename the combined function exposed as `WL.loadMap` so lazy-loading still triggers on Map-tab activation.
- Move `refreshPending`, `renderPendingList`, `wireApprove`, `loadDirect`, `wireDirect`, `loadStocklists`, `wireStocklists`, `WL.loadLandreg`, the two Land Registry click handlers, and `upgradeTitleInput` into the Premises IIFE. Call them from inside `WL.loadPremises` (after the Tabulator replaceData).
- Delete `WL.loadCoverage` and the `case '#wl-tab-coverage':` in `lazyLoadForTab`.
- Repoint `updateBadges()` — drop the `#wl-coverage-pending-badge` writes and replace with `#wl-premises-pending-badge` writes (show when `pending_add + pending_remove > 0`, hide otherwise). The approved-count badge `#wl-premise-count-badge` stays untouched and is written separately by `renderHeader` / `WL.loadPremises`.

- [ ] **Step 5: Preserve polygon Select/Modify on the Map tab**

The existing read-only map has no interactions. Re-add `ol.interaction.Select`, `ol.interaction.Modify`, and conditional `ol.interaction.Draw` (toggled by the Draw button). Wire the Delete button but defer its confirmation behaviour to Task 8.

- [ ] **Step 6: Verify**

- Coverage tab is gone from the nav.
- Map tab now lets users draw + edit + delete polygons; polygon-save toast still fires and the pending badge updates on Premises.
- Premises tab shows Pending Approval + Direct UPRNs + Stocklists + Linked Titles alongside the premises table.
- Premises nav button shows **two** badges when there are pending approvals — the existing approved-count badge (light) and the new pending-count badge (warning); the pending badge disappears when zero.
- Adding a linked title now causes the Premises table to include UPRNs tagged `title` (driven by Task 1's new source).
- Adding a polygon still toggles pending-add rows on Premises.

**Commit:** `refactor(wayleave): collapse Coverage tab — polygons to Map tab, management panels to Premises tab`

---

## Task 5: Add Projects + Attachments + Premises (on-load) badges

**Why this task:** Feedback new-feature request + bugs 1-2. With Task 1 exposing `premise_count` and `project_overlap_count`, the badges can populate from the initial payload instead of waiting for a tab visit.

**Files:**
- Modify: `www/html/html_body_wayleaveedit.php`
- Modify: `www/js/wayleave_edit_v2.js`

- [ ] **Step 1: Add the badge spans in the nav**

Adjust the remaining nav buttons:

```html
<button … data-nav="projects" …><i class="bi bi-diagram-3"></i> Projects
    <span class="badge bg-light text-dark ms-1" id="wl-projects-count-badge"></span>
</button>
<button … data-nav="files" …><i class="bi bi-paperclip"></i> Attachments
    <span class="badge bg-light text-dark ms-1" id="wl-files-count-badge"></span>
</button>
```

`#wl-premise-count-badge` already exists; it just wasn't being populated on load.

- [ ] **Step 2: Populate all four numeric targets in `renderHeader`**

Replace the existing `renderHeader` body with:

```js
$('#wl-editor-title').text(a.agreement_name || 'Agreement #'+a.agreement_id);
$('#wl-editor-status-badge').text(a.status_description || '—');

var pc = state.counts.premise_count || 0;
$('#wl-hdr-premise-count').text(pc);
$('#wl-premise-count-badge').text(pc || '');

$('#wl-hdr-polygon-count').text(state.counts.polygon_count || 0);
$('#wl-hdr-file-count').text(state.counts.file_count || 0);
$('#wl-files-count-badge').text(state.counts.file_count || '');
$('#wl-projects-count-badge').text(state.counts.project_overlap_count || '');
```

- [ ] **Step 3: Keep tab-level refreshes in sync**

Inside `WL.loadPremises`, continue to write `resp.count` back into `#wl-hdr-premise-count` and `#wl-premise-count-badge` so a refresh click still updates them. Inside `WL.loadProjects`, after the Tabulator loads, set `$('#wl-projects-count-badge').text(resp.projects.length || '')`. Inside `WL.loadFiles`, set `$('#wl-files-count-badge').text(state.files.length || '')`.

- [ ] **Step 4: Verify**

Open an agreement cold. Without visiting any tab, confirm all four numbers show the correct values in the header bar + nav badges.

**Commit:** `feat(wayleave): populate premise/projects/attachments badges on initial load`

---

## Task 6: Entity layer group + `entity > reference > base` ordering

**Why this task:** Feedback unresolved issues 2 + 5 — sidebar groups must follow the project order, and four wayleave-specific entity layers need to exist (Wayleave boundaries, UPRNs from boundary, UPRNs from direct, UPRNs from stocklists). We can also expose a fifth for UPRNs from linked titles since Task 1 introduced that source.

**Files:**
- Modify: `www/js/wayleave_edit_v2.js`

- [ ] **Step 1: Teach `createMapLayerControls` about category ordering**

Replace the `Object.keys(byCat).sort()` iteration with a fixed order:

```js
var orderedCats = ['entity','reference','base'];
var knownCats   = new Set(orderedCats);
orderedCats.forEach(function (cat) { if (byCat[cat]) renderCat(cat); });
Object.keys(byCat).forEach(function (cat) { if (!knownCats.has(cat)) renderCat(cat); });
```

Label `entity` as `Entity Layers`, `reference` as `Reference Layers`, `base` as `Basemap Layers` (match the wording in the feedback).

- [ ] **Step 2: Split the single premise-point OL layer into four entity layers**

In the Map-tab IIFE (post-Task-4 merge), replace the single `pointLayer` with four vector layers, all sharing the same `viewPointSource` but each with a distinct style function that only draws features matching its source tag:

| Entity layer title | Filter |
|---|---|
| Wayleave boundaries | `viewPolySource` (polygon layer already exists — just title it in the sidebar) |
| UPRNs from boundary intersection | features where `props.sources.includes('polygon')` |
| UPRNs from direct add | features where `props.sources.includes('direct')` |
| UPRNs from stocklists | features where `props.sources.includes('stocklist')` |
| UPRNs from linked titles | features where `props.sources.includes('title')` |

Alternative (simpler): keep one vector source per category (so visibility toggles work via `setVisible`) — the loader pushes each feature into the source(s) matching its tags. This avoids custom-style branching.

- [ ] **Step 3: Register the entity layers in the sidebar**

After `WL.createMapLayerControls(viewMap, 'wl-view-layer-controls')` returns, append a manual `Entity Layers` `<h6>` + checkbox list at the **top** of the sidebar container. Each checkbox toggles the OL layer's `setVisible(checked)`. Default all five to visible.

- [ ] **Step 4: Verify**

- Sidebar order reads `Entity Layers → Reference Layers → Basemap Layers`.
- Each of the five entity checkboxes hides/shows its OL layer independently.
- With only `UPRNs from direct add` enabled, the map shows only the direct-added points (confirmed against the Premises table's `sources` column).

**Commit:** `feat(wayleave): add Entity layer group on map with wayleave-specific entity layers`

---

## Task 7: Wrap map buttons in a visible container

**Why this task:** Feedback unresolved issue 3 — users cannot easily see the standalone buttons floating over the map. `projectedit` wraps them in the `#mapButtons` panel (absolute-positioned, `text-end`, stacked `<p class="m-1">` rows). Match that.

**Files:**
- Modify: `www/html/html_body_wayleaveedit.php`
- Modify: `www/css/wayleave_edit_v2.css`

- [ ] **Step 1: Wrap the map buttons inside a styled panel**

On the Map tab (post-Task-4), replace the existing `.wl-map-buttons` `<div>` with a container that mirrors `#mapButtons` in `html_body_projectedit.php` lines 374-404:

```html
<div id="wl-map-buttons" class="text-end wl-map-button-box">
    <p class="m-1">
        <button type="button" id="wl-view-layers-toggle" class="btn btn-xs btn-light">
            <i class="fa fa-layer-group"></i> Map Layers
        </button>
    </p>
    <p class="m-1">
        <button type="button" id="wl-map-draw" class="btn btn-xs btn-success">
            <i class="bi bi-pencil-square"></i> Draw Polygon
        </button>
    </p>
    <p class="m-1">
        <button type="button" id="wl-map-delete" class="btn btn-xs btn-danger" disabled>
            <i class="bi bi-trash"></i> Delete Polygon
        </button>
    </p>
</div>
```

- [ ] **Step 2: Style the container**

In `wayleave_edit_v2.css`, add:

```css
.wl-map-button-box {
    position: absolute; top: 10px; right: 10px; z-index: 1000;
    background: rgba(255,255,255,0.85);
    border: 1px solid #ced4da;
    border-radius: 4px;
    padding: 4px 6px;
    box-shadow: 0 2px 4px rgba(0,0,0,0.08);
}
```

(Drop the old free-floating `.wl-map-buttons` positioning.)

- [ ] **Step 3: Verify**

Buttons appear inside a translucent panel in the top-right corner of the Map tab, visually matching `projectedit`.

**Commit:** `style(wayleave): wrap map buttons in a visible panel matching projectedit`

---

## Task 8: Polygon delete confirmation

**Why this task:** Feedback unresolved issue 4 — users must confirm before a polygon is destroyed. `project_edit_v2.js` lines 4284-4336 implement an inline Yes / No swap (click Delete → Delete hides, `Yes, Delete` + `No, Cancel` reveal). Re-use the pattern verbatim so the UX matches.

**Files:**
- Modify: `www/html/html_body_wayleaveedit.php`
- Modify: `www/js/wayleave_edit_v2.js`
- Modify: `www/css/wayleave_edit_v2.css`

- [ ] **Step 1: Add hidden confirmation siblings**

Inside `.wl-map-button-box`, place the Yes / No confirm buttons adjacent to `#wl-map-delete`:

```html
<p class="m-1">
    <button type="button" id="wl-map-delete" class="btn btn-xs btn-danger" disabled>
        <i class="bi bi-trash"></i> Delete Polygon
    </button>
    <button type="button" id="wl-map-delete-yes" class="btn btn-xs btn-danger d-none">Yes, Delete</button>
    <button type="button" id="wl-map-delete-no"  class="btn btn-xs btn-success d-none">No, Cancel</button>
</p>
```

- [ ] **Step 2: Rewire `wireMapButtons`**

Replace the existing `$('#wl-map-delete').on('click', deleteSelected);` with the three-button swap:

```js
$('#wl-map-delete').on('click', function () {
    $('#wl-map-delete').addClass('d-none');
    $('#wl-map-delete-yes, #wl-map-delete-no').removeClass('d-none');
});
$('#wl-map-delete-no').on('click', function () {
    $('#wl-map-delete-yes, #wl-map-delete-no').addClass('d-none');
    $('#wl-map-delete').removeClass('d-none');
});
$('#wl-map-delete-yes').on('click', function () {
    deleteSelected();
    $('#wl-map-delete-yes, #wl-map-delete-no').addClass('d-none');
    $('#wl-map-delete').removeClass('d-none');
});
```

- [ ] **Step 3: Verify**

- Select a polygon → Delete enables.
- Click Delete → Delete hides, Yes/No reveal.
- Click No → reverts to the resting state without touching the feature.
- Click Yes → feature removed, save fired, reverts to resting state.
- Deselecting (clicking empty map) returns Delete to the disabled state — no stale Yes/No buttons.

**Commit:** `feat(wayleave): add inline confirmation before deleting a polygon`

---

## Task 9: Regression pass + commit manifest

**Why this task:** After five user-visible changes plus two backend changes, sweep the editor end-to-end to make sure nothing from the V2 refactor scope regressed.

**Files:** none (observation only).

- [ ] **Step 1: Full editor walkthrough on a populated agreement**

1. Cold-load an agreement with polygons + direct UPRNs + stocklists + linked titles + uploaded files + historical edits.
2. Verify the header numeric row (`Confirmed Premises / Polygons / Files`), all four tab badges (`Premises / Projects / Attachments / plus pending indicator`) match the database.
3. Edit a Main Details field → save via header button → toast + audit log entry with **label** (not id).
4. Draw a polygon on the Map tab → pending badge on Premises updates; approve all → premise count rises.
5. Delete a polygon via the new Yes/No swap.
6. Add a linked title whose `basedata.abp.title_no` resolves to ≥1 UPRN → premises table gains those rows tagged `title`.
7. Confirm the `Entity > Reference > Basemap` group order; toggle each entity layer.
8. Journal entry: type + save → row appears with user + timestamp; no label regression.

- [ ] **Step 2: Sibling-editor sanity check**

Open `projectedit`, `stocklistedit`, `opportunityedit`, and `accountedit` briefly. None of them import the wayleave JS/CSS, but the shared autocomplete bootstrap in `main.js` should still work. No JS console errors.

- [ ] **Step 3: Create a companion CHANGES file**

Write `docs/superpowers/plans/2026-04-24-wayleave_module-v3-updates-CHANGES.md`, mirroring the structure of `2026-04-22-wayleave-module-v2-refactor-CHANGES.md` (Created / Modified / Feedback coverage table / Verification trail). Don't invent a Created section — this plan only modifies files.

**Commit:** `chore(wayleave): v3 updates — regression verified + changes manifest`

---

## Feedback → Task matrix

| Feedback item | Task |
|---|---|
| Bug — `#wl-hdr-premise-count` populate on page load | 1 + 5 |
| Bug — `#wl-premise-count-badge` populate on page load | 1 + 5 |
| Bug — Journal log shows raw ids | 2 |
| Bug — Linked Titles must also link associated UPRNs | 1 |
| Bug — Save button moves to header, gated by tab | 3 |
| Unresolved — Remove Coverage tab; redistribute sub-panels | 4 |
| Unresolved — Layer sidebar ordering (entity / reference / base) | 6 |
| Unresolved — Map buttons in a visible container | 7 |
| Unresolved — Polygon delete confirmation | 8 |
| Unresolved — Entity wayleave layers | 6 |
| New feature — Projects + Attachments tab badges | 5 |

---

## Open questions for the user

Flag before starting Task 4 if any of these change the design:

1. **Pending-approval badge location.** Currently lives on the Coverage tab button; plan 4.4 moves it onto the Premises tab. Is that the intended location, or should it stay near `#wl-editor-status-badge` in the header bar? (The Coverage button is gone regardless.) A- It should be alongside the premise count badge in the premise tab to indicate we have X, Y need approval.
2. **Linked-titles UPRN source.** Task 1 derives UPRNs on read via `basedata.abp.title_no`. If you expect title-linked UPRNs to persist even after the title is removed, we should write them to a new `wayleave.agreement_title_uprns` snapshot table instead. The read-time join is simpler but means removing a title instantly drops every UPRN it contributed. A - For now these can be read live, we will look at this in the future.
3. **Map-tab default zoom.** Previously the Map tab was read-only and fit-to-extent on load. Now that it's editable, should an empty agreement start at the UK overview (current behaviour) or zoom to the user's default region? A - Other maps read/and write the map position and zoom to cookies, use this as default. 
