Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@
"integrations": [
{
"name": "github",
"operations": [
"get_issue",
"create_pull_request"
]
"operations": ["get_issue", "create_pull_request"]
}
]
},
Expand Down
6 changes: 3 additions & 3 deletions docs/de/platform/member/preferences.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: Einstellungen
description: Die Mitglieder-Einstellungen, die dir über Organisationen und Chats hinweg folgen — Anzeigename und Passwort unter Konto, Theme und Locale im Profilmenü, eigene Anweisungen und Erinnerungen unter Personalisierung, und Abmelden.
description: Die Mitglieder-Einstellungen, die dir über Organisationen und Chats hinweg folgen — Name und Passwort unter Konto, Theme und Locale im Profilmenü, eigene Anweisungen und Erinnerungen unter Personalisierung, und Abmelden.
---

Einstellungen sind die Schrauben, die dir gehören, nicht der Organisation. Dein Anzeigename ist der Name, den Agents und Teamkolleginnen in Chats und Genehmigungen sehen. Deine Locale und dein Theme folgen dir zwischen Geräten. Deine eigenen Anweisungen und Erinnerungen prägen, wie Agents speziell dir antworten — getrennt von allem, was Admin oder Redakteur auf Organisationsebene gesetzt hat. Diese Seite zeigt, wo jeder Hebel sitzt und was er ändert.
Einstellungen sind die Schrauben, die dir gehören, nicht der Organisation. Dein Name ist das, was Agents und Teamkolleginnen in Chats und Genehmigungen sehen. Deine Locale und dein Theme folgen dir zwischen Geräten. Deine eigenen Anweisungen und Erinnerungen prägen, wie Agents speziell dir antworten — getrennt von allem, was Admin oder Redakteur auf Organisationsebene gesetzt hat. Diese Seite zeigt, wo jeder Hebel sitzt und was er ändert.

Die Form ist bewusst zweischichtig: das Profilmenü (überall, einen Klick vom Avatar entfernt) trägt die schnellen Schalter; **Einstellungen > Konto** und **Einstellungen > Personalisierung** tragen die tieferen Kontofelder. Alles hier gehört dir — nichts davon lecken zu anderen Mitgliedern oder anderen Organisationen durch.

Expand All @@ -17,7 +17,7 @@ Das Menü trägt außerdem einen Organisationswechsler, wenn du zu mehr als eine

Öffne **Einstellungen > Konto**. Drei Abschnitte sitzen auf der Seite: **Profil**, **Sicherheit** und **Zwei-Faktor-Authentifizierung**.

Der Profil-Abschnitt hält deinen **Anzeigenamen** und deine **E-Mail**. Der Anzeigename ist inline bearbeitbar; die Änderung speichert beim Verlassen des Felds und schlägt beim nächsten Render in jedem Chat und jeder Genehmigung durch. Die E-Mail ist schreibgeschützt — sie ist das, womit du dich angemeldet hast, und ein Wechsel läuft über den Support. Es gibt kein Avatar-Feld auf der Seite; Tale leitet einen Avatar aus den Initialen deines Anzeigenamens ab.
Der Profil-Abschnitt zeigt zuerst deine **E-Mail**, dann deinen **Namen** — die E-Mail legt den Namen nahe, den Tale vorschlägt und den du frei bearbeiten kannst. Der Name ist inline bearbeitbar; die Änderung speichert und schlägt beim nächsten Render in jedem Chat und jeder Genehmigung durch. Die E-Mail ist schreibgeschützt — sie ist das, womit du dich angemeldet hast, und ein Wechsel läuft über den Support. Es gibt kein Avatar-Feld auf der Seite; Tale leitet einen Avatar aus den Initialen deines Namens ab.

Der Sicherheits-Abschnitt hält einen einzelnen Knopf: **Passwort ändern**, wenn du dich mit E-Mail und Passwort registriert hast, **Passwort setzen**, wenn dein Konto über SSO föderiert ist und du ein Passwort als Rückfall hinzufügen willst. Beide Abläufe erzwingen die Passwort-Richtlinie der Organisation und zeigen die Regeln live, während du tippst. Der Zwei-Faktor-Abschnitt paart das Konto mit einer TOTP-App oder einem Hardware-Schlüssel und zeigt die Backup-Codes einmal bei der Einrichtung.

Expand Down
6 changes: 3 additions & 3 deletions docs/en/platform/member/preferences.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: Preferences
description: The member-level settings that follow you across orgs and chats — display name and password under Account, theme and locale in the profile menu, custom instructions and memories under Personalization, and sign-out.
description: The member-level settings that follow you across orgs and chats — name and password under Account, theme and locale in the profile menu, custom instructions and memories under Personalization, and sign-out.
---

Preferences are the dials that belong to you rather than to the org. Your display name is the name agents and teammates see in chats and approvals. Your locale and theme follow you between devices. Your custom instructions and memories shape how agents reply to you specifically — separately from anything the Admin or Editor has set at the org level. This page maps where each lever lives and what it changes.
Preferences are the dials that belong to you rather than to the org. Your name is what agents and teammates see in chats and approvals. Your locale and theme follow you between devices. Your custom instructions and memories shape how agents reply to you specifically — separately from anything the Admin or Editor has set at the org level. This page maps where each lever lives and what it changes.

The shape is intentionally two-layered: the profile menu (everywhere, one click from the avatar) carries the quick toggles; **Settings > Account** and **Settings > Personalization** carry the deeper account fields. Everything here is yours — none of it leaks to other members or other orgs.

Expand All @@ -17,7 +17,7 @@ The menu also carries an organisation switcher when you belong to more than one

Open **Settings > Account**. Three sections sit on the page: **Profile**, **Security**, and **Two-factor authentication**.

The Profile section holds your **display name** and your **email**. The display name is editable inline; the change saves on blur and propagates to every chat and approval the next time they render. Email is read-only — it is what you signed in with, and changing it goes through support. There is no avatar field on the page; Tale derives an avatar from your display name initials.
The Profile section shows your **email** first, then your **name** — the email implies the name Tale suggests, which you can edit freely. The name is editable inline; the change saves and propagates to every chat and approval the next time they render. Email is read-only — it is what you signed in with, and changing it goes through support. There is no avatar field on the page; Tale derives an avatar from your name's initials.

The Security section holds a single button: **Change password** if you signed up with email and password, **Set password** if your account is federated through SSO and you want to add a password as a fallback. Both flows enforce the org's password policy and surface the rules live as you type. The Two-factor section pairs the account with a TOTP app or a hardware key and shows the backup codes once at enrolment.

Expand Down
6 changes: 3 additions & 3 deletions docs/fr/platform/member/preferences.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: Préférences
description: Les réglages au niveau membre qui te suivent entre orgs et chats — nom d'affichage et mot de passe sous Compte, thème et langue dans le menu de profil, instructions personnalisées et mémoires sous Personnalisation, et déconnexion.
description: Les réglages au niveau membre qui te suivent entre orgs et chats — nom et mot de passe sous Compte, thème et langue dans le menu de profil, instructions personnalisées et mémoires sous Personnalisation, et déconnexion.
---

Les préférences sont les molettes qui t'appartiennent plutôt qu'à l'org. Ton nom d'affichage est le nom que voient agents et coéquipiers dans les chats et les approbations. Ta langue et ton thème te suivent entre les appareils. Tes instructions personnalisées et tes mémoires façonnent la manière dont les agents te répondent spécifiquement — séparément de tout ce que l'Administrateur ou l'Éditeur a posé au niveau de l'org. Cette page cartographie où vit chaque levier et ce qu'il change.
Les préférences sont les molettes qui t'appartiennent plutôt qu'à l'org. Ton nom est ce que voient agents et coéquipiers dans les chats et les approbations. Ta langue et ton thème te suivent entre les appareils. Tes instructions personnalisées et tes mémoires façonnent la manière dont les agents te répondent spécifiquement — séparément de tout ce que l'Administrateur ou l'Éditeur a posé au niveau de l'org. Cette page cartographie où vit chaque levier et ce qu'il change.

La forme est volontairement à deux couches : le menu de profil (partout, à un clic de l'avatar) porte les bascules rapides ; **Paramètres > Compte** et **Paramètres > Personnalisation** portent les champs de compte plus profonds. Tout ici t'appartient — rien ne fuite vers d'autres membres ou d'autres orgs.

Expand All @@ -17,7 +17,7 @@ Le menu porte aussi un sélecteur d'organisation quand tu appartiens à plus d'u

Ouvre **Paramètres > Compte**. Trois sections siègent sur la page : **Profil**, **Sécurité** et **Authentification à deux facteurs**.

La section Profil tient ton **nom d'affichage** et ton **e-mail**. Le nom d'affichage s'édite en ligne ; la modification s'enregistre à la sortie du champ et se propage dans chaque chat et chaque approbation au prochain rendu. L'e-mail est en lecture seule — c'est avec lui que tu t'es connecté, et un changement passe par le support. Il n'y a pas de champ avatar sur la page ; Tale dérive un avatar à partir des initiales de ton nom d'affichage.
La section Profil affiche d'abord ton **e-mail**, puis ton **nom** — l'e-mail suggère le nom que Tale propose, que tu peux modifier librement. Le nom s'édite en ligne ; la modification s'enregistre et se propage dans chaque chat et chaque approbation au prochain rendu. L'e-mail est en lecture seule — c'est avec lui que tu t'es connecté, et un changement passe par le support. Il n'y a pas de champ avatar sur la page ; Tale dérive un avatar à partir des initiales de ton nom.

La section Sécurité tient un seul bouton : **Changer le mot de passe** si tu t'es inscrit avec e-mail et mot de passe, **Définir le mot de passe** si ton compte est fédéré via SSO et que tu veux ajouter un mot de passe comme repli. Les deux flux imposent la politique de mot de passe de l'org et affichent les règles en direct pendant que tu tapes. La section Deux-facteurs apparie le compte à une app TOTP ou à une clé matérielle et affiche les codes de secours une fois à l'enrôlement.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@tale/ui/button';
import { Stack } from '@tale/ui/layout';
import { Separator } from '@tale/ui/separator';
import { useCallback, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';

Expand All @@ -21,9 +21,11 @@ import { getEnv } from '@/lib/env';
import { useT } from '@/lib/i18n/client';
import { DEFAULT_PASSWORD_POLICY } from '@/lib/shared/schemas/governance';
import { createPasswordSchema } from '@/lib/shared/schemas/password';
import { deriveNameFromEmail } from '@/lib/utils/derive-name-from-email';

type AccountFormData = {
email: string;
name: string;
password: string;
};

Expand All @@ -47,6 +49,7 @@ export function AccountStep() {
.string()
.min(1, t('validation.emailRequired'))
.email(tCommon('validation.email')),
name: z.string().trim().min(1, t('validation.nameRequired')),
password: createPasswordSchema({
minLength: t('validation.passwordMinLength', {
n: DEFAULT_PASSWORD_POLICY.minLength,
Expand All @@ -63,23 +66,37 @@ export function AccountStep() {
const form = useForm<AccountFormData>({
resolver: zodResolver(schema),
mode: 'onChange',
defaultValues: { email: '', password: '' },
defaultValues: { email: '', name: '', password: '' },
});

const { isValid, errors } = form.formState;
const email = form.watch('email');
const password = form.watch('password');
const passwordValidationItems = usePasswordValidation(password);

// Pre-fill Name with a suggestion derived from the email (#1941), kept as an
// editable value. Once the user edits Name themselves we stop overwriting it.
const nameEditedRef = useRef(false);
const nameField = form.register('name');
useEffect(() => {
if (nameEditedRef.current) return;
form.setValue('name', deriveNameFromEmail(email), { shouldValidate: true });
}, [email, form]);

const createAccount = useCallback(async (): Promise<boolean> => {
form.clearErrors(['email', 'password']);
const ok = await form.trigger();
if (!ok) return false;
const { email, password: pw } = form.getValues();
const {
email: emailValue,
name: nameValue,
password: pw,
} = form.getValues();

try {
const result = await authClient.signUp.email({
name: email,
email,
name: nameValue.trim() || emailValue,
email: emailValue,
password: pw,
});

Expand Down Expand Up @@ -129,6 +146,19 @@ export function AccountStep() {
{...form.register('email')}
/>

<Input
id="name"
label={t('name')}
placeholder={t('namePlaceholder')}
autoComplete="name"
errorMessage={errors.name?.message}
{...nameField}
onChange={(event) => {
nameEditedRef.current = true;
return nameField.onChange(event);
}}
/>

<Stack gap={2}>
<Input
id="password"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { useToast } from '@/app/hooks/use-toast';
import { getEnv } from '@/lib/env';
import { useT } from '@/lib/i18n/client';
import { createPasswordSchema } from '@/lib/shared/schemas/password';
import { deriveNameFromEmail } from '@/lib/utils/derive-name-from-email';

import { useUpdatePassword, useUpdateUserName } from '../hooks/mutations';
import { ChatsSection } from './chats-section';
Expand Down Expand Up @@ -105,10 +106,17 @@ function ProfileSection() {
[tSettings],
);

const data = useMemo<ProfileFormData | undefined>(
() => (user ? { name: user.name ?? '' } : undefined),
[user],
);
const data = useMemo<ProfileFormData | undefined>(() => {
if (!user) return undefined;
const email = user.email ?? '';
const savedName = user.name?.trim() ?? '';
// Legacy owner accounts were created with `name === email` (see the
// onboarding account step); treat that — and an empty name — as "no real
// name yet" and offer an editable suggestion derived from the email.
const name =
savedName && savedName !== email ? savedName : deriveNameFromEmail(email);
return { name };
}, [user]);

const save = useCallback(
async (values: ProfileFormData) => {
Expand Down Expand Up @@ -162,14 +170,26 @@ function ProfileSection() {
disabled={editor.isLoading}
className="divide-border divide-y"
>
{/* Email first, then Name — the email implies the suggested name
(#1941), so it reads top-to-bottom as cause then effect. */}
<SettingsRow
className="py-5"
label={tSettings('account.profile.email')}
description={tSettings('account.profile.emailDescription')}
>
<div className="w-full sm:w-80">
<EmailField email={user?.email ?? ''} />
</div>
</SettingsRow>

<SettingsRow
className="py-5"
label={tSettings('account.profile.name')}
description={tSettings('account.profile.nameDescription')}
>
<div className="w-full sm:w-80">
<Input
id="display-name"
id="name"
// The visible label lives on the enclosing SettingsRow, which
// names a wrapper div — give the input its own accessible name
// so assistive tech (and getByRole) can reach the control.
Expand All @@ -182,16 +202,6 @@ function ProfileSection() {
/>
</div>
</SettingsRow>

<SettingsRow
className="py-5"
label={tSettings('account.profile.email')}
description={tSettings('account.profile.emailDescription')}
>
<div className="w-full sm:w-80">
<EmailField email={user?.email ?? ''} />
</div>
</SettingsRow>
</fieldset>
</Form>
</SettingsSection>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@tale/ui/button';
import { Stack } from '@tale/ui/layout';
import { ConvexError } from 'convex/values';
import { useState, useMemo } from 'react';
import { useEffect, useRef, useState, useMemo } from 'react';
import { useForm } from 'react-hook-form';
import * as z from 'zod';

Expand All @@ -20,6 +20,7 @@ import { usePasswordValidation } from '@/app/hooks/use-password-validation';
import { useToast } from '@/app/hooks/use-toast';
import { useT } from '@/lib/i18n/client';
import { createOptionalPasswordSchema } from '@/lib/shared/schemas/password';
import { deriveNameFromEmail } from '@/lib/utils/derive-name-from-email';
import { narrowStringUnion } from '@/lib/utils/type-utils';

import { useCreateMember } from '../hooks/mutations';
Expand Down Expand Up @@ -108,10 +109,20 @@ export function AddMemberDialog({
formState,
} = form;
const selectedRole = watch('role');
const email = watch('email');
const password = watch('password') ?? '';

const passwordValidationItems = usePasswordValidation(password, policy);

// Pre-fill Name with a suggestion derived from the email (#1941), kept as an
// editable value. Once the admin edits Name themselves we stop overwriting it.
const nameEditedRef = useRef(false);
const displayNameField = register('displayName');
useEffect(() => {
if (nameEditedRef.current) return;
setValue('displayName', deriveNameFromEmail(email));
}, [email, setValue]);

const onSubmit = async (data: AddMemberFormData) => {
try {
const result = await createMember({
Expand All @@ -137,6 +148,7 @@ export function AddMemberDialog({
setCredentials({ email: data.email, password: data.password });
setShowCredentials(true);
} else {
nameEditedRef.current = false;
reset();
onOpenChange(false);
}
Expand Down Expand Up @@ -164,12 +176,14 @@ export function AddMemberDialog({
setShowCredentials(false);
setIsExistingUser(false);
setCredentials(null);
nameEditedRef.current = false;
reset();
onOpenChange(false);
};

const handleOpenChange = (isOpen: boolean) => {
if (!isOpen && !showCredentials) {
nameEditedRef.current = false;
reset();
}
onOpenChange(isOpen);
Expand All @@ -187,14 +201,8 @@ export function AddMemberDialog({
isValid={formState.isValid}
onSubmit={handleSubmit(onSubmit)}
>
<Input
id="displayName"
label={tSettings('form.name')}
placeholder={tSettings('form.namePlaceholder')}
{...register('displayName')}
className="w-full"
/>

{/* Email first, then Name — the email implies the suggested name
(#1941), which we pre-fill below as an editable value. */}
<Input
id="email"
type="email"
Expand All @@ -206,6 +214,18 @@ export function AddMemberDialog({
errorMessage={formState.errors.email?.message}
/>

<Input
id="displayName"
label={tSettings('form.name')}
placeholder={tSettings('form.namePlaceholder')}
{...displayNameField}
onChange={(event) => {
nameEditedRef.current = true;
return displayNameField.onChange(event);
}}
className="w-full"
/>

<Select
value={selectedRole}
onValueChange={(value) => {
Expand Down
Loading