Contract-first API design

When designing or consuming client/server APIs, keep the contract explicit and prevent duplicate logic/types from drifting. Apply this standard: - **Single source of truth:** If the backend persists/owns a value, the UI should display it directly—avoid re-adding client fallbacks that recreate the same logic.

copy reviewer prompt

Prompt

Reviewer Prompt

When designing or consuming client/server APIs, keep the contract explicit and prevent duplicate logic/types from drifting.

Apply this standard:

  • Single source of truth: If the backend persists/owns a value, the UI should display it directly—avoid re-adding client fallbacks that recreate the same logic.
  • No contract drift: Don’t export additional request/type wrappers that duplicate the method’s already-type-checked signature unless they add real behavior/guarantees.
  • Clear protocol boundaries: Distinguish wire-format types from internal/extended types and document mappings intentionally.
  • Typed surfaces > generic fallbacks: Provide a dedicated typed callback/API for each notification/method; use a generic dispatcher only for untyped/escape-hatch cases.
  • Centralize API access patterns: If multiple call sites repeat getClient() + thin wrappers, centralize into a shared utility/hook to keep calling semantics consistent.

Example patterns:

  • Don’t reintroduce UI fallback when the server is authoritative:
    export function getSessionDisplayName(session: Session): string {
    return session.name || DEFAULT_CHAT_TITLE;
    }
    
  • Avoid exporting a redundant request type when the method signature already validates:
    // Prefer relying on GooseClient.initialize(...) typing
    // instead of maintaining GooseInitializeRequest that can diverge.
    
  • Keep notifications typed and route generically only as fallback:
    export interface GooseExtNotifications {
    unstable_sessionUpdate?: (n: GooseSessionNotification_unstable) => Promise<void>;
    }
    

Source discussions