FIX - align health app with observable data

Remove inferred condition, recovery, and player-health values from the Health UI and NUI contract. Keep browser mocks and tests aligned, correct the default development API port, remove the misleading Medical ID disclosure, and apply the shared page gutter to prevent clipped cards.
This commit is contained in:
Type
2026-08-16 00:29:13 +02:00
parent 2f70444071
commit 6fba5bcc10
11 changed files with 11 additions and 113 deletions
-1
View File
@@ -22,7 +22,6 @@ const overview: HealthOverview = {
playerName: 'Alex Morgan',
},
previousWeekSteps: 0,
snapshot: { healthPercent: 100 },
}
describe('health store', () => {
-6
View File
@@ -368,10 +368,6 @@ const healthFallbackLocales = {
kilocalories: '{count} kcal',
thisWeek: 'This week',
snapshot: 'Health snapshot',
condition: 'Condition',
recovery: 'Recovery',
currentHealth: 'Current health',
conditions: { good: 'Good', fair: 'Fair', low: 'Low' },
trends: {
title: 'Trends',
week: 'Week',
@@ -402,8 +398,6 @@ const healthFallbackLocales = {
phoneNumber: 'Phone number',
emergencyCall: 'Emergency call',
callContact: 'Call emergency contact',
privacy:
'Information is stored with your character and can be shown to emergency services.',
saveFailed: 'Medical ID could not be saved.',
},
errors: {
-5
View File
@@ -17,17 +17,12 @@ export type HealthMedicalId = {
playerName: string
}
export type HealthSnapshot = {
healthPercent: number
}
export type HealthOverview = {
dailyStepGoal: number
days: HealthActivityDay[]
emergencyNumber: string
medicalId: HealthMedicalId
previousWeekSteps: number
snapshot: HealthSnapshot
}
export type HealthMedicalIdInput = Omit<HealthMedicalId, 'playerName'>
+1 -1
View File
@@ -34,7 +34,7 @@ describe('nuiCall', () => {
})
expect(vi.getTimerCount()).toBe(0)
expect(fetchMock).toHaveBeenCalledWith(
'http://localhost:3001/api/test',
'http://localhost:3002/api/test',
expect.objectContaining({ signal: expect.any(AbortSignal) }),
)
})
+1 -1
View File
@@ -15,7 +15,7 @@ export async function nuiCall<T = unknown>(
'apiPort',
)
const baseUrl = import.meta.env.DEV
? `http://localhost:${developmentPort ?? '3001'}/api`
? `http://localhost:${developmentPort ?? '3002'}/api`
: `https://${resourceName}`
const requestData = import.meta.env.DEV
? {
@@ -8,6 +8,7 @@ describe('HealthApp Sky UI contract', () => {
expect(source).toContain('<SkyAppPage')
expect(source).toContain('<SkyNavbar')
expect(source).toContain('<SkyScrollArea')
expect(source).toContain('padded with-tabbar')
expect(source).toContain('with-tabbar')
expect(source).toContain('<SkyPillNavigation')
expect(source).not.toContain(
@@ -26,4 +27,10 @@ describe('HealthApp Sky UI contract', () => {
expect(source).toContain("nuiCall('calls:dial'")
expect(source).toContain('health.saveMedicalId')
})
it('does not present values the phone cannot observe', () => {
expect(source).not.toContain('healthPercent')
expect(source).not.toContain('recoveryPercent')
expect(source).not.toContain('conditionKey')
})
})
+1 -76
View File
@@ -9,7 +9,6 @@ import {
MapPin,
Phone,
RefreshCw,
ShieldCheck,
Timer,
UserRound,
} from 'lucide-vue-next'
@@ -102,20 +101,6 @@ const trendCopy = computed(() => {
}
return phone.t('Apps.health.trends.same')
})
const healthPercent = computed(
() => overview.value?.snapshot.healthPercent ?? 0,
)
const conditionKey = computed(() =>
healthPercent.value >= 70
? 'good'
: healthPercent.value >= 35
? 'fair'
: 'low',
)
const recoveryPercent = computed(() => {
const activeLoad = Math.min(20, Math.round((today.value?.activeSeconds ?? 0) / 180))
return Math.max(0, Math.min(100, healthPercent.value - activeLoad))
})
const dateRange = computed(() => {
if (!visibleDays.value.length) return ''
const start = new Date(`${visibleDays.value[0].date}T12:00:00`)
@@ -248,7 +233,7 @@ onBeforeUnmount(() => {
</template>
</SkyNavbar>
<SkyScrollArea class="health-content" with-tabbar>
<SkyScrollArea class="health-content" padded with-tabbar>
<div v-if="health.isLoading && !overview" class="health-loading">
<SkySpinner />
<span>{{ phone.t('Apps.health.loading') }}</span>
@@ -333,34 +318,6 @@ onBeforeUnmount(() => {
</div>
</section>
<section class="health-section">
<h2>{{ phone.t('Apps.health.snapshot') }}</h2>
<div class="health-list">
<div class="health-list__row">
<span class="health-list__icon health-list__icon--green">
<HeartPulse :size="21" />
</span>
<span>{{ phone.t('Apps.health.condition') }}</span>
<strong :class="`health-value--${conditionKey}`">{{
phone.t(`Apps.health.conditions.${conditionKey}`)
}}</strong>
</div>
<div class="health-list__row">
<span class="health-list__icon health-list__icon--orange">
<Activity :size="21" />
</span>
<span>{{ phone.t('Apps.health.recovery') }}</span>
<strong>{{ recoveryPercent }}%</strong>
</div>
<div class="health-list__row">
<span class="health-list__icon">
<ShieldCheck :size="21" />
</span>
<span>{{ phone.t('Apps.health.currentHealth') }}</span>
<strong>{{ healthPercent }}%</strong>
</div>
</div>
</section>
</template>
<template v-else-if="activeTab === 'trends'">
@@ -568,10 +525,6 @@ onBeforeUnmount(() => {
}}
</SkyButton>
<p class="health-privacy-note">
<ShieldCheck :size="22" aria-hidden="true" />
<span>{{ phone.t('Apps.health.medicalId.privacy') }}</span>
</p>
</template>
<p v-if="actionError" class="health-action-error" role="alert">
@@ -890,15 +843,6 @@ onBeforeUnmount(() => {
color: var(--health-orange);
}
.health-list__row .health-value--good {
color: var(--health-green);
}
.health-list__row .health-value--fair {
color: var(--health-orange);
}
.health-list__row .health-value--low,
.health-list__row .health-value--accent {
color: var(--health-accent);
}
@@ -1121,25 +1065,6 @@ onBeforeUnmount(() => {
gap: 9px;
}
.health-privacy-note {
display: flex;
align-items: flex-start;
margin: 0;
padding: 14px 16px;
border: calc(var(--sky-hairline-scale) * 1px) solid var(--health-panel-border);
border-radius: 18px;
background: var(--health-panel);
color: var(--sky-muted);
font-size: 13px;
line-height: 1.35;
gap: 12px;
}
.health-privacy-note svg {
flex: none;
color: var(--health-green);
}
.health-form {
overflow: hidden;
margin: 0;