Registration collects a single optional Name. Emails that greet a customer therefore have only a whole name to work with, which is why the cart reminder reads Hi${name ? ' ' + name : ''} and produces "Hi Thom Lamb," rather than "Hi Thom,".
Splitting it into first and last name makes an informal greeting possible, which is the point.
What has to change
customers.name TEXT is nullable and touched in nine places:
Where
What it does
routes/customers.ts:62,75
reads name from the register body, inserts it
routes/customers.ts:53
publicCustomer returns it to the client
routes/customers.ts:289
the account page updates it
server.ts:47
the cart-reminder greeting
AuthForm.tsx:83
the optional Name field on the register form
admin/Customers.tsx:346,426
the customer list cell and the drawer title
CustomerRow / frontend Customer
the shapes that carry it
The decisions that need making before anyone starts
Required, or still optional? Name is optional today — you can register with an email and a password and nothing else. "Register with firstName and lastName" reads as making them required, which is a change to the signup flow and to what an existing customer with no name sees on their account page. If they stay optional, the greeting needs a fallback anyway, and the informal greeting only happens for people who volunteered a first name.
What happens to the names already stored? This is the part with no clean answer. Existing rows hold one string, and splitting on the first space is a guess that mangles real names: "Mary Jane Smith" becomes either a first name of "Mary" or of "Mary Jane" and both are wrong for somebody. Names also do not reliably divide into two parts at all. The honest options are to backfill first name only and leave last name empty, to leave existing rows untouched and treat the old column as legacy, or to ask customers to re-enter it — and the first is probably right, but it should be chosen with the mangling in mind rather than discovered afterwards.
Does the old column go? Dropping name means a migration and updating every consumer in one change. Keeping it alongside means two sources of truth for the same fact, which will drift. Dropping is cleaner; it just makes the change bigger.
What this is not
shipping_addresses.full_name already exists and is NOT NULL — a delivery name is captured where delivery actually needs it. That is deliberately separate: the person an order ships to is not always the account holder, and a gift is the obvious case. This issue should not fold the two together, and the shipping name should stay exactly as it is.
#92 makes the emails editable and introduces a {{greeting}} placeholder that resolves to "Hi Thom," or "Hi,". That placeholder works against whatever name field exists, so the two are technically independent — but the point of this issue is that the greeting should use a first name, and #92 shipping first would mean {{greeting}} produces the formal full name until this lands.
Doing this one first means the greeting is right from the moment it becomes editable.
Verification
Integration tests that registration stores both parts, that the account page updates them, and that publicCustomer returns them without leaking anything new. A migration test that existing rows survive whatever backfill is chosen. End-to-end that registering with a first name and reaching the account page shows it.
Severity
Low. Nothing is broken — this is about the emails being able to sound like a person wrote them.
Registration collects a single optional **Name**. Emails that greet a customer therefore have only a whole name to work with, which is why the cart reminder reads `Hi${name ? ' ' + name : ''}` and produces "Hi Thom Lamb," rather than "Hi Thom,".
Splitting it into first and last name makes an informal greeting possible, which is the point.
## What has to change
`customers.name TEXT` is nullable and touched in nine places:
| Where | What it does |
| --- | --- |
| `routes/customers.ts:62,75` | reads `name` from the register body, inserts it |
| `routes/customers.ts:53` | `publicCustomer` returns it to the client |
| `routes/customers.ts:289` | the account page updates it |
| `server.ts:47` | the cart-reminder greeting |
| `AuthForm.tsx:83` | the optional **Name** field on the register form |
| `admin/Customers.tsx:346,426` | the customer list cell and the drawer title |
| `CustomerRow` / frontend `Customer` | the shapes that carry it |
## The decisions that need making before anyone starts
**Required, or still optional?** Name is optional today — you can register with an email and a password and nothing else. "Register with firstName and lastName" reads as making them required, which is a change to the signup flow and to what an existing customer with no name sees on their account page. If they stay optional, the greeting needs a fallback anyway, and the informal greeting only happens for people who volunteered a first name.
**What happens to the names already stored?** This is the part with no clean answer. Existing rows hold one string, and splitting on the first space is a guess that mangles real names: "Mary Jane Smith" becomes either a first name of "Mary" or of "Mary Jane" and both are wrong for somebody. Names also do not reliably divide into two parts at all. The honest options are to backfill first name only and leave last name empty, to leave existing rows untouched and treat the old column as legacy, or to ask customers to re-enter it — and the first is probably right, but it should be chosen with the mangling in mind rather than discovered afterwards.
**Does the old column go?** Dropping `name` means a migration and updating every consumer in one change. Keeping it alongside means two sources of truth for the same fact, which will drift. Dropping is cleaner; it just makes the change bigger.
## What this is not
`shipping_addresses.full_name` already exists and is `NOT NULL` — a delivery name is captured where delivery actually needs it. That is deliberately separate: the person an order ships to is not always the account holder, and a gift is the obvious case. This issue should not fold the two together, and the shipping name should stay exactly as it is.
## Relationship to #92
#92 makes the emails editable and introduces a `{{greeting}}` placeholder that resolves to "Hi Thom," or "Hi,". That placeholder works against whatever name field exists, so the two are technically independent — but the *point* of this issue is that the greeting should use a first name, and #92 shipping first would mean `{{greeting}}` produces the formal full name until this lands.
Doing this one first means the greeting is right from the moment it becomes editable.
## Verification
Integration tests that registration stores both parts, that the account page updates them, and that `publicCustomer` returns them without leaking anything new. A migration test that existing rows survive whatever backfill is chosen. End-to-end that registering with a first name and reaching the account page shows it.
## Severity
Low. Nothing is broken — this is about the emails being able to sound like a person wrote them.
bermudalamb
self-assigned this 2026-08-21 17:50:36 -05:00
bermudalamb
added this to the Make the password-reset email editable from Admin project 2026-08-21 17:50:45 -05:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Registration collects a single optional Name. Emails that greet a customer therefore have only a whole name to work with, which is why the cart reminder reads
Hi${name ? ' ' + name : ''}and produces "Hi Thom Lamb," rather than "Hi Thom,".Splitting it into first and last name makes an informal greeting possible, which is the point.
What has to change
customers.name TEXTis nullable and touched in nine places:routes/customers.ts:62,75namefrom the register body, inserts itroutes/customers.ts:53publicCustomerreturns it to the clientroutes/customers.ts:289server.ts:47AuthForm.tsx:83admin/Customers.tsx:346,426CustomerRow/ frontendCustomerThe decisions that need making before anyone starts
Required, or still optional? Name is optional today — you can register with an email and a password and nothing else. "Register with firstName and lastName" reads as making them required, which is a change to the signup flow and to what an existing customer with no name sees on their account page. If they stay optional, the greeting needs a fallback anyway, and the informal greeting only happens for people who volunteered a first name.
What happens to the names already stored? This is the part with no clean answer. Existing rows hold one string, and splitting on the first space is a guess that mangles real names: "Mary Jane Smith" becomes either a first name of "Mary" or of "Mary Jane" and both are wrong for somebody. Names also do not reliably divide into two parts at all. The honest options are to backfill first name only and leave last name empty, to leave existing rows untouched and treat the old column as legacy, or to ask customers to re-enter it — and the first is probably right, but it should be chosen with the mangling in mind rather than discovered afterwards.
Does the old column go? Dropping
namemeans a migration and updating every consumer in one change. Keeping it alongside means two sources of truth for the same fact, which will drift. Dropping is cleaner; it just makes the change bigger.What this is not
shipping_addresses.full_namealready exists and isNOT NULL— a delivery name is captured where delivery actually needs it. That is deliberately separate: the person an order ships to is not always the account holder, and a gift is the obvious case. This issue should not fold the two together, and the shipping name should stay exactly as it is.Relationship to #92
#92 makes the emails editable and introduces a
{{greeting}}placeholder that resolves to "Hi Thom," or "Hi,". That placeholder works against whatever name field exists, so the two are technically independent — but the point of this issue is that the greeting should use a first name, and #92 shipping first would mean{{greeting}}produces the formal full name until this lands.Doing this one first means the greeting is right from the moment it becomes editable.
Verification
Integration tests that registration stores both parts, that the account page updates them, and that
publicCustomerreturns them without leaking anything new. A migration test that existing rows survive whatever backfill is chosen. End-to-end that registering with a first name and reaching the account page shows it.Severity
Low. Nothing is broken — this is about the emails being able to sound like a person wrote them.