Skip to content

feat(wallet-sdk): contacts slice (step 7) - #1174

Merged
jbojcic1 merged 1 commit into
masterfrom
sdk/contacts-slice
Aug 12, 2026
Merged

jbojcic1 merged 1 commit into
masterfrom
sdk/contacts-slice

Conversation

@ditto-agent

@ditto-agent ditto-agent commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Step 7 of the no-cache SDK extraction (spec: docs/superpowers/specs/2026-06-24-wallet-sdk-no-cache-production-design.md). Wraps the already-moved contacts domain in the sdk.contacts contract namespace and flips the web contacts feature off @agicash/wallet-sdk/temporary.

Plan + recorded execution deviations: docs/superpowers/plans/2026-08-11-wallet-sdk-contacts-slice.md.

SDK

  • Contract fill (domain/sdk/contacts.ts): CreateContactParams = { username } (ownerId implicit from the session); findContactCandidates(query): Promise<UserProfile[]> — deviation from the step-4 stub's Contact[]: candidates are user profiles, a contact id exists only after create.
  • createContactsApi (domain/contacts/contacts-api.ts): session-fenced per the accounts pattern (requireUserId + sessionSignal pre/post checks); repository constructed once (db + lud16 domain are process-stable; contacts rows are plaintext, no key material). 11 bun tests.
  • ContactRepository.get → .maybeSingle() + null on missing (contract: Contact | null) + abortSignal option.
  • sdk.ts: contacts getter no longer throws NotImplementedError; wired with config.lightningAddressDomain (its first real consumer).
  • Public Contact projection surfaced: dropped the shadowing export type { Contact } from index.ts (per that file's slice-deletes-its-names mechanism). Review follow-up (5cd8577, 366fa51): ownerId is deleted from the domain entity itself — nothing read it (ownership lives in query params + RLS), so one Contact type serves as both entity and contract type. No Omit, no runtime mapper; the root index.ts domain-type export is back as the sole origin (it no longer shadows a projection), and the web realtime echo (toContact) produces the exact contract shape.
  • Send seam: resolve-destination.ts takes the Contact type and narrows with the zod isContact guard. The guard was deleted mid-slice — while the schema still carried ownerId it would have misrouted public-shaped contacts into string parsing — and restored in b2092ae after the entity collapse made the schema match the seam shape; positive narrowing keeps the else-branch honest if the input union ever grows.

Web

  • contact-hooks.ts: useContacts / useCreateContact / useDeleteContact / useFindContactCandidates now call sdk.contacts.*; query keys and cache semantics unchanged. ContactsCache and useContactChangeHandlers stay as-is — the realtime invalidation layer flips in step 18 (accounts precedent), so the handlers keep their two /temporary imports.
  • contact-repository-hooks.ts deleted (last consumer gone); unused isContact re-export pruned from temporary.ts.

Verification

  • bun run fix:all, bun run typecheck (all packages), bun run test — all exit 0 (SDK 143 tests incl. 11 new; web 38).
  • Browser smoke on a fresh local stack: guest signup + provisioning; contacts list (empty + populated), candidate search, create (list updates via the realtime CONTACT_CREATED echo — the sole cache-add path), detail page, delete. Zero console errors.

🤖 Generated with Claude Code

@supabase

supabase Bot commented Aug 11, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project hrebgkfhjpkbxpztqqke because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@vercel

vercel Bot commented Aug 11, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
agicash Ready Ready Preview Aug 12, 2026 12:01pm

Request Review

Comment thread packages/wallet-sdk/domain/sdk/contacts.ts Outdated
* @param value - The value to check
* @returns True if the value is a valid Contact, false otherwise
*/
export const isContact = (value: unknown): value is Contact => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It lost its last consumer in this PR (deviation 3 in the plan doc). At master, isContact had two consumers: the dead temporary.ts re-export (the web never imported it; pruned in d3973e0) and resolveSendDestination.

This PR retypes that seam's input to the public Contact. The zod guard validates the domain schema, which requires ownerId — a public-shaped contact fails the guard, falls into the string branch, and breaks resolution. typeof input !== 'string' narrows string | Contact exactly. With zero consumers left, the guard was deleted.

After d78a3b5 (thread above), api contacts also lack ownerId at runtime, so the old guard would now reject every real contact.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outcome: restored in b2092ae. The deletion reason expired — with ownerId gone from ContactSchema (entity collapse, thread above) the guard accepts the seam shape again. It replaces the typeof narrowing: a positive guard keeps the else-branch honest if the input union ever gains a third member.

…e web flip

Wraps the contacts domain in the sdk.contacts contract namespace and
flips the web contacts feature off @agicash/wallet-sdk/temporary.

- createContactsApi: session-fenced per the accounts pattern
  (requireUserId + sessionSignal pre/post checks); repository built
  once; findContactCandidates returns UserProfile[] — candidates are
  profiles, a contact id exists only after create.
- ownerId is deleted from the Contact entity: nothing read it
  (ownership lives in query params + RLS), so one Contact type serves
  as entity and contract type, exported from the package root.
- Send seam: resolve-destination narrows string | Contact with the
  isContact guard against the ownerId-free schema.
- ContactRepository.get: maybeSingle + null on missing + abortSignal.
- Web contact hooks call sdk.contacts.*; query keys and cache semantics
  unchanged; realtime invalidation stays on /temporary until step 18;
  contact-repository-hooks deleted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jbojcic1
jbojcic1 merged commit 639ab00 into master Aug 12, 2026
5 checks passed
@jbojcic1
jbojcic1 deleted the sdk/contacts-slice branch August 12, 2026 12:02

This branch was successfully deployed

1 active deployment
Preview — 4e50a839 Deployed Aug 12, 2026 by vercel[bot]
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