Skip to content

Restructure world packages, add SPA defaults registry, fix create-house race - #270

Merged
khanhduyvt0101 merged 2 commits into
mainfrom
spa-defaults-and-cleanup
Apr 29, 2026
Merged

Restructure world packages, add SPA defaults registry, fix create-house race#270
khanhduyvt0101 merged 2 commits into
mainfrom
spa-defaults-and-cleanup

Conversation

@khanhduyvt0101

Copy link
Copy Markdown
Collaborator

Summary

  • Contract package restructured into a router-as-folder layout (lib/router/{me,workspace}lib/router/workspace/{index,sync}.ts), createWorldClient renamed to createClient, dead me procedure + createWorldQuery helper removed, wildcard ./router/* subpath added so TS composite-mode declaration emit can portably name inner router types.
  • Server package swapped to namespace imports (import * as definition from "@openkitten/world-contract", import * as router from "~/lib/router") so adding a new router file flows through every consumer with no edits; new lib/types.ts + ./types subpath for SPA-facing types; is-personal-house.ts returns false instead of throwing on missing workspace, fixing the create-house failure where better-auth's beforeAddMember hook fired before afterCreateOrganization had provisioned the workspace row, leaving orphan house rows that surfaced as ORGANIZATION_ALREADY_EXISTS on retry.
  • SPA package now has apply-defaults.ts invoked from entry.client.tsx registering mutation defaults for every better-auth org operation and query defaults for workspace.sync, orgQueryKeys.list, and a 60s staleTime on the session key. lib/org-query-keys.ts is a scope tree (all/list/full/members/invitations/invitation) and lib/org-mutation-keys.ts carries the mutation scopes (organizations/active) so registry components and our defaults invalidate the same cache. sessionQueryOptions.queryKey aligned to ["auth", "getSession", null] to match @better-auth-ui/react, dropping /auth/get-session traffic from 8 to 2 calls per nav. start-auth-session-sync.ts only invalidates when the session id actually changes (was: refetch + avalanche-invalidate on every $sessionSignal).
  • formatError consolidated into getErrorMessage in @openkitten/world-util; lib/format-error.ts deleted; every caller migrated. New helper handles Error / string / nested {error: {message}} / top-level {message} / falls back to "Something went wrong".
  • Component-vs-lib rule documented in the SPA README: components/ is for registry-generated output (shadcn, better-auth-ui); custom components live under lib/ (flat for multi-route, lib/<route>/<component>.tsx for single-route, function files always flat). New lib/query-error-alert.tsx codifies the data-fetching pattern with retry.
  • File renames matching export names: lib/active-organization-id.tsget-active-organization-id.ts, auth-session-sync.tsstart-auth-session-sync.ts, utils.tscn.ts.
  • Dep bumps (all minor/patch): server's nodemailer/react-email/@react-email/ui; spa's @mantine/hooks/@tanstack/react-query/@tanstack/react-query-devtools/lucide-react/recharts/jsdom/shadcn; spa adds @orpc/tanstack-query.

Test plan

  • bun --bun tsc --build clean
  • bun run --workspaces --if-present test — every world package green: world-contract 13, world-util 27, world-client 6, world-server 92, world-spa 580
  • 100% coverage held across statements / branches / functions / lines
  • bun --bun biome check packages/world reports 0 errors (22 pre-existing warnings in registry test files left alone)
  • Manual smoke in Chrome at //workspace/members → Create house → Update name → Update slug → Invite member → Delete house → Sign-out, all flows render correctly with no orphan DB rows
  • Verify /auth/get-session request count on hard reload is 2 (one TanStack, one nanostore)

🤖 Generated with Claude Code

…se race

Contract package
- Rename `createWorldClient` → `createClient`, drop unused `createWorldQuery`
  helper (and its `@orpc/tanstack-query` dep on the client side).
- Reorganise `lib/router/{me,workspace}` into a router-as-folder layout that
  mirrors `world-server/lib/router/`: `lib/router/workspace/sync.ts` for the
  procedure, `lib/router/workspace/index.ts` aggregates, `lib/router/index.ts`
  re-exports.
- Drop the `me` procedure; nothing in the SPA was consuming it (better-auth's
  session already carries the user).
- Add wildcard `./router/*` subpath to `package.json` exports so TS composite
  mode can portably name types that reference inner router modules.

Server package
- Switch `lib/contract.ts` to `import * as definition from
  "@openkitten/world-contract"` and `lib/rpc-handler.ts` to
  `import * as router from "~/lib/router"` so adding a new router file flows
  through every consumer without edits.
- Add `lib/types.ts` and a `./types` subpath export for SPA-facing type
  re-exports (currently exposes `ActiveMember`).
- Fix the create-house failure (`isPersonalHouse` no longer throws when the
  workspace row is missing). The old throw fired during the better-auth org
  plugin's `beforeAddMember` hook before `afterCreateOrganization` could
  provision the workspace, leaving orphan `house` rows that surfaced as
  `ORGANIZATION_ALREADY_EXISTS` on retry with the same slug.

SPA package
- Replace `rpc-query.ts` with `lib/orpc-client.ts` + `lib/orpc-utils.ts`
  (`createTanstackQueryUtils` from `@orpc/tanstack-query`), mirroring the
  pdfvector layout.
- Add `lib/apply-defaults.ts` invoked from `entry.client.tsx`; registers
  mutation defaults for every better-auth org operation
  (create / delete / setActive / update / inviteMember / removeMember /
  updateMemberRole / cancelInvitation / acceptInvitation / rejectInvitation /
  leave) plus query defaults for `orpcUtils.workspace.sync`,
  `orgQueryKeys.list`, and a 60s `staleTime` on the session key.
- Split keys into `lib/org-query-keys.ts` (scope tree:
  `all/list/full/members/invitations/invitation`) and
  `lib/org-mutation-keys.ts` (`organizations`/`active`) so query-options files
  derive their keys from the same source the registry components use for
  invalidation.
- Each `*-query-options.ts` file now imports the scope from
  `org-query-keys.ts` instead of re-hardcoding the key shape.
- New `lib/query-error-alert.tsx` helper for the data-fetching rule, plus a
  README rule mandating one `useQuery` per component with full state handling
  (`isPending`, `isError`, `error`, `isRefetching`, `refetch`) and a Retry
  button wired through `getErrorMessage`.
- Switch `app/root.tsx` from `import.meta.env.DEV` to `!isLive` from
  `@openkitten/world-util`; rewrite `app/routes/{index,workspace}.tsx` to use
  the new pattern.
- Align `sessionQueryOptions.queryKey` to `["auth", "getSession", null]` so
  the SPA's `clientLoader` and `@better-auth-ui/react`'s `useSession` share
  one cache (down from 8 to 2 `/auth/get-session` requests per nav). Make
  `start-auth-session-sync.ts` smart: only invalidate when the session id
  actually changes, instead of refetching on every `$sessionSignal`.
- File renames matching the export name: `lib/active-organization-id.ts`
  → `get-active-organization-id.ts`, `auth-session-sync.ts`
  → `start-auth-session-sync.ts`, `utils.ts` → `cn.ts`.
- Components: shadcn/better-auth-ui registry output stays under
  `components/`; hand-written customs live under `lib/`. The original
  `components/auth/`, `components/settings/`, `components/user/` are kept
  intact (they're CLI-managed).
- Migrate `formatError` into `getErrorMessage` in `world-util` (now handles
  Error / string / nested `{error: {message}}` / top-level `{message}` / falls
  back to "Something went wrong"); delete `lib/format-error.ts`; route every
  caller through the util helper.

Server runtime
- Migrate `is-personal-house.ts` to return `false` for missing-workspace
  instead of throwing; the strict invariant is enforced by `auth-contract.ts`
  middleware, so the permission gate doesn't need to.

Deps (all minor / patch)
- server: nodemailer 8.0.5→8.0.7, react-email 6.0.0→6.0.5,
  @react-email/ui 6.0.0→6.0.5
- spa: @mantine/hooks 9.1.0→9.1.1, @tanstack/react-query 5.100.1→5.100.6,
  @tanstack/react-query-devtools 5.100.1→5.100.6, lucide-react 1.12.0→1.14.0,
  recharts 3.8.0→3.8.1, jsdom 29.0.2→29.1.0, shadcn 4.4.0→4.6.0
- spa: add `@orpc/tanstack-query`

100% coverage held across `world-contract` (13), `world-util` (27),
`world-client` (6), `world-server` (92), `world-spa` (580).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@khanhduyvt0101
khanhduyvt0101 enabled auto-merge (squash) April 29, 2026 12:43
@khanhduyvt0101
khanhduyvt0101 disabled auto-merge April 29, 2026 12:44
…ullAssertion warnings

AGENTS.md prohibits non-null assertions (`!`). The 22 outstanding biome
warnings were all `.closest("form")!` in dialog test files. Replace with a
typed helper at `test/lib/closest-form.ts` that throws if the lookup misses.
Also pick up the one fixable `useTemplate` lint via biome --write --unsafe.

biome check is now 0 errors / 0 warnings / 0 info; tsc clean; all 580 SPA
tests still green at 100% coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@khanhduyvt0101
khanhduyvt0101 enabled auto-merge (squash) April 29, 2026 12:50
@khanhduyvt0101
khanhduyvt0101 merged commit 024d69e into main Apr 29, 2026
1 check passed
@khanhduyvt0101
khanhduyvt0101 deleted the spa-defaults-and-cleanup branch April 29, 2026 12:53
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.

1 participant