doxa-maps/plugin-designs/regions-dashboard/src/useLastWordHighlight.js
doxa-maps-88655c70 efe2e506a8 Their heading highlight, and the page head tab 1 never had
A 68-of-68 computed-style diff was reporting the region page identical while two
visible things were wrong.

1. THEIR TITLES HIGHLIGHT THEIR LAST WORD. Every heading on their regions
surface is <h1 class="highlight" data-highlight-last>, and their
useTextHighlight wraps the final word in .color-primary. Measured live:
/regions/africa is ENTIRELY rgb(146,177,149) because "Africa" is the last word,
and /regions/middle-east renders 'Middle <span class="color-primary">East</span>'.
Ours was entirely dark.

★ HOW A 68/68 DIFF MISSED AN ENTIRELY GREEN HEADING: the h1 spec listed
fontSize, lineHeight, fontFamily, textAlign and textTransform — and not COLOUR.
The lesson is not "add colour to the list". It is that a property absent from
the spec is NOT MEASURED, and unmeasured is not identical. Same shape as the
font miss, where both sides named Poppins and only a glyph-width oracle showed
we were not rendering it. Colour now covers the h1, its highlight span, the stat
values, the headline figures and the goal lines: 68 -> 73 properties, still 0
differ. I found this reading their source for a different card, not from a check.

Ported declaratively rather than as their DOM walk. Theirs mutates innerHTML
once onMounted and stamps a processed flag — right for a Nuxt page whose title
never changes. Ours changes on every selection, so an imperative one-shot would
either fight Vue's re-render or go stale after the first subject. Same logic,
expressed where Vue can keep it true.

2. TAB 1 HAD NO PAGE HEAD AT ALL. pages/regions/index.vue:47-58 opens with that
h1 plus a .color-brand-lighter strapline; we rendered only the region cards.
Invisible inside the composite, where the tab strip names the view, and plainly
missing on the regions-list leaf, which is a page in its own right.

★ AND THE IMAGE CAUGHT MY OWN DEFECT. A whitespace-only <span> </span> between
two inline elements COLLAPSES: "Middle East" rendered "MIDDLEEAST". Every
assertion was green — the colour was right, the span was there, the text
content was right once whitespace-normalised. Only the screenshot showed it.
The space now lives inside the text interpolation, and an innerHTML assertion
guards it that was first shown to FAIL on the collapsed form.

Verified: region 73/73 vs live, country 28/28, mobile 44/0 and 26/0 at 390+768,
i18n, chooser and dashboard suites green, no gate newly red.
2026-09-24 03:22:15 +00:00

38 lines
2.1 KiB
JavaScript

/**
* useLastWordHighlight.js — their `.highlight` + `data-highlight-last` heading treatment.
*
* WHAT IT IS. Every page heading on DX-life's regions surface is
* `<h1 class="highlight" data-highlight-last>`, and app/composables/useTextHighlight.ts wraps the
* LAST WORD of that heading in `<span class="color-primary">`. So their titles read with the final
* word in brand primary:
* /regions/africa -> the whole title green, because "Africa" IS the last word
* /regions/middle-east -> "Middle" in ink, "East" in green
* MEASURED on the live pages: h1 colour rgb(42,51,42) with an inner span at rgb(146,177,149).
*
* ★ HOW IT WAS MISSED. A 68-of-68 computed-style diff of the region page reported no differences
* while our titles were entirely dark, because the diff compared the h1's fontSize, lineHeight,
* fontFamily and textAlign — and never its COLOUR, nor the existence of an inner span. The lesson
* is not "add colour to the list"; it is that a property absent from the spec list is not
* measured, and unmeasured is not identical.
*
* WHY DECLARATIVE AND NOT THEIR DOM WALK. Theirs mutates innerHTML once onMounted and stamps
* data-highlightProcessed so re-runs are cheap — correct for a Nuxt page whose title never
* changes. Our heading changes on every selection (chooser -> region -> country), so an
* imperative one-shot would either fight Vue's re-render or go stale after the first subject.
* Same logic, expressed where Vue can keep it true: split the words, wrap the last.
*/
/**
* Split a heading for last-word highlighting.
* @param {string} text
* @returns {{ head: string, last: string }} head = everything before the final word (may be ''),
* last = the final word. Their split is on any whitespace run, after trimming, so word
* boundaries are right regardless of how the template was formatted.
*/
export function splitLastWord(text) {
const words = String(text ?? '').trim().split(/\s+/).filter(Boolean)
if (!words.length) return { head: '', last: '' }
const last = words[words.length - 1]
const head = words.slice(0, -1).join(' ')
return { head, last }
}