User avatar (Google picture), avatar in mobile You tab, /account page

- Capture the Google profile picture (picture claim) into users.avatar_url; an
  Avatar component shows it, falling back to the initial. Used in the desktop
  header and the mobile "You" tab (which now shows the user when signed in).
- Move account/settings to its own route /account (robust + scrolls to top),
  reached by the desktop avatar and the mobile You tab; drop the inline "You"
  sheet. AccountPanel gains a Sign out action; the page links to Saved/History/
  Boundaries via home intent params (?view= / ?open=).
- db: users.avatar_url (schema + idempotent migration). 118 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-03 14:41:43 +00:00
parent bb008cfaa5
commit 15728c3bcb
11 changed files with 168 additions and 87 deletions
+10 -8
View File
@@ -46,7 +46,7 @@ def normalize_email(email: str) -> str:
def get_user(conn: sqlite3.Connection, user_id: int) -> sqlite3.Row | None:
return conn.execute(
"SELECT id, email, display_name, created_at FROM users WHERE id = ?", (user_id,)
"SELECT id, email, display_name, avatar_url, created_at FROM users WHERE id = ?", (user_id,)
).fetchone()
@@ -56,6 +56,7 @@ def find_or_create_user(
provider: str,
provider_subject: str,
display_name: str | None = None,
avatar_url: str | None = None,
) -> int:
"""Resolve (or create) the user for a verified sign-in, linking the identity.
@@ -73,15 +74,16 @@ def find_or_create_user(
user = conn.execute("SELECT id FROM users WHERE email = ?", (email,)).fetchone()
if user:
user_id = user["id"]
if display_name:
conn.execute(
"UPDATE users SET display_name = COALESCE(display_name, ?), "
"updated_at = CURRENT_TIMESTAMP WHERE id = ?",
(display_name, user_id),
)
# Fill display name if missing; refresh avatar whenever the provider gives one.
conn.execute(
"UPDATE users SET display_name = COALESCE(display_name, ?), "
"avatar_url = COALESCE(?, avatar_url), updated_at = CURRENT_TIMESTAMP WHERE id = ?",
(display_name, avatar_url, user_id),
)
else:
user_id = conn.execute(
"INSERT INTO users (email, display_name) VALUES (?, ?)", (email, display_name)
"INSERT INTO users (email, display_name, avatar_url) VALUES (?, ?, ?)",
(email, display_name, avatar_url),
).lastrowid
conn.execute(