Conversation
Adds null as a third sort state so clicking a sorted-desc column removes the sort rather than snapping back to asc. Also fixes a side-effect-in-updater bug where setSortDirection was called inside setSortBy's pure updater, which React StrictMode could invoke out of order.
Extract the inline search debounce in UsersTable into a reusable useDebouncedValue hook. The optional onDebounced callback fires only when the debounced value actually changes (via an Object.is guard against the previous value), so it does not fire on mount or on no-op re-settles.
Switch the delete-account confirmation input to type=text (so Bitwarden's keyword/type heuristics don't detect it) and reproduce the native bullet masking with CSS -webkit-text-security. Add typeOverride and placeholder props to PasswordInput so all other password fields stay native, and drop the PasswordManagerHint from the delete dialog.
Surface the admin and system audit logs as paginated, sortable, filterable tables in the admin route. Server: - Add GET /admin-audit-logs and /system-audit-logs endpoints with arktype-validated query params (page, pageSize, sortBy, sortDirection, action). Numerics are coerced from query strings via morphs; sorting and filtering on `action` reach into the JSON metadata column via SQLite's `->>` operator. Admin logs inner-join their acting user. - Add GET /avatar/:userId to serve an arbitrary user's avatar, mirroring the authenticated handler (weak ETag, 304 revalidation, no-cache). Client: - Add AdminAuditLogsTable and SystemAuditLogsTable with their column defs, wired into route.lazy via new AdminSection blocks. - Extract SortableHeader into its own component, shared with the users table. - getUserInitials now takes Pick<User, 'name' | 'lastName'>. Shared: - Add arrayOfAll helper for exhaustive, type-checked literal arrays. - Add adminAuditLogActions / systemAuditLogActions as the single source of truth for the audit log action filters. Tests: - Add adminRoutes integration tests plus a bunfig preload that sets the env vars server modules read at module-load time.
Sync the fullstack template with the upstream example: extract better-auth
options into authOptions.ts and add admin user-management audit logging,
tests, types, and constants. Preserves the {{PROJECT_NAME}} placeholder.
….toml in the same way that `npm pack` excludes `.npmrc`
…on types Make each enum-like type/array pair have one canonical definition that everything else derives from, in whichever direction fits the data. - ErrorContext: the `errorContexts` array in shared/constants.ts is now the source of truth (`[...] as const`); the union derives from it via `(typeof errorContexts)[number]`. Drops the `arrayOfAll` wrapper and the duplicated hand-maintained union, so the two can no longer drift. - Audit-log actions: keep the metadata discriminated unions canonical, but move AdminAuditLogAction/SystemAuditLogAction into server/types.d.ts and derive them via `Pick<...Metadata, 'action'>['action']` instead of indexing through AppSchemaSelect. Label SharedAuditLogsMetadata as the source of truth. `arrayOfAll` stays here — it's the correct exhaustiveness check against a schema-derived type, not a duplication to collapse. - Admin tables: import the action types from @/shared/types; drop the local `ActionFilter` alias in -SystemAuditLogsTable in favor of the shared SystemAuditLogAction type. - Update the add-error-context skill to document the array as source of truth.
- Move DefaultErrorComponent out of router.tsx into its own file so the router module no longer mixes a component with non-component code, restoring a clean Fast Refresh boundary (only-export-components). - Group AdminAuditLogsTable's 5 related table-query useState calls into a single useReducer with typed actions, centralizing the reset-to-page-1 logic and making each transition one atomic update (prefer-useReducer).
…file Port React Doctor maintainability fixes into the fullstack template: - deslop/unused-export: bunPluginTailwind was exported as a named function but only the default export is consumed (by build.ts). Drop the named export keyword; the default export is unchanged. - deslop/unused-file: genDbHash.ts was unreachable from any entry point and imported nowhere. Delete it.
- Destructure useQuery results in 7 admin tables/dialogs so TanStack Query's tracked-property optimization stays effective (query-destructure-result) - Honor prefers-reduced-motion: useReducedMotion() halts the border-beam loop and a global CSS media query collapses CSS animations/transitions (require-reduced-motion, WCAG 2.3.3) - Key the field error list by message instead of array index (no-array-index-as-key)
Resolves react-doctor no-tiny-text warnings across the five transactional email templates.
Mirror of fullstack-example b475b3d. - extract generateStrongPassword into client/lib/utils.ts; admin CreateUser/SetPassword dialogs now import it (removes the duplicated block) - rename passwordCharset -> pwGenCharset to avoid a secret-scanner false positive (no-secrets-in-client-code) - AccountAvatar: selectedFile useState -> useRef since it's only read in handlers (rerender-state-only-in-handlers) - combine flatMap().filter() into a single pass in handleFormSubmitInvalid (js-combine-iterations)
…pers Mirror of fullstack-example 8ba1be9. Extract three copy-pasted patterns into shared homes: - usePasswordGenerator hook: the generate + copy-to-clipboard state/handlers duplicated between the create-user and set-password admin dialogs. - nameFieldValidator: the first/last-name field validator (single regex check; the prior regex+arktype double-check was redundant). - passwordsMatchValidator: the "confirm password" matcher shared by set-password, signup, and reset-password; unified to validate live and on submit with an empty-field guard.
Port of the same changes from fullstack-example into the fullstack project
template.
- only-export-components: extract badge/button cva variants into sibling
*.variants.ts files; move DetailText into its own file
- use-lazy-motion: hoist <LazyMotion features={domAnimation} strict> to
app.tsx, convert border-beam/magic-card to <m.div>
- no-derived-state/no-event-handler: replace ChangeRoleDialog reset effect
with a render-time previous-value state sync
- biome: restrict the full `motion` import in client code (use `m`)
- conventions: document provider placement, motion `m`, comment style
…files Reverse the file extractions made while resolving React Doctor warnings in the fullstack project template, moving each back into its original module: - DefaultErrorComponent -> router.tsx - DetailText -> -adminAuditLogsColumns.tsx - badgeVariants -> badge.tsx - usePasswordGenerator hook -> -CreateUserDialog.tsx and -SetPasswordDialog.tsx No behavior change.
Make shared/validators.ts the single home for the person's-name check: - add framework-agnostic validateName(value, label?) — now the only nameRegex.test caller - nameFieldValidator becomes a thin TanStack adapter over validateName - migrate the missed ChangeName call site (account) off its inline regex copy onto nameFieldValidator No behavior change.
…t seam
Every user-triggered mutation hand-rolled the same ~15-line envelope: a
try/catch distinguishing Better Auth's `{error}` resolve from a thrown
error, success/error toasts, React Query invalidation, logClientError, and
an isPending flag. That policy lived in ~20 places and drifted.
Introduce src/client/hooks/useMutationWithToast.ts owning the envelope
behind a small interface (run + isPending). Options express the real
variations: success text, invalidate key(s), errorFallback vs exception
messages, errorMessage mode (raw/safe/static) for the three error-display
policies, onSuccess, and a suppress predicate for the WebAuthn
NotAllowedError. Pre-flight checks stay in callers.
Migrate all 13 mutation sites across the admin dialogs, UserRowActions,
account forms, and Passkeys. AccountAvatar is left as-is: it always throws
(parseResponse) and needs toast-but-don't-log-on-DetailedError, a policy
outside this seam.
Add the first client-side test (useMutationWithToast.test.tsx) plus a
scoped happy-dom setup (src/client/test/happyDom.ts) that brackets
register/unregister per file so it doesn't clobber Bun's native
Headers/Response in the server tests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Complete ground-up rewrite of
create-new-appfrom v7 → v8. The old CLI is deleted entirely and replaced with a Bun-native TypeScript CLI that generates one of two opinionated, production-ready templates.Scope: 376 files changed, 257 added / 116 deleted / 3 modified (~20k lines in, ~17.6k lines out).
What changed
commander+ EJS + webpack-config-generator pipeline is gone. The new CLI lives insrc/cli/:src/cli/index.ts— entrypoint (#!/usr/bin/env bun), printsintro/outrovia@clack/prompts.src/cli/options-parser.ts—node:utilparseArgswrapper.src/cli/resolveProjectOptions.ts— turns raw args into validated options; prompts only for values that are missing or invalid (unless--yes).src/cli/generateProject.ts— plans the file copy, replaces{{PROJECT_NAME}}/{{BETTER_AUTH_SECRET}}, applies writes, runsbun install/bunx biomeInit/git init.[project-name],--type(fullstack|client-only),--yes,--help,--version. The v7 flag soup (--router,--express,--mongo,--api,--apiPort,--mongoPort,--mongoPortProd,--mongoUser,--mongoAuthSource,--browserslist,--sandbox,--offline,--force,--noGit,--title, etc.) is gone — replaced by two well-tested project shapes.package.jsonexposes bothcreate-new-appandcna, both pointing atsrc/cli/index.ts.src/projects/:fullstack/— React 19 + TanStack Router (file-based, type-safe) + TanStack Query + TanStack Form + Jotai on the client; Bunserve()+ Hono + Drizzle ORM +bun:sqlite+ LiteFS (Fly.io) + Better Auth (with@better-auth/passkey) + Resend / React Email + Sharp on the server. Ships with multi-stageDockerfile,Dockerfile.local,fly.toml,litefs.yml, adeploy.tsscript, and a Drizzle Studio build for prod.client-only-react/— React 19 SPA with the same TanStack stack and Tailwind v4 + shadcn/ui, served by Bun's bundler.package.jsondeclares"workspaces": ["src/projects/*"], so the templates can be developed standalone (cd src/projects/fullstack && bun install) while still being shipped as files.-keeprename trick. Files/dirs that npm strips on publish (.gitignore-keep,.vscode-keep, etc.) or that Bun workspaces would over-eagerly resolve (biome.jsonc-keep,.claude-keep) live with-keepsuffixes in the repo and are renamed on copy bygenerateProject.ts.bun:test.tests/now contains 4 files covering CLI arg parsing, option resolution / prompting, the template planning step, and an end-to-end generate-project integration test. All v7 Jest tests undertests/unit/*.jsare deleted.@qodestack/biome-config) replaces Prettier;bun build --compileproduces a standalone binary for distribution alongside the npm package; TypeScript 6.main.js, all ofmodules/, all offile-creators/, all offiles/(the v7 webpack/EJS template tree including Express, MongoDB, React Router, and sandbox files),checkDependencies.js,removeTestFolders.js,versionCheck.js,.npmignore,.prettierignore,.watchmanconfig. All Express/Mongo/sandbox/webpack code paths are gone.README.mdrewritten to reflect the new architecture, the two-template model, and the new CLI surface. Instructions are Bun-only (bun install -g,bunx); the npm version badge is preserved.Migration note
This is a hard break from v7. Anyone wanting the legacy Express/MongoDB/sandbox CLI should pin to v7. v8 is a different tool published under the same name.
Test plan
bun installat the repo root succeedsbun test— all 4 test files pass (cli.test.ts,resolve-project-options.test.ts,plan-template.test.ts,generate-project.integration.test.ts)bun run check— Biome passes cleanbun run build—bun build --compileproduces a workingdist/create-new-appbinarybun run dev(i.e. running the CLI from source) with no args → guided prompts ask for name + type, then generatebun run dev my-app -t fullstack -y→ no prompts, generates fullstack templatebun run dev my-app -t client-only -y→ no prompts, generates client-only templateMyApp,1app,-app) → validation error or re-prompt--yes→ exits with error--helpand--versionprint and exit 0bun devboots, browser opens, auth flows work, Drizzle Studio reachable viabun run db:viewbun devboots and rendersgit initruns in the generated project (and CLI logs a warning rather than failing ifgitis unavailable){{PROJECT_NAME}}placeholders are replaced everywhere (package.json, fly.toml, litefs.yml, Dockerfile, env files)BETTER_AUTH_SECRETis generated per run for fullstack projects-keepfiles/dirs are renamed on copy (e.g..gitignore-keep→.gitignore,.claude-keep/→.claude/)