fix: update terminology from "Report a Bug" to "Report an Issue" across multiple components

This commit is contained in:
Mo Tarbin
2026-08-16 18:03:34 -04:00
parent dae647a4be
commit c0d2d5a9ec
8 changed files with 64 additions and 19 deletions

View File

@@ -30,7 +30,7 @@
"activities": "Activities", "activities": "Activities",
"points": "Points", "points": "Points",
"settings": "Settings", "settings": "Settings",
"reportBug": "Report a Bug" "reportBug": "Report an Issue"
}, },
"search": { "search": {
"title": "Search", "title": "Search",

View File

@@ -91,8 +91,8 @@
"description": "Tell us how Donetick is working for you or request a feature." "description": "Tell us how Donetick is working for you or request a feature."
}, },
"bugReport": { "bugReport": {
"title": "Report a Bug", "title": "Report an Issue",
"description": "Something not working right? Send us the details along with a technical snapshot." "description": "Tell us what's not working and we'll attach the technical details for you."
} }
} }
}, },

View File

@@ -1,4 +1,6 @@
import { useQuery } from '@tanstack/react-query' import { useQuery } from '@tanstack/react-query'
import { setServerVersion } from '../service/DiagnosticsSession'
import { GetResource } from '../utils/Fetcher' import { GetResource } from '../utils/Fetcher'
// Helper to check if we have a valid token // Helper to check if we have a valid token
@@ -13,10 +15,13 @@ const isTokenValid = () => {
} }
export const useResource = () => { export const useResource = () => {
const { data, isLoading, error, refetch } = useQuery({ const { data, error, isLoading, refetch } = useQuery({
queryKey: ['resource'], queryKey: ['resource'],
queryFn: async () => { queryFn: async () => {
const response = await GetResource() const response = await GetResource()
// The backend only names its build here, so this is also where crash
// reports learn which server version the user was talking to.
setServerVersion(response?.api_version, response?.api_commit)
return response return response
}, },
staleTime: 6 * 60 * 60 * 1000, // 6 hours in milliseconds staleTime: 6 * 60 * 60 * 1000, // 6 hours in milliseconds

View File

@@ -5,7 +5,8 @@
* *
* Deliberately dependency-free — ApiClient imports it on the request path, so * Deliberately dependency-free — ApiClient imports it on the request path, so
* anything imported here would risk a module cycle. Everything is in memory * anything imported here would risk a module cycle. Everything is in memory
* and dies with the tab; nothing is persisted. * and dies with the tab, except the server build, which is remembered across
* launches so a crash before the first API answer still names the backend.
*/ */
const SESSION_STARTED_AT = Date.now() const SESSION_STARTED_AT = Date.now()
@@ -25,6 +26,7 @@ const routeTrail = []
const apiFailures = [] const apiFailures = []
let backgroundedCount = 0 let backgroundedCount = 0
let serverVersion = null let serverVersion = null
let serverCommit = null
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Route trail // Route trail
@@ -67,6 +69,33 @@ export const getPreviousRoute = () =>
// Server identity // Server identity
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
const SERVER_BUILD_KEY = 'diagnostics_server_build'
// A crash on cold start happens before /resource has answered, and that is
// exactly when knowing which backend the user is on matters most. Carrying the
// last known build across launches keeps the report from saying "not reported".
try {
const cached = JSON.parse(localStorage.getItem(SERVER_BUILD_KEY) || 'null')
serverVersion = cached?.version ?? null
serverCommit = cached?.commit ?? null
} catch {
// corrupt or unavailable storage just means we start without a known build
}
const rememberServerBuild = (version, commit) => {
if (!version && !commit) return
serverVersion = version || serverVersion
serverCommit = commit || serverCommit
try {
localStorage.setItem(
SERVER_BUILD_KEY,
JSON.stringify({ version: serverVersion, commit: serverCommit }),
)
} catch {
// storage full or blocked; the in-memory copy still serves this session
}
}
/** /**
* Picks the server build out of response headers. Costs nothing when the * Picks the server build out of response headers. Costs nothing when the
* server doesn't send them — the field simply stays null. * server doesn't send them — the field simply stays null.
@@ -74,21 +103,24 @@ export const getPreviousRoute = () =>
export const recordServerVersionFromResponse = response => { export const recordServerVersionFromResponse = response => {
if (serverVersion) return if (serverVersion) return
try { try {
serverVersion = rememberServerBuild(
response?.headers?.get?.('x-donetick-version') || response?.headers?.get?.('x-donetick-version') ||
response?.headers?.get?.('x-api-version') || response?.headers?.get?.('x-api-version'),
null null,
)
} catch { } catch {
// headers may be inaccessible on opaque responses; not worth reporting // headers may be inaccessible on opaque responses; not worth reporting
} }
} }
export const setServerVersion = version => { /** Authoritative source: what /resource reports about the backend build. */
if (version) serverVersion = version export const setServerVersion = (version, commit) =>
} rememberServerBuild(version, commit)
export const getServerVersion = () => serverVersion export const getServerVersion = () => serverVersion
export const getServerCommit = () => serverCommit
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// API failures // API failures
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -176,6 +208,7 @@ export const getSessionDiagnostics = async () => {
navigationType: NAVIGATION_TYPE, navigationType: NAVIGATION_TYPE,
backgroundedCount, backgroundedCount,
serverVersion, serverVersion,
serverCommit,
previousRoute: getPreviousRoute(), previousRoute: getPreviousRoute(),
routeTrail: getRouteTrail(), routeTrail: getRouteTrail(),
apiFailures: getApiFailures(), apiFailures: getApiFailures(),

View File

@@ -153,7 +153,9 @@ export const formatErrorReport = report => {
session.previousRoute ? `Came from: ${session.previousRoute}` : null, session.previousRoute ? `Came from: ${session.previousRoute}` : null,
'', '',
`App: ${app.appVersion} · ${app.platform}${app.isNative ? ' (native)' : ''}`, `App: ${app.appVersion} · ${app.platform}${app.isNative ? ' (native)' : ''}`,
`Server: ${session.serverVersion ?? 'not reported'}`, `Server: ${session.serverVersion ?? 'not reported'}${
session.serverCommit ? ` (${session.serverCommit.slice(0, 8)})` : ''
}`,
`Session: ${formatDuration(session.sessionDurationMs)} active · ${ `Session: ${formatDuration(session.sessionDurationMs)} active · ${
session.navigationType ?? 'unknown' session.navigationType ?? 'unknown'
} start · backgrounded ${session.backgroundedCount ?? 0}×`, } start · backgrounded ${session.backgroundedCount ?? 0}×`,

View File

@@ -1,11 +1,11 @@
import { Browser } from '@capacitor/browser' import { Browser } from '@capacitor/browser'
import { Capacitor } from '@capacitor/core' import { Capacitor } from '@capacitor/core'
import { import {
BugReportRounded,
CheckRounded, CheckRounded,
ContentCopyRounded, ContentCopyRounded,
ExpandMoreRounded, ExpandMoreRounded,
GitHub, GitHub,
ReportProblemRounded,
} from '@mui/icons-material' } from '@mui/icons-material'
import { import {
Box, Box,
@@ -165,7 +165,7 @@ const ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
<Stack spacing={2}> <Stack spacing={2}>
<Box sx={{ ...enter(0) }}> <Box sx={{ ...enter(0) }}>
<IconHalo <IconHalo
icon={<BugReportRounded />} icon={<ReportProblemRounded />}
color={isBugReport ? 'warning' : 'danger'} color={isBugReport ? 'warning' : 'danger'}
/> />
</Box> </Box>
@@ -175,7 +175,7 @@ const ErrorReportModal = ({ error, errorInfo, onClose, open }) => {
level='h4' level='h4'
sx={{ fontWeight: 700, letterSpacing: '-0.01em' }} sx={{ fontWeight: 700, letterSpacing: '-0.01em' }}
> >
{isBugReport ? 'Report a bug' : 'Report this problem'} {isBugReport ? 'Report an issue' : 'Report this problem'}
</Typography> </Typography>
<Typography <Typography
level='body-sm' level='body-sm'

View File

@@ -1,4 +1,9 @@
import { BugReport, ChevronRight, Feedback, Star } from '@mui/icons-material' import {
ChevronRight,
Feedback,
ReportProblem,
Star,
} from '@mui/icons-material'
import { import {
Avatar, Avatar,
Box, Box,
@@ -52,7 +57,7 @@ const SettingsOverview = () => {
id: 'bugreport', id: 'bugreport',
title: t('overview.sections.bugReport.title'), title: t('overview.sections.bugReport.title'),
description: t('overview.sections.bugReport.description'), description: t('overview.sections.bugReport.description'),
icon: <BugReport />, icon: <ReportProblem />,
onSelect: () => setBugReportOpen(true), onSelect: () => setBugReportOpen(true),
}, },
] ]

View File

@@ -2,7 +2,6 @@ import { Capacitor } from '@capacitor/core'
import { import {
Archive, Archive,
ArrowBack, ArrowBack,
BugReport,
FilterAlt, FilterAlt,
FolderOpen, FolderOpen,
History, History,
@@ -10,6 +9,7 @@ import {
ListAlt, ListAlt,
Logout, Logout,
MenuRounded, MenuRounded,
ReportProblem,
SearchRounded, SearchRounded,
SettingsOutlined, SettingsOutlined,
Toll, Toll,
@@ -305,7 +305,7 @@ const NavBar = () => {
}} }}
> >
<ListItemDecorator> <ListItemDecorator>
<BugReport /> <ReportProblem />
</ListItemDecorator> </ListItemDecorator>
<ListItemContent>{t('navigation.reportBug')}</ListItemContent> <ListItemContent>{t('navigation.reportBug')}</ListItemContent>
</ListItemButton> </ListItemButton>