mirror of
https://github.com/we-promise/sure.git
synced 2026-05-30 15:59:02 +00:00
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:
@@ -28,7 +28,7 @@ export default class extends Controller {
|
||||
// Helper text reacts to the currently-selected account, not the goal as a
|
||||
// whole. A mixed-funding goal (one connected account + one manual) used to
|
||||
// paint the "connected" helper even if the user then picked the manual
|
||||
// account from the dropdown — the saved pledge would be `kind: manual_save`
|
||||
// account from the dropdown; the saved pledge would be `kind: manual_save`
|
||||
// (correct, per `kind_for_account` in the controller) but the helper read
|
||||
// "transfer-style" copy until submission.
|
||||
accountChanged() {
|
||||
|
||||
@@ -38,9 +38,9 @@ export default class extends Controller {
|
||||
}
|
||||
// After a Turbo render (eg. after saving the goal from the edit modal
|
||||
// and redirecting back to show), the chart container can be left empty
|
||||
// — its children are wiped by the morph but connect() was already
|
||||
// called and ResizeObserver doesn't fire because the size didn't
|
||||
// change. Listen for the render event so we redraw when needed.
|
||||
// its children may be wiped by the morph even though connect() was
|
||||
// already called, and ResizeObserver doesn't fire because the size
|
||||
// didn't change. Listen for the render event so we redraw when needed.
|
||||
this._onTurboRender = () => {
|
||||
if (!this.element.querySelector("svg")) this._draw();
|
||||
};
|
||||
@@ -139,7 +139,7 @@ export default class extends Controller {
|
||||
.attr("viewBox", `0 0 ${width} ${height}`)
|
||||
.attr("preserveAspectRatio", "none");
|
||||
|
||||
// Drop the <title> child — browsers render it as a native hover tooltip
|
||||
// Drop the <title> child; browsers render it as a native hover tooltip
|
||||
// that fights with our own crosshair tooltip. aria-label gives the same
|
||||
// SR accessible name without the tooltip side-effect.
|
||||
const descId = `chart-desc-${this._id()}`;
|
||||
@@ -209,7 +209,7 @@ export default class extends Controller {
|
||||
.attr("fill", textPrimary)
|
||||
.text(`Target · ${this._fmtMoneyShort(targetAmount, data.currency)}`);
|
||||
} else {
|
||||
// Plenty of room — keep the right-side full-format label.
|
||||
// Plenty of room: keep the right-side full-format label.
|
||||
svg
|
||||
.append("text")
|
||||
.attr("x", margin.left + innerWidth - 4)
|
||||
@@ -252,7 +252,7 @@ export default class extends Controller {
|
||||
|
||||
if (requiredSeries.length) {
|
||||
// Light dashed reference line: the path needed to hit the target.
|
||||
// Neutral stroke (text-secondary) instead of green — both the
|
||||
// Neutral stroke (text-secondary) instead of green: both the
|
||||
// projection and the required line are otherwise green when the
|
||||
// goal is on track, and the two would visually merge.
|
||||
svg
|
||||
@@ -291,7 +291,7 @@ export default class extends Controller {
|
||||
|
||||
// Suppress the projection-end label when it would visually collide
|
||||
// with the target label above. In a barely-on-track case the dot
|
||||
// already conveys "you'll hit the target" — duplicating "$2.4K"
|
||||
// already conveys "you'll hit the target". duplicating "$2.4K"
|
||||
// beside "Target · $2,400" adds noise.
|
||||
const projDotY = y(projectionEnd);
|
||||
const collidesWithTargetLabel = targetAmount > 0 && Math.abs(projDotY - y(targetAmount)) < 18;
|
||||
@@ -407,7 +407,7 @@ export default class extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
// Hover interactivity — crosshair + dots + tooltip on pointermove.
|
||||
// Hover interactivity: crosshair + dots + tooltip on pointermove.
|
||||
// Transparent rect catches pointer events across the plot area.
|
||||
const crosshair = svg
|
||||
.append("line")
|
||||
|
||||
@@ -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 = "…";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user