fix: sync meta_json.title in updateSessionTitle so ZCode App shows it#16
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates updateSessionTitle to synchronize the updated session title into the meta_json field, ensuring that the ZCode App (which prioritizes meta_json over the title column) displays the correct title. The corresponding tests and mocks are also updated. The reviewer identified a critical issue where unconditionally setting meta["title"] = trimmed when title_overridden === 1 would overwrite the user's custom title in meta_json with the auto-generated one. To fix this, the reviewer suggested selecting the title column as well and preserving the user-defined title in meta_json when an override is present, along with updating the test mocks to support this new query.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const row = con | ||
| .prepare("SELECT title_overridden, meta_json FROM tasks WHERE task_id=?") | ||
| .get(taskId) as { title_overridden: number; meta_json: string } | undefined; | ||
| if (!row) return false; | ||
|
|
||
| // The ZCode App reads title from meta_json first (falling back to the | ||
| // title column only when meta_json fails to parse). If we update only the | ||
| // column, the App keeps showing the stale meta_json title (empty at create | ||
| // time). So we patch meta_json.title in both branches below. | ||
| let metaJson: string; | ||
| try { | ||
| const meta = JSON.parse(row.meta_json ?? "{}") as Record<string, unknown>; | ||
| meta["title"] = trimmed; | ||
| metaJson = JSON.stringify(meta); | ||
| } catch { | ||
| // meta_json corrupt/unparseable — the App will fall back to the title | ||
| // column anyway, so skip the meta_json write rather than guessing. | ||
| metaJson = row.meta_json ?? "{}"; | ||
| } |
There was a problem hiding this comment.
If the user has already overridden the title (row.title_overridden === 1), unconditionally setting meta["title"] = trimmed (the auto-generated title) will overwrite the user's custom title in meta_json. Since the App reads the title from meta_json first, this will cause the App to display the auto-generated title instead of the user's overridden title, breaking the override behavior.
To fix this, we should select the title column as well, and if title_overridden === 1, preserve the existing user-defined title in meta_json.
| const row = con | |
| .prepare("SELECT title_overridden, meta_json FROM tasks WHERE task_id=?") | |
| .get(taskId) as { title_overridden: number; meta_json: string } | undefined; | |
| if (!row) return false; | |
| // The ZCode App reads title from meta_json first (falling back to the | |
| // title column only when meta_json fails to parse). If we update only the | |
| // column, the App keeps showing the stale meta_json title (empty at create | |
| // time). So we patch meta_json.title in both branches below. | |
| let metaJson: string; | |
| try { | |
| const meta = JSON.parse(row.meta_json ?? "{}") as Record<string, unknown>; | |
| meta["title"] = trimmed; | |
| metaJson = JSON.stringify(meta); | |
| } catch { | |
| // meta_json corrupt/unparseable — the App will fall back to the title | |
| // column anyway, so skip the meta_json write rather than guessing. | |
| metaJson = row.meta_json ?? "{}"; | |
| } | |
| const row = con | |
| .prepare("SELECT title, title_overridden, meta_json FROM tasks WHERE task_id=?") | |
| .get(taskId) as { title: string; title_overridden: number; meta_json: string | null } | undefined; | |
| if (!row) return false; | |
| // The ZCode App reads title from meta_json first (falling back to the | |
| // title column only when meta_json fails to parse). If we update only the | |
| // column, the App keeps showing the stale meta_json title (empty at create | |
| // time). So we patch meta_json.title in both branches below. | |
| let metaJson: string; | |
| try { | |
| const meta = JSON.parse(row.meta_json ?? "{}") as Record<string, unknown>; | |
| meta["title"] = row.title_overridden === 1 ? row.title : trimmed; | |
| metaJson = JSON.stringify(meta); | |
| } catch { | |
| // meta_json corrupt/unparseable — the App will fall back to the title | |
| // column anyway, so skip the meta_json write rather than guessing. | |
| metaJson = row.meta_json ?? "{}"; | |
| } |
| // SELECT title_overridden, meta_json FROM tasks WHERE task_id=? | ||
| if (/^SELECT title_overridden/i.test(sql)) { | ||
| return { | ||
| get(taskId: string) { | ||
| for (const r of rows.values()) { | ||
| if (r.task_id === taskId) { | ||
| return { title_overridden: r.title_overridden } as const; | ||
| return { | ||
| title_overridden: r.title_overridden, | ||
| meta_json: r.meta_json, | ||
| } as const; | ||
| } | ||
| } | ||
| return undefined; | ||
| }, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Update the mock statement to support the new SELECT title, title_overridden, meta_json query format and return the title property, ensuring that the unit tests continue to pass.
| // SELECT title_overridden, meta_json FROM tasks WHERE task_id=? | |
| if (/^SELECT title_overridden/i.test(sql)) { | |
| return { | |
| get(taskId: string) { | |
| for (const r of rows.values()) { | |
| if (r.task_id === taskId) { | |
| return { title_overridden: r.title_overridden } as const; | |
| return { | |
| title_overridden: r.title_overridden, | |
| meta_json: r.meta_json, | |
| } as const; | |
| } | |
| } | |
| return undefined; | |
| }, | |
| }; | |
| } | |
| // SELECT title, title_overridden, meta_json FROM tasks WHERE task_id=? | |
| if (/^SELECT (title, )?title_overridden/i.test(sql)) { | |
| return { | |
| get(taskId: string) { | |
| for (const r of rows.values()) { | |
| if (r.task_id === taskId) { | |
| return { | |
| title: r.title, | |
| title_overridden: r.title_overridden, | |
| meta_json: r.meta_json, | |
| } as const; | |
| } | |
| } | |
| return undefined; | |
| }, | |
| }; | |
| } |
No description provided.