# HOMEPAGE_FLOW.md

> 🛑 **CORRECTION 2026-08-25 — this document says FOUR panes. D7 has FIVE.**
> A fifth pane (`type=custom`, position 2, between the Mailchimp block and
> `education_provider_block`) carries 540 bytes of inline `full_html` — the
> "Your Continuing Education Provider" testimonials block. It was missed because
> the original query read the panes of `did=8` (the *handler's* id) instead of
> `did=11` (the display id stored inside the handler's `conf`); both ids exist and
> both carry panes, so the wrong query returned plausible rows rather than an error.
> The content is extracted and preserved at
> `.logs/extracted/homepage-custom-pane-pid30.html`.
> **The pane tree in §1, the order in §4b item 5, and the acceptance criterion in
> §5 are all affected.** See `HOMEPAGE_DEPENDENCY_INVENTORY.md` §0 and open
> decision **H-1**.

**What D7 actually serves at `/`, and what D10 needs to reproduce it.**
Investigated 2026-08-25 from the D7 source of truth. **No D7 file or row was modified.**

---

## 1. The answer to "what is D7's homepage?"

It is **not** a node, **not** a View, and **not** a custom callback. It is a **Page Manager
(Panels) page exported to code** — and, critically, it is composed of **six specific node IDs**.

```
GET /
 └─ D7 variable  site_frontpage = "front"                    (verified: s:5:"front";)
     └─ menu_router path 'front'
          page_callback   = page_manager_page_execute
          access_callback = ctools_access_menu
          └─ Page Manager page `homepage`
             defined in code: sites/all/modules/course_features/homepage/
                              homepage.pages_default.inc  (hook_default_page_manager_pages)
             path            = 'front'
             handler         = page_homepage_panel_context  (panel_context)
             layout          = onecol,  single region "middle"
             ├─ [0] node 29         view mode `homepage_video`   override_title=1, blank title
             ├─ [1] block           mailchimp_signup-3_tips_to_increase_client_retent
             ├─ [2] custom          540 bytes full_html - "Your Continuing Education Provider"
             │                      🛑 MISSING FROM THIS DOC UNTIL 2026-08-25; see the banner above
             ├─ [3] panels_mini     education_provider_block     (flexible layout, region "center")
             │        ├─ node 28    view mode full
             │        └─ block      views-states_block-block
             └─ [4] panels_mini     information_blocks           (onecol, region "middle")
                      ├─ node 25    full
                      ├─ node 24    full
                      ├─ node 22    full
                      └─ node 23    full
```

Rendering confirmation from the live D7 site (task 22, read-only HTTP):
`/front` → **HTTP 200**, `<title>` = *CE Online Training Courses*,
body classes = `html front not-logged-in no-sidebars`.

---

## 2. 🛑 THE HOMEPAGE HARD-CODES SIX NODE IDs

**22, 23, 24, 25, 28, 29.**

This is the single most important finding. The homepage is not "render the latest N nodes" — it
references specific nids. Two consequences:

1. **Node IDs must survive the migration.** If `ce_migrate` renumbers nodes, the homepage renders
   the *wrong content* or nothing at all, with no error. This is now a **hard acceptance criterion
   for the node migration**, not a nice-to-have.
2. It confirms why three theme templates hard-code node IDs:
   `node--29--homepage-video.tpl.php` · `node--36--full.tpl.php` ·
   `node--video--homepage-video.tpl.php`. I flagged those earlier as a risk; this is the mechanism.

`responsive.css` also targets `div#mini-panel-information_blocks` directly — so the mini-panel's
**DOM id is part of the design contract**, not just an implementation detail.

---

## 3. Why D10 `/` currently shows the login form

`system.site:page.front` is still `/user/login` — the **Drupal installer default**, never changed.
Nothing is broken; the front page was simply never configured, because there was nothing to point
it at.

**Setting it to `/front` today would produce a 404 homepage**, because no route serves `/front` in
D10 yet. Earlier I said this was blocked on the node migration; that was wrong and is corrected —
it is blocked on the **page** existing.

---

## 4. The three layers, separated

### A. ROUTING / configuration — **implementable now** ✅

A route must serve `/front`, and `system.site:page.front` must point at it.

**Approved disposition** (`MODULE_MAPPING.md` §55, `PHASE_2_MIGRATION_DECISIONS.md` §261):
`panels` → class **D** — *"7 displays + 4 Page Manager pages: rebuild as Layout Builder or Views
pages."*

**Implemented as a route + controller in `ce_core`.** Reasoning, so the choice is auditable:

| Option | Fit |
|---|---|
| **Views page** | ❌ Views selects rows by query. This page renders **six specific nodes plus two blocks** in a fixed order — not a result set |
| **Layout Builder** | ⚠️ LB is the true successor to Panels, but it attaches to an **entity**. Using it would mean **creating a new "Homepage" node that does not exist in D7** — inventing content |
| **Route + controller** ✅ | Reproduces the D7 structure exactly: one column, fixed order, mixed node/block/mini-panel content. Invents no content and no new visual language |

This is a **TECHNICAL REWRITE** under `CLAUDE.md` §5 — same observable behaviour, D10-native
implementation. The controller is deliberately thin, so swapping to Layout Builder later is
straightforward if the business prefers it.

### B. CONTENT / data — **blocked on the node migration** 🛑

Nodes **22, 23, 24, 25, 28, 29** must exist in D10 **with those exact IDs**. D10 currently has
**0 nodes**. The controller renders whatever exists and degrades gracefully per component, so the
homepage improves as content arrives rather than failing wholesale.

Also required:
- the `states_block` **view** (block display) — for `education_provider_block`
- the **Mailchimp signup block** `3_tips_to_increase_client_retent` — `drupal/mailchimp` is a
  deferred package, restored at **Phase 3.3** (`MIGRATION_STATE.md` §6)

### C. THEME / rendering — **partly done**

| Template | Status |
|---|---|
| `page.html.twig`, `html.html.twig` | ✅ converted |
| `page--front.tpl.php` | ⏳ front-page page template — **next** |
| `node--29--homepage-video.tpl.php` | ⏳ pane [0], hard-coded nid |
| `node--video--homepage-video.tpl.php` | ⏳ view-mode template for the same pane |
| `div#mini-panel-information_blocks` | must be emitted by the controller — CSS depends on it |

---

## 4b. Implementation status — 2026-08-25

| # | Item | Status |
|---|---|---|
| 1 | `page--front.tpl.php` → `page--front.html.twig` | ✅ written, runtime-unverified |
| 2 | `node--29--homepage-video` → Twig | ✅ written |
| 3 | `node--video--homepage-video` → Twig | ✅ written |
| 4 | Controller emits `#mini-panel-information_blocks` + `#mini-panel-education_provider_block` | ✅ implemented |
| 5 | Component order preserved (29 → mailchimp → education → information) | 🛑 **incomplete — the custom testimonials pane between mailchimp and education is not rendered.** Decision H-1 |
| 6 | Six node IDs preserved as named constants | ✅ `HomepageController::REQUIRED_NODE_IDS` |
| 7 | `ce_migrate` enforces the node IDs | ✅ `HomepageNodeIdRequirement` |
| 8 | `states_block` view | 🛑 blocked — views migration |
| 9 | Mailchimp block | 🛑 blocked — `drupal/mailchimp` restored at Phase 3.3 |
| 10 | `page.front` → `/front` | ⏳ `.logs/task24-frontpage-route.sh` prepared, needs a runtime |
| 11 | Never point `/` at `/user/login` | ✅ task 24 changes it away from that default |
| 12 | No placeholder business content created | ✅ **none created** |

### `page--front` is a different layout, not a variant of `page`

| `page.html.twig` | `page--front.html.twig` |
|---|---|
| `div.inner_page-main` | `div.main-content` |
| `header#navbar` | `header#navbar.front-page-header` |
| — | `div.left-content` + `div.right-content` |
| — | `div.fix-menu-banner > div.home-menu` |
| `div.main-body-content` | `div.main-container` |
| **no footer** | `footer.footer.bg-primary` + `div.footer-bottom` |
| `div.popup-side-menu` | **no off-canvas menu at all** |

The front page is the **only** template that renders the footer regions, and the **only** one with
no off-canvas mobile menu. The two files must not be harmonised.

### The two `homepage_video` templates are deliberately different

| | node 29 | generic video |
|---|---|---|
| `#mouse-scroll` indicator | ✅ | ✗ |
| title rendered | ✗ | ✅ `h2.video-title.text-danger` |

Node 29's missing title matches its pane config — `override_title = 1` with an **empty**
`override_title_text`, i.e. suppressed.

### 🛑 Drupal 10 dropped node-ID template suggestions

D7 offered `node--<nid>.tpl.php` and `node--<nid>--<view_mode>.tpl.php` automatically. **D10 does
not.** Without a hook, `node--29--homepage-video` and `node--36--full` would **never be used** and
those pages would silently fall back to generic markup — no error, just wrong output.

`cetc_d10_theme_suggestions_node_alter()` restores both suggestion forms, appended after the bundle
suggestions so the most specific still wins.

### Node-ID enforcement

`ce_migrate.homepage_node_ids` checks presence **and plausibility**: node 29 existing but being a
*course* rather than the hero *video* means the id was reused for different content — worse than it
being absent. It reports only; it never renumbers, never creates a node, and explicitly refuses
placeholder content as a remedy.

---

## 5. Acceptance criterion

> D10 `/` must reproduce the **same intended D7 flow**: front page → Page Manager equivalent →
> node 29 in `homepage_video` view mode, then the Mailchimp signup block, then the education
> provider mini-panel, then the information-blocks mini-panel — in that order, in one column.

Not "shows something". Not "shows the login page". The **same components, same order**.

> 🛑 **This criterion is out of date.** It was written against four components; D7 renders
> **five** — the testimonials custom pane sits between the Mailchimp block and the education
> provider mini-panel. Amending it is part of open decision **H-1**
> (`HOMEPAGE_DEPENDENCY_INVENTORY.md` §9), because it changes what "parity" means.

Full parity is reached when: the route exists (**A — done**), the six nodes exist with their D7 IDs
(**B — node migration**), and the view-mode templates are converted (**C — in progress**).


---

## 6. Bill of materials

The dependency inventory — the six nodes verified, the `homepage_video` view mode's per-field
settings, the `states_block` taxonomy filter, the Mailchimp form config, and a row-by-row
comparison against D10's current state — is in **`HOMEPAGE_DEPENDENCY_INVENTORY.md`**.

Three dependencies there are **not covered by any of the 45 migrations** and will not resolve
themselves when content arrives:

1. the `node.video.homepage_video` **view mode** — no migration creates view modes
2. the `states_block` **view** — code-only in D7, so no data migration reaches it
3. the **custom testimonials pane** — decision H-1
