Skip to content

[v3 stable] Verify exact Better Auth UI override contracts across repositories #170

Description

@olliethedev

Stable-v3 cohort note — 2026-08-31

The override contract remains unchanged, but final acceptance must run against the corrected companion candidate from better-auth-ui#22 on the retained Better Auth 1.6.16 database cohort. @btst/db@2.2.3 and the 2.2.3 adapters remain unchanged.


This was generated by AI during triage.

Current status — 2026-08-31

The companion implementation dependency is complete: better-auth-ui#20 merged through PR #21, and @btst/better-auth-ui@2.0.0-rc.3 is published. Core #202 is also complete.

This issue remains open only for its cross-repository acceptance scope:

The bridge/API migration is complete, but stable-v3 acceptance must use the corrected dependency-cohort candidate from better-auth-ui#22 rather than the published RC3 package. This remains a stable-v3 acceptance gate and does not reopen the completed bridge design.


Parent

Original RC2 reproduction — preserve this regression

Environment:

  • @btst/stack@3.0.0-rc.2
  • @btst/better-auth-ui@2.0.0-rc.1
  • @btst/yar@1.3.2

Following the RC2 Better Auth UI guide, both the primary provider example and Avatar Upload recipe put avatar configuration under account:

overrides={{
  account: {
    authClient,
    basePath: "/p/account",
    avatar: {
      upload: async (file) => myUploader(file),
      size: 128,
      extension: "png",
    },
  },
}}

This type-checks because AccountPluginOverrides extends Partial<AuthPluginOverrides>. The released RC1 bridge, however, constructs avatar configuration only from authOverrides.avatar. It never reads accountOverrides.avatar, so the documented configuration compiles and is silently ignored. Moving the same block to auth.avatar makes it work; the retained Better Stack Web RC2 audit verified the real Vercel Blob/session flow through that undocumented location.

Evidence: RC2 report section.

Broader defect

Avatar exposed a structural contract failure rather than a one-field typo. Account and organization publicly inherit auth options that the bridge does not read at those locations. TypeScript therefore accepts auth clients, credentials, callbacks, feature flags, page props, and route bases that may be ignored or interpreted elsewhere. Independently configurable route base paths can also make Better Auth UI links disagree with BTST's registered routes.

RC3 must make accepted configuration and consumed configuration identical across the external companion, core documentation/scaffolding, generated fixtures, and public declarations.

Final contract to validate

The implementation belongs to better-stack-ai/better-auth-ui#20. This issue owns cross-repository acceptance of that contract after #202.

The decision-rich shape is:

interface AuthPluginOverrides {
  authClient: AnyAuthClient
  redirectTo?: string
  onSessionChange?: () => void | Promise<void>
  // auth-only flags, hooks, mutators, localization, and auth page props
}

interface AccountPluginOverrides {
  account?: boolean | AccountOptionsWithoutRouteBasePath
  avatar?: boolean | AvatarOptions
  deleteUser?: boolean | DeleteUserOptions
  teams?: boolean | TeamOptions
  onRouteError?: OnRouteError
  pageProps?: AccountPagePropsMap
}

interface OrganizationPluginOverrides {
  organization?: boolean | OrganizationOptionsWithoutRouteBasePath
  teams?: boolean | TeamOptions
  onRouteError?: OnRouteError
  pageProps?: OrganizationPagePropsMap
}

Exact names may follow the final #202 API, but these semantics are fixed:

  1. authClient is configured once under auth.
  2. account.avatar is the only wrapper-facing avatar location; auth.avatar is rejected.
  3. Account and organization do not publicly extend auth override types.
  4. Auth-only credentials, callbacks, hooks/mutators, feature flags, and auth page props are rejected under account/organization.
  5. Each page-prop map accepts only pages owned by that plugin.
  6. Shared internal behavior may use private composition; it must not widen public interfaces.
  7. No override exposes a root or nested route basePath. /auth, /account, and /organization are relative plugin routes derived from [v3 RC3 DX] Centralize client runtime configuration and normalize all plugin interfaces #202's resolved site runtime.
  8. A public field that type-checks is read from that exact location at runtime.
  9. Omitted override blocks are valid; empty objects are not plugin-activation signals.
  10. Provider keys and values are inferred from the registered client stack rather than a manually assembled override map.

Cross-repository acceptance coverage

  • Package-level positive type fixtures cover intended auth, account, and organization overrides.
  • Negative type fixtures cover auth.avatar, account.authClient, organization.authClient, misplaced auth credentials/hooks/callbacks/feature flags/page props, cross-plugin page keys, and every removed basePath placement.
  • Runtime bridge tests prove account.avatar reaches the upstream avatar context and the configured upload/delete callbacks.
  • Runtime tests prove every accepted override is consumed from the location exposed by its public type.
  • Route tests prove a site mount such as /pages consistently yields /pages/auth, /pages/account, and /pages/organization for matching, links, redirects, metadata, and sitemap output.
  • Auth-plus-account works without organization; organization remains an explicit opt-in.
  • Core docs, CLI examples, generated code, and Better Auth UI docs use the same exact locations and contain no stale accepted-but-ignored examples.
  • Packed consumer fixtures compile the public declarations rather than relying on workspace-only source types.

Acceptance criteria

  • The original RC2 example remains captured as a regression fixture or equivalent consumer test.
  • account.avatar type-checks and invokes upstream upload/delete behavior.
  • auth.avatar fails to type-check with no compatibility alias or precedence rule.
  • Account and organization no longer inherit auth overrides publicly.
  • authClient is accepted only at the auth seam and consumed once.
  • Misplaced auth-only settings and cross-plugin page props fail compile-time fixtures.
  • Every field accepted by a public override type is consumed from the same object at runtime.
  • Independent route base-path overrides are absent; resolved site runtime is the sole route-base source.
  • Omitted override blocks work and inferred provider types reject unregistered plugin keys.
  • Companion package tests, public declaration fixtures, core documentation, and generated examples all encode the same contract.
  • Next.js, React Router, and TanStack clean consumer fixtures compile/build the minimal auth-plus-account configuration from the packed corrected companion candidate on the retained Better Auth 1.6.16 cohort.
  • The issue is not closed solely because the external implementation merged; cross-repository docs/generated acceptance evidence is linked here.

Out of scope

  • Recreating Better Auth native permissions through BTST authorization.
  • Exporting Better Auth-to-BTST identity/auth factories.
  • Generating a Better Auth backend, database, schema, providers, or secrets.
  • Making organization or optional Better Auth features mandatory.

Blocked by

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingready-for-agentFully specified and ready for an agent to implement

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions