PR #6224 · rendered evidence

Four decreasing tints, actually decreasing

The prospects list’s new leasing funnel, rendered in both themes across five data states. Produced because the review asked for pixels — and looking at them found two defects the whole test suite was blind to.

What you are looking at

The real <ProspectFunnel> component, server-rendered through renderToStaticMarkup with the app’s own globals.css tokens, photographed in Chromium at 2×. Synthetic counts, so there is no customer data on the page. This is the component, not the live route — the layout around it (the lg:grid-cols-3 split with leads-by-source) is not shown.

Each card is a different state: populated with a stage selected, sparse (1 application, 0 tours — the gap that must render no percentage), empty cohort, occupancy not measured, and the route shell.

Light

Light theme, before the fixes: the top two bars are nearly the same tint, and every zero-value stage draws a small violet stub whose closing rule cuts through the stage label.
before tints 26 / 20 / 15 / 11
Light theme, after: four clearly descending violet tints, and zero-value stages render as empty tracks.
after tints 38 / 26 / 16 / 9

Dark

The harder of the two, and the one that exposed the palette problem: mixed into a near-black card, the original four tints collapsed into a single dark violet.

Dark theme, before: all four bars read as roughly the same dark violet, and zero-value stages show stubs over their labels.
before tints 26 / 20 / 15 / 11
Dark theme, after: the four tints separate cleanly from strong violet down to faint, and empty stages show bare tracks.
after tints 38 / 26 / 16 / 9

What looking caught

Neither of these could have failed an assertion. Both are now pinned by one.

A stage nobody reached drew a bar anyway

The 8 % width floor exists so a real-but-tiny stage still reads as a bar. Applied to zero, it drew a violet stub whose 2 px closing rule sliced straight through the stage label — Inquir|ies — claiming a magnitude for a stage with no one in it. Visible on every bar of the empty-cohort card and on two bars of the sparse one.

Zero now renders an empty track, and the closing rule rides the fill so it disappears with it. Pinned by a stage nobody reached draws NO bar, which reads the fill span’s own width and border-right.

The one-hue ladder wasn’t a ladder

The entire palette decision is “one hue at four decreasing tints, shade = magnitude”. At 26 / 20 / 15 / 11 the top two bars were indistinguishable in light mode, and in dark mode all four flattened into one violet. The design was specified correctly and not delivered, in either theme.

Widened to 38 / 26 / 16 / 9 with matching edge-rule opacities. The steps are deliberately uneven — mixing toward the card colour compresses perceptually as it goes, so even numeric steps do not read as even visual ones.

States the render also confirms

No percentage without a denominator

On the sparse card — 1 application over 0 tours — the Applications bar carries no conversion chip, while the two gaps that do have a denominator still render theirs. Unmeasured shows as absent, not as 0 % and not as infinity.

The occupancy line never invents a total

Populated: 19 vacant · 3 pre-leased · 4 on notice · 20 to fill. With nothing pre-leased the middle term drops and the total reconstructs as 23. With pendingLeases unmeasured, the total is gone entirely rather than silently over-reporting by the units already leased.

The shell is the same component

The last card is loading.tsx. It renders <ProspectFunnel loading /> — inert bars, no caret affordance, ghosted values, ghosted window chip — rather than a hand-copy of the markup. Note rows are reserved under every bar in both, so the card does not grow when the stats poll lands.

PropFlow Docs