mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
fix(insights): MED-bucket polish across coach + report surfaces
- weekly-report eyebrow chip drops uppercase + tracking-wide for the
sentence-case rule the rest of the app follows; print stylesheet
now neutralises bg + colour so monochrome output reads clean
(Design-M4, Design-L3).
- correlation-card "Try a 7-day experiment" CTA flips from default to
outline while disabled. Disabled-primary read as "the system is
broken right now"; outline reads as "this is on the roadmap"
(Design-M7).
- coach-drawer settings cog aria-label switches to the coming-soon
tooltip copy so SR users hear the same "arrives in v1.4.21" message
sighted users see (Design-M9).
- sources-rail static "fresh" dot becomes aria-hidden — v1.4.20 has
no real freshness state behind it; SR users no longer hear "Fresh"
five times in a row (Design-L4).
- summariseTitle slices on Unicode code points rather than UTF-16
code units so a user message ending mid-emoji ("🩺") no longer
produces a half-character on disk (Code-LOW-04).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
340239423b
commit
9f3baf940d
5 files changed
+26
-11
No files matched your search
@@ -186,7 +186,10 @@ export function CoachDrawer({
|
|||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
disabled
|
disabled
|
||||||
aria-label={t("insights.coach.settings")}
|
// Match the visible disabled state — sighted users
|
||||||
|
// see a coming-soon tooltip; SR users hear the same
|
||||||
|
// copy instead of a working "Coach settings" label.
|
||||||
|
aria-label={t("insights.coach.settingsTooltip")}
|
||||||
data-slot="coach-drawer-settings"
|
data-slot="coach-drawer-settings"
|
||||||
className="size-8"
|
className="size-8"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -98,10 +98,13 @@ export function SourcesRail({ className }: SourcesRailProps) {
|
|||||||
<span className="text-foreground flex-1 text-xs font-medium">
|
<span className="text-foreground flex-1 text-xs font-medium">
|
||||||
{t(row.metricKey)}
|
{t(row.metricKey)}
|
||||||
</span>
|
</span>
|
||||||
{/* Fresh / stale indicator — v1.4.20 is a static dot.
|
{/* Fresh / stale indicator — v1.4.20 is a static dot
|
||||||
v1.4.21 will reuse <IntegrationStatusPill>. */}
|
with no real freshness state behind it (v1.4.21 plugs
|
||||||
|
in <IntegrationStatusPill>). aria-hidden so SR users
|
||||||
|
don't hear "Fresh" five times in a row for what is
|
||||||
|
actually a placeholder. */}
|
||||||
<span
|
<span
|
||||||
aria-label={t("insights.coach.sourcesFresh")}
|
aria-hidden="true"
|
||||||
className="bg-dracula-green size-1.5 rounded-full"
|
className="bg-dracula-green size-1.5 rounded-full"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -131,7 +131,11 @@ export function CorrelationCard({ result }: CorrelationCardProps) {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="default"
|
// Outline variant so the disabled placeholder reads as
|
||||||
|
// "this is on the roadmap" rather than "the system is
|
||||||
|
// broken right now". Flip back to default when the
|
||||||
|
// 7-day experiment feature ships in v1.5.
|
||||||
|
variant="outline"
|
||||||
disabled
|
disabled
|
||||||
data-slot="correlation-card-cta"
|
data-slot="correlation-card-cta"
|
||||||
className="gap-1.5"
|
className="gap-1.5"
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export function WeeklyReportPresentation({
|
|||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
data-slot="weekly-report-eyebrow"
|
data-slot="weekly-report-eyebrow"
|
||||||
className="bg-dracula-purple/15 text-dracula-purple inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-[11px] font-medium uppercase tracking-wide"
|
className="bg-dracula-purple/15 text-dracula-purple inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-[11px] font-medium print:bg-transparent print:px-0 print:text-foreground"
|
||||||
>
|
>
|
||||||
<FileText className="h-3 w-3" aria-hidden="true" />
|
<FileText className="h-3 w-3" aria-hidden="true" />
|
||||||
{t("insights.report.eyebrow")}
|
{t("insights.report.eyebrow")}
|
||||||
|
|||||||
@@ -34,11 +34,16 @@ const TITLE_MAX = 80;
|
|||||||
export function summariseTitle(input: string): string {
|
export function summariseTitle(input: string): string {
|
||||||
const collapsed = input.replace(/\s+/g, " ").trim();
|
const collapsed = input.replace(/\s+/g, " ").trim();
|
||||||
if (collapsed.length === 0) return "New conversation";
|
if (collapsed.length === 0) return "New conversation";
|
||||||
if (collapsed.length <= TITLE_MAX) return collapsed;
|
// Spread to a code-point array so the slice respects multi-code-unit
|
||||||
// Cut at TITLE_MAX-1 chars and append a single-character ellipsis so
|
// characters (emoji like "🩺" land as a single grapheme rather than
|
||||||
// the visible width matches TITLE_MAX. Cuts at the word boundary
|
// a half "?"). The visible-length metric is grapheme count, not
|
||||||
// when one is within reach of the limit.
|
// UTF-16 code units.
|
||||||
const sliced = collapsed.slice(0, TITLE_MAX - 1);
|
const points = [...collapsed];
|
||||||
|
if (points.length <= TITLE_MAX) return collapsed;
|
||||||
|
// Cut at TITLE_MAX-1 code points and append a single-character
|
||||||
|
// ellipsis so the visible width matches TITLE_MAX. Cuts at the word
|
||||||
|
// boundary when one is within reach of the limit.
|
||||||
|
const sliced = points.slice(0, TITLE_MAX - 1).join("");
|
||||||
const lastSpace = sliced.lastIndexOf(" ");
|
const lastSpace = sliced.lastIndexOf(" ");
|
||||||
const cut = lastSpace > TITLE_MAX - 20 ? sliced.slice(0, lastSpace) : sliced;
|
const cut = lastSpace > TITLE_MAX - 20 ? sliced.slice(0, lastSpace) : sliced;
|
||||||
return `${cut.trimEnd()}…`;
|
return `${cut.trimEnd()}…`;
|
||||||
|
|||||||
Reference in new issue
Block a user