fix(goals): demote Behind pill to neutral surface + drop em-dashes

Behavioural + RUI audit follow-ups.

The yellow overload finding flagged three concurrent yellow surfaces
on the show page: the "Behind" status pill, the catch-up alert, and
the open-pledge banner(s). Demoting the alert to outline ownership
of the primary CTA addressed one layer, but the pill kept fighting
the alert for hue attention. "Behind" is a state, not a call to
action; the alert owns the action signal.

Switch the pill's classes from `bg-yellow-500/10 text-yellow-700`
to `bg-surface-inset text-yellow-700` (with the same dark-mode
override). Background goes neutral (matches paused/archived chips);
the text keeps the warning hue and the triangle-alert icon stays.
Signal preserved, weight reduced. The yellow alert below now reads
as the primary nudge instead of one of three matching tones.

Also: copy/em-dash sweep across goal surfaces. User-facing strings
that contained em-dashes ("Reaches 70% — $X of $Y", "into your
linked account — Sure will catch it", "You're at 80% — $X of $Y")
read as a stylistic tic; replace with comma/period/period
respectively. Form-stepper review placeholders "—" become "…"
(ellipsis reads as "not yet set" without the typographic weight).
Code comments + log messages also scrubbed for consistency; awkward
sed artifacts (//. its...) restored to readable English.

No locale-key shape changes; pure string-content edits + one
component-style tweak.
This commit is contained in:
Guillem Arias
2026-05-14 22:12:52 +02:00
parent da4af43a7d
commit 880ca69657
13 changed files with 39 additions and 39 deletions

View File

@@ -4,7 +4,7 @@ import { Controller } from "@hotwired/stimulus";
//
// Single <form> with two panels. Step 1 collects identity (name, amount,
// date, color, notes, linked accounts). Step 2 reviews and submits. All
// state lives in the DOM — no half-records, single POST.
// state lives in the DOM. No half-records, single POST.
export default class extends Controller {
static targets = [
"step1Panel",
@@ -114,7 +114,7 @@ export default class extends Controller {
}
if (!this.hasAvatarPreviewTarget || !this.hasNameInputTarget) return;
// If the user has explicitly picked an icon, leave it alone — name
// If the user has explicitly picked an icon, leave it alone. Name
// changes shouldn't undo an explicit choice.
const iconPicked = this.element.querySelector('input[name="goal[icon]"]:checked');
if (iconPicked) return;
@@ -123,7 +123,7 @@ export default class extends Controller {
if (name) {
this.avatarPreviewTarget.textContent = name.charAt(0).toUpperCase();
} else if (this._defaultAvatarHTML) {
// Captured at connect — restore the default "target" icon from the
// Captured at connect. Restore the default "target" icon from the
// server-rendered template, not a "?" character.
this.avatarPreviewTarget.innerHTML = this._defaultAvatarHTML;
}
@@ -229,7 +229,7 @@ export default class extends Controller {
updateReview() {
if (!this.hasReviewNameTarget) return;
const name = this.element.querySelector('input[name="goal[name]"]')?.value || "";
const name = this.element.querySelector('input[name="goal[name]"]')?.value || "";
const amountInput = this.element.querySelector('input[name="goal[target_amount]"]');
const amount = amountInput?.value ? Number.parseFloat(amountInput.value) : 0;
const dateInput = this.element.querySelector('input[type="date"][name="goal[target_date]"]');
@@ -239,7 +239,7 @@ export default class extends Controller {
this.reviewNameTarget.textContent = name;
if (this.hasReviewSummaryTarget) {
const formattedAmount = amount > 0 ? this.#money(amount) : "";
const formattedAmount = amount > 0 ? this.#money(amount) : "";
const template = dateValue ? this.summaryWithDateValue : this.summaryNoDateValue;
this.reviewSummaryTarget.textContent = template
.replace("{amount}", formattedAmount)
@@ -260,7 +260,7 @@ export default class extends Controller {
} else if (amount > 0 && checked.length > 0) {
this.reviewSuggestedTarget.textContent = this.suggestedNoDateValue;
} else {
this.reviewSuggestedTarget.textContent = "";
this.reviewSuggestedTarget.textContent = "";
}
}
}