Skip to content

feat(siwe): act as a Safe — switcher, persistent banner, subject-aware profile - #103

Open
douglance wants to merge 3 commits into
mainfrom
siwe/act-as-ui
Open

feat(siwe): act as a Safe — switcher, persistent banner, subject-aware profile#103
douglance wants to merge 3 commits into
mainfrom
siwe/act-as-ui

Conversation

@douglance

Copy link
Copy Markdown
Contributor

Stacked on #102. Second of four.

Adds the UI for GET /api/auth/safes and POST/DELETE /api/auth/act-as.

Two design points that drove the shape

The Safe list is a recall table, not a discovery one. The indexer only remembers a Safe after a successful act-as, so GET /api/auth/safes is empty for every new user. A picker alone would be a dead end with no way in — which is why "Act as a Safe by address…" is always offered rather than tucked away as a power-user path. The dialog deliberately stays open on failure so the indexer's reason (404 not a Safe / 403 not an owner) sits next to the address that caused it.

ActingAsBanner is deliberately not dismissible. Act-as is stored server-side on the session row, and the cookie is 7 days on a sliding window — so the mode outlives reloads, tab closes, and restarts. Without a permanent marker, a user can come back days later and quietly edit the Safe's profile believing it is their own. ProfileEditor now names the subject it will write for the same reason.

Review focus

The cache-eviction path in hooks/use-act-as.ts. Keys already embed the effective address (#102), so a stale entry cannot be read as the new subject's; the extra removeQueries on switch stops the old subject's data lingering and flashing back when toggling to and fro.

Verification

pnpm test — 1225 unit tests pass. Behaviour is covered end to end by the spec in the fourth PR of this stack (5 tests, including 404 safe_not_found and 403 not_an_owner against a real Safe on the local testnode).

@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tally-zero Ready Ready Preview Aug 27, 2026 6:10pm

Request Review

…the full mount

The SIWE mount exposes 24 routes; the client wrapped 6. Add the remaining
18 (session, safes, act-as, drafts, elections, candidate profiles).

Every call now goes through one send() helper, including the five that
previously hand-rolled fetch + parse — one convention in the file rather
than two. That also fixes logout(), which fire-and-forgot its response, so
a failed sign-out looked successful while the server kept the session alive.

Query keys: the indexer resolves every owned read/write against
effectiveSubject = actingAs ?? address, so act-as is a server-side mode and
the same URL means a different subject once it flips. Caching across that
boundary can put one entity's data into a form whose Save writes it to
another. Every subject-scoped key therefore nests under one shared
SUBJECT_SCOPE prefix carrying the effective address, so eviction on a
subject switch is a single removeQueries() and a newly added subject-scoped
query is covered the moment it is written — no parallel list to keep in
sync. keys.test.ts pins that invariant.

Signer-scoped keys are kept deliberately separate: /api/auth/safes resolves
against session.address, not the effective subject, so acting as a Safe must
not move it.

MeResponse.safes was typed unknown[] but is a bare string[]; the enriched
KnownSafe[] only comes from /api/auth/safes. The draft governor type is
DraftGovernorType rather than GovernorType because config/governors.ts
already owns that name with different values.
douglance and others added 2 commits August 27, 2026 13:43
They are regenerated by the setup project on every run and were already
gitignored; the ignore rule never applied because they were tracked.
…e profile

Adds the UI for GET /api/auth/safes and POST/DELETE /api/auth/act-as.

The Safe list is a recall table: the indexer only remembers a Safe after a
successful act-as, so it is empty for every new user. A picker alone would
be a dead end, which is why the by-address dialog is always offered rather
than hidden as a power-user path. It stays open on failure so the indexer's
reason (404 not a Safe / 403 not an owner) sits next to the address that
caused it.

useSafes keys on session.address, not the connected wallet. Those diverge —
sign in as A, switch wallet to B without signing out — and the response
belongs to whoever the session says you are, so keying on the connected
address would file A's Safes under B. Leaving act-as drops every
subject-scoped query in one removeQueries(SUBJECT_SCOPE), which also covers
subject-scoped queries added later.

ActingAsBanner is deliberately not dismissible. Act-as lives on the session
row and the cookie is 7 days on a sliding window, so the mode outlives
reloads, tab closes, and restarts; without a permanent marker a user can
return days later and quietly edit the Safe's profile believing it is their
own. ProfileEditor names the subject it will write for the same reason.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants