feat(mobile): editable activity title for local activities

Adds edits_json column (migration v3) to store user overrides separately
from detail_json so Option A server re-extraction never clobbers them.

- Tap the title in the detail screen to edit (local activities only, shown
  with a ✎ hint). Saves on keyboard dismiss via onEndEditing.
- Cards and search display user_title ?? title.
- Raw upload: user_title sent to server -> sidecar written so web UI shows
  the correct title (server re-extracts from FIT, which has Karoo's title).
- BAS upload: detail.title overridden before sending, no sidecar needed.
This commit is contained in:
Davide Scaini
2026-04-27 15:20:19 +02:00
parent 090d4bd8dc
commit 946da685e5
6 changed files with 81 additions and 18 deletions
+18 -11
View File
@@ -13,11 +13,13 @@ export type ActivityRow = {
synced_at: number | null;
origin: 'local' | 'remote';
created_at: number;
edits_json: string | null;
};
export type ActivitySummary = {
id: string;
title: string;
user_title: string | null; // from edits_json; takes display priority over title
sport: string;
started_at: string;
distance_m: number | null;
@@ -34,20 +36,11 @@ const PAGE_SIZE = 50;
export function useActivities(searchQuery = '', limit = PAGE_SIZE): ActivitySummary[] {
const db = useSQLiteContext();
const like = `%${searchQuery}%`;
const rows = db.getAllSync<{
id: string;
origin: 'local' | 'remote';
synced_at: number | null;
title: string;
sport: string;
started_at: string;
distance_m: number | null;
duration_s: number | null;
elevation_gain_m: number | null;
}>(`
const rows = db.getAllSync<ActivitySummary>(`
SELECT
id, origin, synced_at,
json_extract(detail_json, '$.title') AS title,
json_extract(edits_json, '$.title') AS user_title,
json_extract(detail_json, '$.sport') AS sport,
json_extract(detail_json, '$.started_at') AS started_at,
json_extract(detail_json, '$.distance_m') AS distance_m,
@@ -93,6 +86,7 @@ export function useFilteredActivities(filter: ActivityFilter, limit = PAGE_SIZE)
SELECT
id, origin, synced_at,
json_extract(detail_json, '$.title') AS title,
json_extract(edits_json, '$.title') AS user_title,
json_extract(detail_json, '$.sport') AS sport,
json_extract(detail_json, '$.started_at') AS started_at,
json_extract(detail_json, '$.distance_m') AS distance_m,
@@ -194,6 +188,19 @@ export async function deleteActivity(
return row?.original_path ?? null;
}
export async function setActivityTitle(
db: ReturnType<typeof useSQLiteContext>,
id: string,
title: string,
): Promise<void> {
await db.runAsync(
`UPDATE activities
SET edits_json = json_set(COALESCE(edits_json, '{}'), '$.title', ?)
WHERE id = ?`,
[title, id],
);
}
export async function deleteActivities(
db: ReturnType<typeof useSQLiteContext>,
ids: string[],