Skip to content

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

Open
ditto-agent wants to merge 7 commits into
masterfrom
sdk/contacts-slice
Open

feat(wallet-sdk): contacts slice (step 7)#1174
ditto-agent wants to merge 7 commits 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). The api strips ownerId before it returns contacts, so the runtime shape matches the Omit projection (review fix d78a3b5; the first pass let it ride along at runtime).
  • Send seam: resolve-destination.ts takes the public Contact and narrows with typeof input !== 'string' — a host-built public-shaped contact must not fall through to string parsing. The zod isContact guard lost its last consumer and is deleted.

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

ditto-agent and others added 6 commits August 11, 2026 14:07
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d api, sdk wiring

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d seam

The index shadow export hid the contract Contact; resolve-destination now
takes the public projection with typeof narrowing (isContact deleted with
its last consumer), and the API keeps ownerId riding along at runtime like
the account entities.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@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 11, 2026 3:44pm

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.

Restores plan decision 4, reversed mid-slice when the send seam surfaced
as an extra Contact consumer. The seam now takes the public projection
and no code reads ownerId, so the api strips it in get/list/create and
the runtime shape matches the Omit contract. Tests assert the exact
public shape; the stale projection comment is removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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