The People screen forgets when somebody last signed in, and removing them is one of the things
that makes it forget. A person who signed in last week reads as "never signed in" once their
sessions are gone, which is indistinguishable from somebody who has never been here at all.
Where the value comes from
lastSignedInAt is not stored. It is computed on every read as max(sessions.createdAt)
(server/src/people/store.ts:188), over a left join to the sessions table
(server/src/people/store.ts:194). Sessions are not history — they expire, they are cleaned up, and
they are deleted outright when somebody is removed, inside the same transaction that revokes their
access (server/src/people/store.ts:302).
So the screen reports a fact about who currently holds a session and labels it as a fact about the
past. The app renders the empty case as "never signed in" (app/src/routes/_authed/admin/people.tsx:55),
which is a claim the data cannot support.
The sort makes it worse rather than hiding it: the list is ordered by that same aggregate, desc nulls last (server/src/people/store.ts:227), so a person whose sessions expired sinks to the
bottom of the screen among people who never arrived.
This deployment already knows the shape of this problem. The comment introducing sign-in audit rows
names it exactly — "whether a person somebody has just removed had ever been here, because removing
them deletes the sessions that were the only evidence" (server/src/auth/index.ts:105). The audit
trail was given a durable record. The screen an administrator actually looks at was not.
Reproduction
- Open People and note somebody with a last sign-in date.
- Delete that person's rows from
sessions — or remove them through the screen, which does it for
you, or simply wait for their sessions to expire.
- Reload People.
They now read "never signed in", and they have moved to the bottom of the list.
Why it matters where it does
This screen answers two administrative questions: is this account dormant, and was this person ever
actually here. Both are answered from the one field that gets erased by the ordinary lifecycle of a
session — and erased hardest by the removal an administrator performs precisely when they want the
record.
What a fix probably has to do
Store the timestamp on the person rather than deriving it from something that expires: stamp it when
a session is created, and backfill from whatever sessions still exist so a deployment upgrading does
not start from nothing.
Two things are worth getting right. The stamp must never move backwards, so a session row written
out of order cannot rewrite history. And the keyset paging currently compares against the aggregate
in a HAVING clause; moving to a stored column means moving that comparison, not just the select.
What no fix can recover is anybody whose sessions were already gone before the change lands. They
stay blank, because the evidence was deleted.
Severity
No data is at risk and nothing fails. It is a screen that answers a question wrongly, in the
direction that hides information rather than inventing it, about the people an administrator is
being asked to make decisions about.
The People screen forgets when somebody last signed in, and removing them is one of the things
that makes it forget. A person who signed in last week reads as "never signed in" once their
sessions are gone, which is indistinguishable from somebody who has never been here at all.
Where the value comes from
lastSignedInAtis not stored. It is computed on every read asmax(sessions.createdAt)(
server/src/people/store.ts:188), over a left join to the sessions table(
server/src/people/store.ts:194). Sessions are not history — they expire, they are cleaned up, andthey are deleted outright when somebody is removed, inside the same transaction that revokes their
access (
server/src/people/store.ts:302).So the screen reports a fact about who currently holds a session and labels it as a fact about the
past. The app renders the empty case as "never signed in" (
app/src/routes/_authed/admin/people.tsx:55),which is a claim the data cannot support.
The sort makes it worse rather than hiding it: the list is ordered by that same aggregate,
desc nulls last(server/src/people/store.ts:227), so a person whose sessions expired sinks to thebottom of the screen among people who never arrived.
This deployment already knows the shape of this problem. The comment introducing sign-in audit rows
names it exactly — "whether a person somebody has just removed had ever been here, because removing
them deletes the sessions that were the only evidence" (
server/src/auth/index.ts:105). The audittrail was given a durable record. The screen an administrator actually looks at was not.
Reproduction
sessions— or remove them through the screen, which does it foryou, or simply wait for their sessions to expire.
They now read "never signed in", and they have moved to the bottom of the list.
Why it matters where it does
This screen answers two administrative questions: is this account dormant, and was this person ever
actually here. Both are answered from the one field that gets erased by the ordinary lifecycle of a
session — and erased hardest by the removal an administrator performs precisely when they want the
record.
What a fix probably has to do
Store the timestamp on the person rather than deriving it from something that expires: stamp it when
a session is created, and backfill from whatever sessions still exist so a deployment upgrading does
not start from nothing.
Two things are worth getting right. The stamp must never move backwards, so a session row written
out of order cannot rewrite history. And the keyset paging currently compares against the aggregate
in a
HAVINGclause; moving to a stored column means moving that comparison, not just the select.What no fix can recover is anybody whose sessions were already gone before the change lands. They
stay blank, because the evidence was deleted.
Severity
No data is at risk and nothing fails. It is a screen that answers a question wrongly, in the
direction that hides information rather than inventing it, about the people an administrator is
being asked to make decisions about.