@payez/next-mvp 4.8.1 → 4.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/POST-INSTALL.md +408 -408
  2. package/README.md +859 -859
  3. package/dist/api-handlers/admin/audit.js +13 -21
  4. package/dist/api-handlers/admin/sessions.js +59 -45
  5. package/dist/api-handlers/admin/stats.js +1 -1
  6. package/dist/api-handlers/admin/users.js +15 -23
  7. package/dist/api-handlers/admin/vibe-data.d.ts +5 -0
  8. package/dist/api-handlers/admin/vibe-data.js +5 -0
  9. package/dist/components/account/MobileNavDrawer.js +18 -18
  10. package/dist/components/account/UserAvatarMenu.js +1 -1
  11. package/dist/components/admin/AlertSettingsTab.js +40 -40
  12. package/dist/components/admin/LoggingSettingsTab.js +28 -28
  13. package/dist/lib/session-store.js +21 -21
  14. package/dist/lib/startup-init.js +19 -19
  15. package/dist/pages/login/page.js +28 -28
  16. package/dist/pages/settings/EnhancedSettingsPage.js +17 -17
  17. package/dist/vibe/client.d.ts +1 -1
  18. package/dist/vibe/client.js +2 -1
  19. package/dist/vibe/filters.d.ts +36 -0
  20. package/dist/vibe/filters.js +47 -0
  21. package/dist/vibe/generic.js +2 -1
  22. package/dist/vibe/sessions.js +28 -3
  23. package/package.json +2 -2
  24. package/scripts/check-publish-eol.mjs +33 -15
  25. package/src/api/auth-handler.ts +550 -550
  26. package/src/api/index.ts +17 -17
  27. package/src/api-handlers/account/change-password.ts +170 -170
  28. package/src/api-handlers/account/masked-info.ts +46 -46
  29. package/src/api-handlers/account/profile.ts +99 -99
  30. package/src/api-handlers/account/recovery/forward.ts +117 -117
  31. package/src/api-handlers/account/recovery/initiate.ts +59 -59
  32. package/src/api-handlers/account/recovery/send-code.ts +21 -21
  33. package/src/api-handlers/account/recovery/verify-code.ts +20 -20
  34. package/src/api-handlers/account/reset-password.ts +87 -87
  35. package/src/api-handlers/account/send-code.ts +70 -70
  36. package/src/api-handlers/account/update-phone.ts +84 -84
  37. package/src/api-handlers/account/validate-password.ts +139 -139
  38. package/src/api-handlers/account/verify-email.ts +134 -134
  39. package/src/api-handlers/account/verify-sms.ts +134 -134
  40. package/src/api-handlers/admin/analytics.ts +452 -452
  41. package/src/api-handlers/admin/audit.ts +223 -232
  42. package/src/api-handlers/admin/index.ts +63 -63
  43. package/src/api-handlers/admin/redis-sessions.ts +259 -259
  44. package/src/api-handlers/admin/sessions.ts +341 -327
  45. package/src/api-handlers/admin/site-logs.ts +388 -388
  46. package/src/api-handlers/admin/stats.ts +260 -260
  47. package/src/api-handlers/admin/users.ts +240 -251
  48. package/src/api-handlers/admin/vibe-data.ts +338 -333
  49. package/src/api-handlers/anon/preferences.ts +123 -123
  50. package/src/api-handlers/auth/credential-login-email-code.ts +92 -92
  51. package/src/api-handlers/auth/credential-login.ts +948 -948
  52. package/src/api-handlers/auth/jwks.ts +31 -31
  53. package/src/api-handlers/auth/login.ts +252 -252
  54. package/src/api-handlers/auth/passkey-signin.ts +116 -116
  55. package/src/api-handlers/auth/refresh.ts +829 -829
  56. package/src/api-handlers/auth/signout.ts +201 -201
  57. package/src/api-handlers/auth/status.ts +23 -23
  58. package/src/api-handlers/auth/update-session.ts +144 -144
  59. package/src/api-handlers/auth/validate.ts +45 -45
  60. package/src/api-handlers/auth/verify-code.ts +116 -116
  61. package/src/api-handlers/mfa/backup-code-verify.ts +170 -170
  62. package/src/api-handlers/mfa/backup-codes.ts +42 -42
  63. package/src/api-handlers/mfa/methods.ts +47 -47
  64. package/src/api-handlers/mfa/totp-confirm.ts +43 -43
  65. package/src/api-handlers/mfa/totp-enroll.ts +43 -43
  66. package/src/api-handlers/mfa/totp-verify.ts +172 -172
  67. package/src/api-handlers/oauth/resume.ts +182 -182
  68. package/src/api-handlers/onboarding/send-email-code.ts +18 -18
  69. package/src/api-handlers/onboarding/set-password.ts +21 -21
  70. package/src/api-handlers/onboarding/verify-code.ts +20 -20
  71. package/src/api-handlers/onboarding/verify-welcome-email.ts +17 -17
  72. package/src/api-handlers/passkeys/list.ts +32 -32
  73. package/src/api-handlers/passkeys/manage.ts +55 -55
  74. package/src/api-handlers/passkeys/register-options.ts +43 -43
  75. package/src/api-handlers/passkeys/register-verify.ts +42 -42
  76. package/src/api-handlers/session/refresh-viability.ts +56 -56
  77. package/src/api-handlers/session/viability.ts +238 -238
  78. package/src/api-handlers/test/force-expire.ts +167 -167
  79. package/src/auth/auth-decision.ts +292 -292
  80. package/src/auth/better-auth.ts +1688 -1688
  81. package/src/auth/idp-oauth.ts +310 -310
  82. package/src/auth/password-reset-bridge.ts +98 -98
  83. package/src/auth/revocation-fields.ts +16 -16
  84. package/src/auth/route-config.ts +236 -236
  85. package/src/auth/safe-callback.ts +199 -199
  86. package/src/auth/session-cookie-clear.ts +30 -30
  87. package/src/auth/session-reset-link.ts +159 -159
  88. package/src/auth/session-response-sanitizer.ts +151 -151
  89. package/src/auth/types/auth-types.ts +555 -555
  90. package/src/auth/types/index.ts +7 -7
  91. package/src/auth/unauthenticated-routes.ts +2 -2
  92. package/src/auth/utils/idp-client.ts +364 -364
  93. package/src/auth/utils/index.ts +6 -6
  94. package/src/auth/utils/token-utils.ts +243 -243
  95. package/src/client/AuthContext.tsx +192 -192
  96. package/src/client/better-auth-client.ts +70 -70
  97. package/src/client/fetch-with-auth.ts +47 -47
  98. package/src/client/fetchWithSession.ts +21 -21
  99. package/src/client/index.ts +13 -13
  100. package/src/client/useAnonSession.ts +131 -131
  101. package/src/components/SessionSync.tsx +138 -138
  102. package/src/components/SignalRHealthCheck.tsx +131 -131
  103. package/src/components/account/MobileNavDrawer.tsx +305 -305
  104. package/src/components/account/UserAvatarMenu.tsx +277 -277
  105. package/src/components/account/index.ts +13 -13
  106. package/src/components/admin/AlertSettingsTab.tsx +728 -728
  107. package/src/components/admin/AnalyticsTab.tsx +703 -703
  108. package/src/components/admin/DataBrowserTab.tsx +508 -508
  109. package/src/components/admin/LoggingSettingsTab.tsx +665 -665
  110. package/src/components/admin/SessionsTab.tsx +421 -421
  111. package/src/components/admin/StatsTab.tsx +379 -379
  112. package/src/components/admin/VibeAdminContext.tsx +87 -87
  113. package/src/components/admin/VibeAdminLayout.tsx +187 -187
  114. package/src/components/admin/index.ts +59 -59
  115. package/src/components/auth/CredentialSignInForm.tsx +240 -240
  116. package/src/components/auth/FederatedAuthSection.tsx +129 -129
  117. package/src/components/auth/ModeAwareLoginPage.tsx +146 -146
  118. package/src/components/auth/ModeAwareSignupPage.tsx +200 -200
  119. package/src/components/auth/PasswordResetBridge.tsx +30 -30
  120. package/src/components/auth/TraditionalAuthSection.tsx +103 -103
  121. package/src/components/recovery/CompleteStep.tsx +36 -36
  122. package/src/components/recovery/InitiateRecoveryStep.tsx +68 -68
  123. package/src/components/recovery/RequestedStep.tsx +30 -30
  124. package/src/components/recovery/SelectMethodStep.tsx +73 -73
  125. package/src/components/recovery/SetPasswordStep.tsx +118 -118
  126. package/src/components/recovery/VerifyCodeStep.tsx +90 -90
  127. package/src/components/reserved/ReservedRecoveryWarning.tsx +160 -160
  128. package/src/components/reserved/ReservedStatusBox.tsx +118 -118
  129. package/src/components/ui/BetaBadge.tsx +58 -58
  130. package/src/components/ui/Footer.tsx +93 -93
  131. package/src/config/env.ts +55 -55
  132. package/src/config/logger.ts +62 -62
  133. package/src/config/logging-config.ts +82 -82
  134. package/src/config/unauthenticated-routes.ts +19 -19
  135. package/src/config/vibe-log-transport.ts +264 -264
  136. package/src/edge/internal-api-url.ts +65 -65
  137. package/src/edge/middleware.ts +42 -42
  138. package/src/hooks/useAuth.ts +117 -117
  139. package/src/hooks/useAuthSettings.ts +109 -109
  140. package/src/hooks/useAvailableProviders.ts +143 -143
  141. package/src/hooks/usePasswordValidation.ts +127 -127
  142. package/src/hooks/useProfile.ts +82 -82
  143. package/src/hooks/usePublicAuthSettings.ts +171 -171
  144. package/src/hooks/useSessionExpiration.ts +101 -101
  145. package/src/hooks/useViabilitySession.ts +337 -337
  146. package/src/index.ts +64 -64
  147. package/src/lib/anon-session.ts +213 -213
  148. package/src/lib/api-handler.ts +643 -643
  149. package/src/lib/app-slug.ts +228 -228
  150. package/src/lib/authorize-resume.ts +172 -172
  151. package/src/lib/bearer-fetch.ts +31 -31
  152. package/src/lib/client-key.ts +88 -88
  153. package/src/lib/code-step-copy.ts +83 -83
  154. package/src/lib/credential-sign-in-client.ts +101 -101
  155. package/src/lib/demo-mode.ts +18 -18
  156. package/src/lib/device-id.ts +67 -67
  157. package/src/lib/ensure-fresh-access-token.ts +509 -509
  158. package/src/lib/geolocation.ts +265 -265
  159. package/src/lib/idp-client-config.ts +1005 -1005
  160. package/src/lib/idp-fetch.ts +169 -169
  161. package/src/lib/idp-url-guard.ts +37 -37
  162. package/src/lib/internal-api.ts +171 -171
  163. package/src/lib/jwt-decode-client.ts +45 -45
  164. package/src/lib/jwt-decode.ts +82 -82
  165. package/src/lib/lock-window.ts +49 -49
  166. package/src/lib/login-error-message.ts +41 -41
  167. package/src/lib/never-in-response.ts +64 -64
  168. package/src/lib/nextauth-secret.ts +168 -168
  169. package/src/lib/onboarding-proxy.ts +57 -57
  170. package/src/lib/password-rules.ts +50 -50
  171. package/src/lib/rate-limit-service.ts +9 -9
  172. package/src/lib/redis.ts +105 -105
  173. package/src/lib/refresh-token-validator.ts +64 -64
  174. package/src/lib/roles.ts +177 -177
  175. package/src/lib/safe-error-response.ts +149 -149
  176. package/src/lib/secret-validation.ts +8 -8
  177. package/src/lib/session-cookie-shape.ts +185 -185
  178. package/src/lib/session-store.ts +1092 -1092
  179. package/src/lib/session.ts +34 -34
  180. package/src/lib/single-flight.ts +64 -64
  181. package/src/lib/site-logger.ts +245 -245
  182. package/src/lib/standardized-client-api.ts +901 -901
  183. package/src/lib/startup-init.ts +276 -276
  184. package/src/lib/test-aware-get-token.ts +41 -41
  185. package/src/lib/token-expiry.ts +40 -40
  186. package/src/lib/token-lifecycle.ts +546 -546
  187. package/src/lib/token-log-id.ts +69 -69
  188. package/src/lib/twofa-rejection.ts +91 -91
  189. package/src/lib/types/api-responses.ts +336 -336
  190. package/src/lib/user-agent-parser.ts +252 -252
  191. package/src/logging/api/admin-analytics.ts +51 -51
  192. package/src/logging/api/audit-log.ts +53 -53
  193. package/src/logging/components/AdminAnalyticsLayout.tsx +49 -49
  194. package/src/logging/components/AuditLogViewer.tsx +125 -125
  195. package/src/logging/components/ErrorMetricsCard.tsx +98 -98
  196. package/src/logging/components/HealthMetricsCard.tsx +70 -70
  197. package/src/logging/hooks/useAdminAnalytics.ts +22 -22
  198. package/src/logging/hooks/useAuditLog.ts +24 -24
  199. package/src/logging/hooks/useErrorMetrics.ts +40 -40
  200. package/src/logging/hooks/useHealthMetrics.ts +44 -44
  201. package/src/logging/index.ts +18 -18
  202. package/src/logging/types/analytics.ts +81 -81
  203. package/src/logging/types/audit.ts +31 -31
  204. package/src/logging/types/index.ts +3 -3
  205. package/src/middleware/create-middleware.ts +1141 -1141
  206. package/src/middleware/rbac-check.ts +279 -279
  207. package/src/middleware/twofa-presets.ts +224 -224
  208. package/src/models/DecodedAccessToken.ts +16 -16
  209. package/src/models/SessionModel.ts +331 -331
  210. package/src/pages/admin-login/page.tsx +111 -111
  211. package/src/pages/admin-page-permissions/PagePermissionsAdminPage.tsx +527 -527
  212. package/src/pages/admin-page-permissions/index.ts +7 -7
  213. package/src/pages/admin-roles/RolesAdminPage.tsx +533 -533
  214. package/src/pages/admin-roles/index.ts +9 -9
  215. package/src/pages/admin-roles/modals.tsx +469 -469
  216. package/src/pages/client-admin/ClientSiteAdminPage.tsx +382 -382
  217. package/src/pages/client-admin/index.ts +33 -33
  218. package/src/pages/coming-soon/page.tsx +83 -83
  219. package/src/pages/login/page.tsx +338 -338
  220. package/src/pages/onboarding/index.ts +11 -11
  221. package/src/pages/onboarding/page.tsx +212 -212
  222. package/src/pages/profile/EnhancedProfilePage.tsx +514 -514
  223. package/src/pages/profile/index.ts +9 -9
  224. package/src/pages/profile/page.tsx +166 -166
  225. package/src/pages/recovery/page.tsx +267 -267
  226. package/src/pages/roles/MyRolesPage.tsx +211 -211
  227. package/src/pages/roles/components.tsx +294 -294
  228. package/src/pages/roles/index.ts +17 -17
  229. package/src/pages/security/EnhancedSecurityPage.tsx +660 -660
  230. package/src/pages/security/index.ts +12 -12
  231. package/src/pages/security/page.tsx +507 -507
  232. package/src/pages/service-unavailable/page.tsx +63 -63
  233. package/src/pages/settings/EnhancedSettingsPage.tsx +642 -642
  234. package/src/pages/settings/index.ts +9 -9
  235. package/src/pages/settings/page.tsx +47 -47
  236. package/src/pages/showcase/ShowcasePage.tsx +532 -532
  237. package/src/pages/showcase/index.ts +13 -13
  238. package/src/pages/test-env/EmergencyLogoutPage.tsx +192 -192
  239. package/src/pages/test-env/JwtInspectPage.tsx +449 -449
  240. package/src/pages/test-env/RefreshTokenPage.tsx +178 -178
  241. package/src/pages/test-env/TestEnvPage.tsx +130 -130
  242. package/src/pages/test-env/guarded.tsx +81 -81
  243. package/src/pages/test-env/index.ts +77 -77
  244. package/src/pages/test-env/redact-credentials.ts +96 -96
  245. package/src/pages/verify-code/page.tsx +746 -746
  246. package/src/routes/account/index.ts +31 -31
  247. package/src/routes/account/masked-info.ts +36 -36
  248. package/src/routes/account/send-code.ts +39 -39
  249. package/src/routes/account/update-phone.ts +13 -13
  250. package/src/routes/account/verify-email.ts +40 -40
  251. package/src/routes/account/verify-sms.ts +40 -40
  252. package/src/routes/auth/credential-login-email-code.ts +6 -6
  253. package/src/routes/auth/credential-login.ts +6 -6
  254. package/src/routes/auth/index.ts +22 -22
  255. package/src/routes/auth/logout.ts +111 -111
  256. package/src/routes/auth/nextauth.ts +45 -45
  257. package/src/routes/auth/passkey-signin.ts +13 -13
  258. package/src/routes/auth/refresh.ts +77 -77
  259. package/src/routes/auth/session-reset.ts +37 -37
  260. package/src/routes/auth/session.ts +261 -261
  261. package/src/routes/auth/settings.ts +181 -181
  262. package/src/routes/auth/viability.ts +519 -519
  263. package/src/routes/index.ts +17 -17
  264. package/src/routes/mfa/backup-code-verify.ts +1 -1
  265. package/src/routes/mfa/backup-codes.ts +10 -10
  266. package/src/routes/mfa/methods.ts +22 -22
  267. package/src/routes/mfa/totp-confirm.ts +10 -10
  268. package/src/routes/mfa/totp-enroll.ts +10 -10
  269. package/src/routes/mfa/totp-verify.ts +14 -14
  270. package/src/routes/onboarding/index.ts +15 -15
  271. package/src/routes/onboarding/send-email-code.ts +2 -2
  272. package/src/routes/onboarding/set-password.ts +2 -2
  273. package/src/routes/onboarding/verify-code.ts +2 -2
  274. package/src/routes/onboarding/verify-welcome-email.ts +2 -2
  275. package/src/routes/passkeys/list.ts +10 -10
  276. package/src/routes/passkeys/manage.ts +23 -23
  277. package/src/routes/passkeys/register-options.ts +10 -10
  278. package/src/routes/passkeys/register-verify.ts +10 -10
  279. package/src/routes/session/index.ts +7 -7
  280. package/src/routes/session/refresh-viability.ts +17 -17
  281. package/src/server/auth-guard.ts +203 -203
  282. package/src/server/auth.ts +486 -486
  283. package/src/server/decode-session.ts +309 -309
  284. package/src/server/slim-middleware.ts +117 -117
  285. package/src/server/verified-session-token.ts +103 -103
  286. package/src/server/with-auth.ts +93 -93
  287. package/src/services/signalrActivityService.ts +258 -258
  288. package/src/stores/authStore.ts +1900 -1900
  289. package/src/templates/instrumentation.ts +60 -60
  290. package/src/theme/ThemeProvider.tsx +39 -39
  291. package/src/theme/default.ts +33 -33
  292. package/src/theme/index.ts +31 -31
  293. package/src/theme/types.ts +69 -69
  294. package/src/theme/useTheme.ts +57 -57
  295. package/src/theme/utils.ts +40 -40
  296. package/src/types/api.ts +12 -12
  297. package/src/types/auth.d.ts +23 -23
  298. package/src/types/auth.ts +48 -48
  299. package/src/types/logging.ts +11 -11
  300. package/src/types/recovery.ts +54 -54
  301. package/src/utils/api.ts +401 -401
  302. package/src/utils/circuitBreaker.ts +40 -40
  303. package/src/utils/error-message.ts +108 -108
  304. package/src/utils/layout/reservedSpace.ts +124 -124
  305. package/src/utils/logout.ts +30 -30
  306. package/src/vibe/client.ts +878 -877
  307. package/src/vibe/enterprise-auth.ts +208 -208
  308. package/src/vibe/errors.ts +209 -209
  309. package/src/vibe/filters.ts +52 -0
  310. package/src/vibe/generic.ts +340 -339
  311. package/src/vibe/hooks/index.ts +367 -367
  312. package/src/vibe/index.ts +133 -133
  313. package/src/vibe/sessions.ts +925 -899
  314. package/src/vibe/types.ts +577 -577
package/POST-INSTALL.md CHANGED
@@ -1,408 +1,408 @@
1
- # Post-Install Debt
2
-
3
- **A running tab of everything a consuming site must do BY HAND after `npm install @payez/next-mvp`.**
4
-
5
- This file exists because "works out of the box" is the product claim and it is currently
6
- false. Every row below is work the package pushed onto the consumer. The tab is the backlog:
7
- the goal is to empty it, not to document it more thoroughly.
8
-
9
- Started 2026-08-26 from the UMI Browser (`umi_browser_website`, client 16) integration, where
10
- every single row was hit in one sitting. UMI is the honest measure of the out-of-box
11
- experience because nobody had pre-wired it — unlike idealresume.online and idealvibe.online,
12
- which were built alongside the package and quietly absorbed each of these as they appeared.
13
-
14
- **Add a row the moment you hand-write something the package could have shipped.** A step you
15
- only discover by hitting it is the same defect class this codebase keeps paying for: present,
16
- plausible and inert.
17
-
18
- ---
19
-
20
- ## Scoring
21
-
22
- - **P1** — site is broken or insecure until done, and nothing tells you
23
- - **P2** — site is degraded or wrong until done
24
- - **P3** — friction; you get there, but slower than you should
25
-
26
- **Fix class:**
27
- - `auto` — the package could just do this, no consumer action at all
28
- - `codemod` — a generator / `npx @payez/next-mvp init` could write it
29
- - `doc` — genuinely a consumer choice; it only needs to be findable
30
-
31
- ---
32
-
33
- ## The tab
34
-
35
- | # | What you must hand-write | P | Fix class | Status |
36
- |---|---|---|---|---|
37
- | 1 | 16 CSS variables in `globals.css` | P1 | auto | open |
38
- | 2 | `/api/auth/[...nextauth]` route file | P1 | codemod | open |
39
- | 3 | `/api/auth/settings` route file | P1 | codemod | open |
40
- | 4 | `/login` page mounting `ModeAwareLoginPage` | P1 | codemod | open |
41
- | 5 | `instrumentation.ts` + the node split | P1 | codemod | open |
42
- | 6 | `next.config.js` transpile + tracing keys | P1 | doc | open |
43
- | 7 | `--primary` vs `--color-primary` collision | P2 | auto | open |
44
- | 8 | `/test-env/*` mounts + `ENABLE_TEST_ROUTES` | P2 | doc | open |
45
- | 9 | Local user record (route + lib + hook) | P2 | codemod | open |
46
- | 10 | `server-auth.ts` wrapper | P3 | auto | open |
47
- | 11 | `REDIS_URL` and the required env set | P3 | doc | open |
48
- | 12 | `providers.tsx` provider nesting | P3 | codemod | open |
49
- | 13 | `/api/internal/session-data` — page ships, endpoint does not | P1 | auto | open |
50
- | 14 | **`routes/auth/nextauth` is a trap — do not mount it** | P1 | auto | open |
51
- | 15 | `id_token` dropped by a module-scoped ALS | P1 | auto | **CLOSED 4.3.4** |
52
- | 16 | Tenant-side: `VIBE_COLLECTION` is doing double duty | P2 | doc | open |
53
- | 17 | Tenant-side: vibe-client 0.2.3 sends the wrong filter shape | P2 | — | open (not this package) |
54
-
55
- **Nothing is closed yet. There is no `postinstall` script and no `init` command.**
56
-
57
- ---
58
-
59
- ## 1. Sixteen CSS variables, and `ThemeProvider` emits none of them — P1
60
-
61
- The worst row on the tab, because it is invisible: the site renders, it just looks wrong, and
62
- the fallbacks make it look *deliberately* wrong rather than unstyled.
63
-
64
- Measured 2026-08-26 against installed `dist`:
65
-
66
- ```
67
- components/ and pages/ READ 16 CSS variables
68
- theme/ThemeProvider WRITES 0
69
- ```
70
-
71
- The sixteen:
72
-
73
- ```
74
- --background --bg-card --bg-default --border --border-default --card
75
- --color-error --color-primary --foreground --input --muted --muted-foreground
76
- --primary --text-muted --text-primary --text-secondary
77
- ```
78
-
79
- `ThemeProvider` takes a theme object and emits **nothing to CSS**. So all three sites
80
- independently hand-authored the same variable block in their own `globals.css` — three
81
- copies, already drifting.
82
-
83
- **Fix:** `ThemeProvider` maps the theme object onto these sixteen and sets them. That is the
84
- entire job it appears to exist to do. Until then a site that installs the package and uses a
85
- component gets silent fallbacks.
86
-
87
- ## 2–4. The package exports routes and pages; you mount every one — P1
88
-
89
- `@payez/next-mvp` exports working handlers and pages. It cannot create files in your `app/`
90
- directory, so **every one of them is a 404 until you write a re-export by hand** — and
91
- nothing tells you which exist.
92
-
93
- UMI had **zero of them mounted** and looked like a working site. The login page rendered a
94
- demo gate against hardcoded strings; `/api/auth/*` did not exist at all, so the sign-in
95
- screen's request for what the client offers 404'd and it drew nothing.
96
-
97
- ```
98
- app/api/auth/[...nextauth]/route.ts export { GET, POST } from '@payez/next-mvp/routes/auth/nextauth';
99
- app/api/auth/settings/route.ts export { GET } from '@payez/next-mvp/routes/auth/settings';
100
- app/login/page.tsx ModeAwareLoginPage + your branding
101
- ```
102
-
103
- **Fix:** `npx @payez/next-mvp init` writes these. Failing that, one page in the README that
104
- lists every mountable export — there is currently no such list anywhere.
105
-
106
- ## 5. `instrumentation.ts`, and the part the template gets wrong — P1
107
-
108
- A template ships at `src/templates/instrumentation.ts` — the only template in the package —
109
- but you still copy it in by hand, and it is **not** the shape that works.
110
-
111
- Two things it does not tell you:
112
-
113
- - **`instrumentation.ts` is compiled for BOTH the node and edge runtimes.** `NEXT_RUNTIME` is
114
- a *runtime* guard; imports resolve at *build* time, so node-only dependencies still get
115
- traced into the edge bundle and the build fails. The working shape is a thin
116
- `instrumentation.ts` that dynamic-imports `instrumentation-node.ts`.
117
- - **`await import()` does not stay dynamic under `module: CommonJS`.** TypeScript emits
118
- `Promise.resolve().then(() => require(...))`, which is statically traced exactly like a
119
- top-level import. Cost us three failed fixes.
120
-
121
- Also: the template's catch block used to log *"Will retry initialization on first request"*
122
- and continue. It does not retry. A site whose IDP handshake failed would serve requests
123
- with no auth config and say so nowhere. Now `process.exit(1)`.
124
-
125
- **Fix:** ship the two-file split as the template, and put both facts above in it as comments.
126
-
127
- ## 6. `next.config.js` — two keys, and one that must never come back — P1
128
-
129
- ```js
130
- transpilePackages: ['@payez/next-mvp'],
131
- outputFileTracingRoot: path.join(__dirname),
132
- ```
133
-
134
- Mentioned in two docs, in passing, not as a required step.
135
-
136
- **Do not add a `webpack:` key to work around a bundler warning.** It disables Turbopack and
137
- turns a cosmetic warning into a hard `UnhandledSchemeError`. A note now sits in UMI's
138
- `next.config.js` saying so.
139
-
140
- **Related, and the reason a day was lost:** `next dev` uses **webpack** by default in Next
141
- 15.5. Turbopack is opt-in via `--turbopack`. Assuming otherwise sent three bundler fixes at
142
- the wrong bundler.
143
-
144
- ## 7. `--primary` and `--color-primary` are two names for one value — P2
145
-
146
- Components read both. Nothing defines the relationship. `TraditionalAuthSection`'s submit
147
- button hardcodes `var(--color-primary, #10b981)` — so a site that defines `--primary` and not
148
- `--color-primary` gets a **green** button no matter what its brand colour is. UMI is
149
- `#FF6B5B`; it defines both, as a workaround.
150
-
151
- This is the CLAUDE.md rule the package itself breaks: **one value gets one name.** Pick
152
- `--color-primary`, and drop the hardcoded emerald — a fallback that silently substitutes
153
- another product's brand colour is worse than no fallback.
154
-
155
- ## 8. `/test-env/*` — four pages, a flag, and a trap — P2
156
-
157
- Four diagnostics ship (`TestEnvPage`, `JwtInspectPage`, `RefreshTokenPage`,
158
- `EmergencyLogoutPage`). Documented in **zero** docs. idealvibe mounts five routes; UMI mounted
159
- none and every URL 404'd.
160
-
161
- They are exported **only** as guarded server components — the raw client pages are not
162
- exported. The guard reads `ENABLE_TEST_ROUTES`, exact-match `'true'`, fail-closed, and returns
163
- a real 404 rather than a 403 so probing cannot confirm the route exists. All of that is
164
- correct and should not be softened.
165
-
166
- **The trap:** putting `'use client'` on the page file that re-exports the guard. Non-`NEXT_PUBLIC_`
167
- variables do not exist in the browser, so the guard reads `undefined` and denies **forever** —
168
- a gate that greps as present and protects nothing. Hit on 2026-08-26.
169
-
170
- These pages show a **live session token with a copy button**. `ENABLE_TEST_ROUTES=true` belongs
171
- in `.env.development` and nowhere else. Verified both ways on UMI: flag off, all four 404 while
172
- `/login` stays 200.
173
-
174
- **Fix:** document the four exports, the server-component requirement, and the flag — together,
175
- in one place, with the `'use client'` trap called out.
176
-
177
- ## 9. The local user record — three files, written three times — P2
178
-
179
- The IDP owns identity; the tenant app needs its own row so application data can join to a user.
180
- The package ships **none** of it, so all three sites wrote the same three files:
181
-
182
- ```
183
- app/api/auth/record-login/route.ts upsert on sign-in
184
- app/lib/vibe-user.ts find-or-create by idp_user_id
185
- app/hooks/useLoginRecorder.ts fire once per session
186
- ```
187
-
188
- Two things every consumer must get right and has no way to know:
189
-
190
- - **Identity is `idp_user_id`, never email.** Email changes and gets reused; matching on it
191
- silently merges two people.
192
- - **The effect needs a ref guard.** Without one, a re-render or Fast Refresh posts again —
193
- harmless for the row, but it inflates `login_count`, which is then wrong in a way nobody
194
- would think to question.
195
-
196
- The app-specific parts (tiers, credits, milestones — idealresume's runs to 1,567 lines) are
197
- genuinely the site's. The find-or-create core is not.
198
-
199
- ## 10. `server-auth.ts` — a wrapper over one export — P3
200
-
201
- Every site writes the same ~60-line wrapper over `getFreshIdpToken` to assemble the refresh
202
- config from env and normalise the result. Package-side, this is a default config reader.
203
-
204
- ## 11. Env vars — no single list, no startup validation of the set — P3
205
-
206
- `IDP_URL`, `CLIENT_ID`, `REFRESH_ENDPOINT`, `REDIS_URL`, `ENABLE_TEST_ROUTES`, plus the broker
207
- secret. `REDIS_URL` was missing from UMI and surfaced as an unrelated-looking failure.
208
-
209
- **`CLIENT_ID` is the only spelling.** Until 4.2.5 the package accepted `CLIENT_ID`,
210
- `NEXT_PUBLIC_CLIENT_ID` and `NEXT_CLIENT_ID` for one value; that cost a live defect. All six
211
- read sites are collapsed and `idp-client-config.ts` carries the banner. Do not reintroduce a
212
- second name here.
213
-
214
- **Fix:** ship `.env.example` with every variable, what reads it, and whether it is required.
215
-
216
- ## 12. `providers.tsx` — nesting order matters and is unstated — P3
217
-
218
- ```
219
- AuthProvider → LoginRecorderProvider → ThemeProvider
220
- ```
221
-
222
- `ModeAwareLoginPage` reads `useAuthConfig()`, so without `AuthProvider` the sign-in screen
223
- renders **no controls at all** — no Google button, no email form — even while
224
- `/api/auth/settings` correctly reports `enabledProviders: ["google"]`. It fails as a blank
225
- card, which reads as a styling problem.
226
-
227
- **Do not set `mode` in a site's auth config.** What sign-in offers comes from the IDP via
228
- `/api/auth/settings` (4.3.0+). A site hardcoding its own auth mode is how a login screen and
229
- its own IDP end up disagreeing.
230
-
231
- ## 13. The Session Inspector fetches an endpoint the package does not ship — P1
232
-
233
- `JwtInspectPage` fetches `/api/internal/session-data`. **No such handler exists in the
234
- package and none is exported.** Line 39 of the page says the quiet part — "reads them from
235
- the *consuming app's* `/api/internal/session-data` route" — which is a defensible design
236
- call (a session record's shape is the app's business) documented in the one place no
237
- consumer will ever read: the source of a page they did not write.
238
-
239
- **It fails invisibly**, which is the real defect. The page does:
240
-
241
- ```ts
242
- fetch('/api/internal/session-data').then(r => r.ok ? r.json() : null).catch(() => {})
243
- ```
244
-
245
- A 404 and a session with no data render **identically**: every field `N/A`, and
246
- `Error: N/A` underneath, which reads as "no error". The inspector looked like it was
247
- reporting a broken session. It was reporting a missing route. Found only by watching the
248
- network tab.
249
-
250
- UMI now owns `app/api/internal/session-data/route.ts`. Two things any implementation must
251
- get right:
252
-
253
- - **The 16 field names are a contract** (`JwtInspectPage` reads `ss.userId`, `ss.roles`,
254
- `ss.idpAccessToken`, …). Rename one and that row silently goes `N/A` forever.
255
- - **It returns the live IDP access AND refresh token.** The package's own `better-auth.ts`
256
- names this exact endpoint as the leak path it refuses to put `id_token` behind. UMI gates
257
- it on `ENABLE_TEST_ROUTES` — same fail-closed exact-match, read at request time, 404 not
258
- 403. Verified both ways.
259
-
260
- **Fix:** either ship the route (it is mechanical — `getSession` + `getSession` from
261
- `lib/session-store`, both already exported), or make the page say `endpoint missing` instead
262
- of swallowing the 404. Preferably both.
263
-
264
- ## 14. `routes/auth/nextauth` is a trap. Do not mount it. — P1
265
-
266
- **This one cost the most and looked the most correct.** The package ships
267
- `routes/auth/nextauth`, its own docblock calls it "Ready-to-Use", and its usage example is
268
- the obvious one-liner:
269
-
270
- ```ts
271
- export { GET, POST } from '@payez/next-mvp/routes/auth/nextauth'; // WRONG
272
- ```
273
-
274
- It signs users in. It never provisions them.
275
-
276
- ```
277
- routes/auth/nextauth GET -> toNextJsHandler(auth) — signs in. Stops.
278
- createAuthGetHandler GET -> ba.GET + IDP token exchange — signs in AND provisions.
279
- ```
280
-
281
- Only `createAuthGetHandler` runs `exchangeOAuthForIdpTokens` after the OAuth callback. With
282
- the re-export, Better Auth creates a **perfectly convincing session** — cookie, user id,
283
- email, Google avatar — and the Redis record holding the IDP access token, refresh token and
284
- roles is **never written**.
285
-
286
- The damage surfaces nowhere near the cause:
287
-
288
- - `/test-env/jwt-inspect` → `NO_REDIS_SESSION`, no tokens, no roles
289
- - anything calling `getFreshIdpToken` → `NO_SESSION` **for a user who is visibly signed in**
290
- - `record-login` → 401, so the app never creates its local user row
291
-
292
- Correct wiring, and what idealvibe.online and idealresume.online **both** hand-write:
293
-
294
- ```ts
295
- export const GET = createAuthGetHandler('/login'); // your login path
296
- export async function POST(request: Request) {
297
- const ba = await getBetterAuthHandler();
298
- return ba!.POST(request);
299
- }
300
- ```
301
-
302
- Neither working site uses the re-export. That is not boilerplate they failed to factor out —
303
- it is the only wiring that works.
304
-
305
- **This row is also a correction to row 2 of this tab.** Row 2's advice — prefer the package
306
- re-export over copying another site's file — is right for `settings` and wrong here, and I
307
- followed it into this exact bug on UMI. A codemod that generates the "obvious" re-export
308
- would ship this defect to every new consumer.
309
-
310
- **Fix:** `routes/auth/nextauth` should either export the exchanging handler or refuse to
311
- exist. A "Ready-to-Use" export that yields a half-provisioned session is worse than no
312
- export, because it looks like the sanctioned path.
313
-
314
- ## 15. `id_token` was dropped because the ALS was module-scoped — P1, CLOSED in 4.3.4
315
-
316
- The most expensive one, and the package's own fault end to end.
317
-
318
- `state` (the Better Auth instance) was moved to `globalThis` by 181814 **because this module
319
- loads more than once per process** — instrumentation and the auth route are separate chunks
320
- with separate registries. `idTokenScope`, the AsyncLocalStorage that carries the provider's
321
- raw `id_token` from `getUserInfo` to the IDP exchange, was left in module scope.
322
-
323
- So the two copies shared the instance and **not** the scope:
324
-
325
- ```
326
- copy A builds the instance -> the getUserInfo closure captures A's idTokenScope
327
- copy B serves the callback -> createAuthGetHandler runs .run({}) on B's
328
- getUserInfo fires, reads A -> getStore() undefined, `if (scope)` skips SILENTLY
329
- the exchange reads B -> empty, so id_token is omitted from the body
330
- ```
331
-
332
- Sign-in succeeds at every visible step — cookie, user id, email, avatar. The IDP then
333
- answers `401 UNVERIFIED_ASSERTION`, and its own log names the reason:
334
- `reason="id_token missing"` (idp.payez.net, correlationId 0HNO2755RESR1, 2026-08-26).
335
-
336
- **The consequence is a security control that shipped as a no-op.** 472fb75c added the raw
337
- `id_token` because `/api/ExternalAuth/oauth-callback` would mint an IDP access+refresh pair
338
- for any *asserted* email, on a public LoadBalancer — the only verifiable field in that body
339
- is the provider's signed token. Wherever this module double-loads, that fix never arrived.
340
-
341
- Fixed in 4.3.4: `idTokenScope` now lives on `globalThis` under the same pattern as `state`,
342
- and the silent `if (scope)` skip warns and names its two causes.
343
-
344
- **The lesson generalises past this bug: shared instance + per-copy request scope is the worst
345
- combination.** Everything looks wired, and the one field that proves identity is dropped. If
346
- any other module-scoped singleton in this package participates in a request, audit it the
347
- same way.
348
-
349
- ## 16. Tenant-side: one env var, two unrelated jobs — P2
350
-
351
- Not this package's bug, but every consumer that has a marketing form will hit it.
352
-
353
- `createVibeClient` resolves the collection as
354
- `config.defaultCollection || process.env.VIBE_COLLECTION || 'vibe_app'`. The library default
355
- is already correct. UMI set `VIBE_COLLECTION=payez_contact_list` for its **waitlist form**,
356
- and that variable is global to the client — so it silently retargeted every collection call
357
- in the app, including user identity. Sign-in queried the waitlist's collection for a `users`
358
- table that does not live there.
359
-
360
- Same rule as `CLIENT_ID`, from the other direction: there, one value had three names; here,
361
- one name carries two values. Pin the collection at the call site.
362
-
363
- ## 17. Tenant-side: vibe-client 0.2.3 sends a filter shape the API rejects — P2
364
-
365
- `list({ filter })` serialises through `convertFiltersToVibeFormat`, which returns an **array**
366
- `[{ field, operator, value }]`. `PayEz.Vibe.Public.Api` binds `filter` to a single
367
- `VibeFilterNode` **object** (`VibeQueryModels.cs:63`), so the request fails to deserialise
368
- before the query builder ever runs:
369
-
370
- ```
371
- 400 VALIDATION_ERROR $.filter
372
- "The JSON value could not be converted to ...QueryBuilder.VibeFilterNode"
373
- ```
374
-
375
- The API is not being arbitrary — `VibeQueryBuilder.cs:355` documents this exact query as
376
- supported (`{ "idp_user_id": { "eq": 22 } }`), and :94 accepts the flat
377
- `{ field, operator, value }` form too. The array wrapper is the whole defect, and the API
378
- change is deliberate.
379
-
380
- **This affects every filtered query from 0.2.3, on all three sites.** UMI was only the first
381
- to make one — measured in the IDP proxy logs, UMI's client was the sole source of `$.filter`
382
- 400s in 24h while idealresume queried cleanly, because idealresume's calls in that window did
383
- not filter.
384
-
385
- UMI hand-rolls the single lookup against the proxy with the correct node, reusing the
386
- library's `resolveConfig()`. That is a workaround with a cost: it duplicates the HMAC recipe
387
- (`timestamp|method|endpoint`, key base64-decoded) because `generateHmacSignature` is not
388
- exported, so a signing change in the library breaks that call with a 401. **The real fix is a
389
- vibe-client release that emits a `VibeFilterNode`**; its source is not in this workspace.
390
-
391
- ---
392
-
393
- ## What the tab says about the package
394
-
395
- Fourteen rows; **eleven are `auto` or `codemod`** — the package could do them, or generate
396
- them, with no judgement call from the consumer. Only three are real consumer choices.
397
-
398
- The three P1 route/page rows share one root cause: **the package can export a thing but cannot
399
- mount it**, and nothing enumerates what is mountable. That is one fix, not three — a list, or
400
- an `init` command that writes the re-exports.
401
-
402
- Eight of the fourteen appear in **no doc at all**. `QUICK_START.md` is a stub with a single
403
- heading. Row 14 is worse than undocumented — the wrong path is the one with the usage example.
404
-
405
- The recurring shape, which is the same one this codebase keeps paying for: **things present,
406
- plausible and inert.** A `ThemeProvider` that provides no theme. A template whose comment
407
- promises a retry that never happens. A guard that greps as a gate and denies everything. An
408
- exported page nothing mounts. None of them announce themselves; all of them look done.
1
+ # Post-Install Debt
2
+
3
+ **A running tab of everything a consuming site must do BY HAND after `npm install @payez/next-mvp`.**
4
+
5
+ This file exists because "works out of the box" is the product claim and it is currently
6
+ false. Every row below is work the package pushed onto the consumer. The tab is the backlog:
7
+ the goal is to empty it, not to document it more thoroughly.
8
+
9
+ Started 2026-08-26 from the UMI Browser (`umi_browser_website`, client 16) integration, where
10
+ every single row was hit in one sitting. UMI is the honest measure of the out-of-box
11
+ experience because nobody had pre-wired it — unlike idealresume.online and idealvibe.online,
12
+ which were built alongside the package and quietly absorbed each of these as they appeared.
13
+
14
+ **Add a row the moment you hand-write something the package could have shipped.** A step you
15
+ only discover by hitting it is the same defect class this codebase keeps paying for: present,
16
+ plausible and inert.
17
+
18
+ ---
19
+
20
+ ## Scoring
21
+
22
+ - **P1** — site is broken or insecure until done, and nothing tells you
23
+ - **P2** — site is degraded or wrong until done
24
+ - **P3** — friction; you get there, but slower than you should
25
+
26
+ **Fix class:**
27
+ - `auto` — the package could just do this, no consumer action at all
28
+ - `codemod` — a generator / `npx @payez/next-mvp init` could write it
29
+ - `doc` — genuinely a consumer choice; it only needs to be findable
30
+
31
+ ---
32
+
33
+ ## The tab
34
+
35
+ | # | What you must hand-write | P | Fix class | Status |
36
+ |---|---|---|---|---|
37
+ | 1 | 16 CSS variables in `globals.css` | P1 | auto | open |
38
+ | 2 | `/api/auth/[...nextauth]` route file | P1 | codemod | open |
39
+ | 3 | `/api/auth/settings` route file | P1 | codemod | open |
40
+ | 4 | `/login` page mounting `ModeAwareLoginPage` | P1 | codemod | open |
41
+ | 5 | `instrumentation.ts` + the node split | P1 | codemod | open |
42
+ | 6 | `next.config.js` transpile + tracing keys | P1 | doc | open |
43
+ | 7 | `--primary` vs `--color-primary` collision | P2 | auto | open |
44
+ | 8 | `/test-env/*` mounts + `ENABLE_TEST_ROUTES` | P2 | doc | open |
45
+ | 9 | Local user record (route + lib + hook) | P2 | codemod | open |
46
+ | 10 | `server-auth.ts` wrapper | P3 | auto | open |
47
+ | 11 | `REDIS_URL` and the required env set | P3 | doc | open |
48
+ | 12 | `providers.tsx` provider nesting | P3 | codemod | open |
49
+ | 13 | `/api/internal/session-data` — page ships, endpoint does not | P1 | auto | open |
50
+ | 14 | **`routes/auth/nextauth` is a trap — do not mount it** | P1 | auto | open |
51
+ | 15 | `id_token` dropped by a module-scoped ALS | P1 | auto | **CLOSED 4.3.4** |
52
+ | 16 | Tenant-side: `VIBE_COLLECTION` is doing double duty | P2 | doc | open |
53
+ | 17 | Tenant-side: vibe-client 0.2.3 sends the wrong filter shape | P2 | — | open (not this package) |
54
+
55
+ **Nothing is closed yet. There is no `postinstall` script and no `init` command.**
56
+
57
+ ---
58
+
59
+ ## 1. Sixteen CSS variables, and `ThemeProvider` emits none of them — P1
60
+
61
+ The worst row on the tab, because it is invisible: the site renders, it just looks wrong, and
62
+ the fallbacks make it look *deliberately* wrong rather than unstyled.
63
+
64
+ Measured 2026-08-26 against installed `dist`:
65
+
66
+ ```
67
+ components/ and pages/ READ 16 CSS variables
68
+ theme/ThemeProvider WRITES 0
69
+ ```
70
+
71
+ The sixteen:
72
+
73
+ ```
74
+ --background --bg-card --bg-default --border --border-default --card
75
+ --color-error --color-primary --foreground --input --muted --muted-foreground
76
+ --primary --text-muted --text-primary --text-secondary
77
+ ```
78
+
79
+ `ThemeProvider` takes a theme object and emits **nothing to CSS**. So all three sites
80
+ independently hand-authored the same variable block in their own `globals.css` — three
81
+ copies, already drifting.
82
+
83
+ **Fix:** `ThemeProvider` maps the theme object onto these sixteen and sets them. That is the
84
+ entire job it appears to exist to do. Until then a site that installs the package and uses a
85
+ component gets silent fallbacks.
86
+
87
+ ## 2–4. The package exports routes and pages; you mount every one — P1
88
+
89
+ `@payez/next-mvp` exports working handlers and pages. It cannot create files in your `app/`
90
+ directory, so **every one of them is a 404 until you write a re-export by hand** — and
91
+ nothing tells you which exist.
92
+
93
+ UMI had **zero of them mounted** and looked like a working site. The login page rendered a
94
+ demo gate against hardcoded strings; `/api/auth/*` did not exist at all, so the sign-in
95
+ screen's request for what the client offers 404'd and it drew nothing.
96
+
97
+ ```
98
+ app/api/auth/[...nextauth]/route.ts export { GET, POST } from '@payez/next-mvp/routes/auth/nextauth';
99
+ app/api/auth/settings/route.ts export { GET } from '@payez/next-mvp/routes/auth/settings';
100
+ app/login/page.tsx ModeAwareLoginPage + your branding
101
+ ```
102
+
103
+ **Fix:** `npx @payez/next-mvp init` writes these. Failing that, one page in the README that
104
+ lists every mountable export — there is currently no such list anywhere.
105
+
106
+ ## 5. `instrumentation.ts`, and the part the template gets wrong — P1
107
+
108
+ A template ships at `src/templates/instrumentation.ts` — the only template in the package —
109
+ but you still copy it in by hand, and it is **not** the shape that works.
110
+
111
+ Two things it does not tell you:
112
+
113
+ - **`instrumentation.ts` is compiled for BOTH the node and edge runtimes.** `NEXT_RUNTIME` is
114
+ a *runtime* guard; imports resolve at *build* time, so node-only dependencies still get
115
+ traced into the edge bundle and the build fails. The working shape is a thin
116
+ `instrumentation.ts` that dynamic-imports `instrumentation-node.ts`.
117
+ - **`await import()` does not stay dynamic under `module: CommonJS`.** TypeScript emits
118
+ `Promise.resolve().then(() => require(...))`, which is statically traced exactly like a
119
+ top-level import. Cost us three failed fixes.
120
+
121
+ Also: the template's catch block used to log *"Will retry initialization on first request"*
122
+ and continue. It does not retry. A site whose IDP handshake failed would serve requests
123
+ with no auth config and say so nowhere. Now `process.exit(1)`.
124
+
125
+ **Fix:** ship the two-file split as the template, and put both facts above in it as comments.
126
+
127
+ ## 6. `next.config.js` — two keys, and one that must never come back — P1
128
+
129
+ ```js
130
+ transpilePackages: ['@payez/next-mvp'],
131
+ outputFileTracingRoot: path.join(__dirname),
132
+ ```
133
+
134
+ Mentioned in two docs, in passing, not as a required step.
135
+
136
+ **Do not add a `webpack:` key to work around a bundler warning.** It disables Turbopack and
137
+ turns a cosmetic warning into a hard `UnhandledSchemeError`. A note now sits in UMI's
138
+ `next.config.js` saying so.
139
+
140
+ **Related, and the reason a day was lost:** `next dev` uses **webpack** by default in Next
141
+ 15.5. Turbopack is opt-in via `--turbopack`. Assuming otherwise sent three bundler fixes at
142
+ the wrong bundler.
143
+
144
+ ## 7. `--primary` and `--color-primary` are two names for one value — P2
145
+
146
+ Components read both. Nothing defines the relationship. `TraditionalAuthSection`'s submit
147
+ button hardcodes `var(--color-primary, #10b981)` — so a site that defines `--primary` and not
148
+ `--color-primary` gets a **green** button no matter what its brand colour is. UMI is
149
+ `#FF6B5B`; it defines both, as a workaround.
150
+
151
+ This is the CLAUDE.md rule the package itself breaks: **one value gets one name.** Pick
152
+ `--color-primary`, and drop the hardcoded emerald — a fallback that silently substitutes
153
+ another product's brand colour is worse than no fallback.
154
+
155
+ ## 8. `/test-env/*` — four pages, a flag, and a trap — P2
156
+
157
+ Four diagnostics ship (`TestEnvPage`, `JwtInspectPage`, `RefreshTokenPage`,
158
+ `EmergencyLogoutPage`). Documented in **zero** docs. idealvibe mounts five routes; UMI mounted
159
+ none and every URL 404'd.
160
+
161
+ They are exported **only** as guarded server components — the raw client pages are not
162
+ exported. The guard reads `ENABLE_TEST_ROUTES`, exact-match `'true'`, fail-closed, and returns
163
+ a real 404 rather than a 403 so probing cannot confirm the route exists. All of that is
164
+ correct and should not be softened.
165
+
166
+ **The trap:** putting `'use client'` on the page file that re-exports the guard. Non-`NEXT_PUBLIC_`
167
+ variables do not exist in the browser, so the guard reads `undefined` and denies **forever** —
168
+ a gate that greps as present and protects nothing. Hit on 2026-08-26.
169
+
170
+ These pages show a **live session token with a copy button**. `ENABLE_TEST_ROUTES=true` belongs
171
+ in `.env.development` and nowhere else. Verified both ways on UMI: flag off, all four 404 while
172
+ `/login` stays 200.
173
+
174
+ **Fix:** document the four exports, the server-component requirement, and the flag — together,
175
+ in one place, with the `'use client'` trap called out.
176
+
177
+ ## 9. The local user record — three files, written three times — P2
178
+
179
+ The IDP owns identity; the tenant app needs its own row so application data can join to a user.
180
+ The package ships **none** of it, so all three sites wrote the same three files:
181
+
182
+ ```
183
+ app/api/auth/record-login/route.ts upsert on sign-in
184
+ app/lib/vibe-user.ts find-or-create by idp_user_id
185
+ app/hooks/useLoginRecorder.ts fire once per session
186
+ ```
187
+
188
+ Two things every consumer must get right and has no way to know:
189
+
190
+ - **Identity is `idp_user_id`, never email.** Email changes and gets reused; matching on it
191
+ silently merges two people.
192
+ - **The effect needs a ref guard.** Without one, a re-render or Fast Refresh posts again —
193
+ harmless for the row, but it inflates `login_count`, which is then wrong in a way nobody
194
+ would think to question.
195
+
196
+ The app-specific parts (tiers, credits, milestones — idealresume's runs to 1,567 lines) are
197
+ genuinely the site's. The find-or-create core is not.
198
+
199
+ ## 10. `server-auth.ts` — a wrapper over one export — P3
200
+
201
+ Every site writes the same ~60-line wrapper over `getFreshIdpToken` to assemble the refresh
202
+ config from env and normalise the result. Package-side, this is a default config reader.
203
+
204
+ ## 11. Env vars — no single list, no startup validation of the set — P3
205
+
206
+ `IDP_URL`, `CLIENT_ID`, `REFRESH_ENDPOINT`, `REDIS_URL`, `ENABLE_TEST_ROUTES`, plus the broker
207
+ secret. `REDIS_URL` was missing from UMI and surfaced as an unrelated-looking failure.
208
+
209
+ **`CLIENT_ID` is the only spelling.** Until 4.2.5 the package accepted `CLIENT_ID`,
210
+ `NEXT_PUBLIC_CLIENT_ID` and `NEXT_CLIENT_ID` for one value; that cost a live defect. All six
211
+ read sites are collapsed and `idp-client-config.ts` carries the banner. Do not reintroduce a
212
+ second name here.
213
+
214
+ **Fix:** ship `.env.example` with every variable, what reads it, and whether it is required.
215
+
216
+ ## 12. `providers.tsx` — nesting order matters and is unstated — P3
217
+
218
+ ```
219
+ AuthProvider → LoginRecorderProvider → ThemeProvider
220
+ ```
221
+
222
+ `ModeAwareLoginPage` reads `useAuthConfig()`, so without `AuthProvider` the sign-in screen
223
+ renders **no controls at all** — no Google button, no email form — even while
224
+ `/api/auth/settings` correctly reports `enabledProviders: ["google"]`. It fails as a blank
225
+ card, which reads as a styling problem.
226
+
227
+ **Do not set `mode` in a site's auth config.** What sign-in offers comes from the IDP via
228
+ `/api/auth/settings` (4.3.0+). A site hardcoding its own auth mode is how a login screen and
229
+ its own IDP end up disagreeing.
230
+
231
+ ## 13. The Session Inspector fetches an endpoint the package does not ship — P1
232
+
233
+ `JwtInspectPage` fetches `/api/internal/session-data`. **No such handler exists in the
234
+ package and none is exported.** Line 39 of the page says the quiet part — "reads them from
235
+ the *consuming app's* `/api/internal/session-data` route" — which is a defensible design
236
+ call (a session record's shape is the app's business) documented in the one place no
237
+ consumer will ever read: the source of a page they did not write.
238
+
239
+ **It fails invisibly**, which is the real defect. The page does:
240
+
241
+ ```ts
242
+ fetch('/api/internal/session-data').then(r => r.ok ? r.json() : null).catch(() => {})
243
+ ```
244
+
245
+ A 404 and a session with no data render **identically**: every field `N/A`, and
246
+ `Error: N/A` underneath, which reads as "no error". The inspector looked like it was
247
+ reporting a broken session. It was reporting a missing route. Found only by watching the
248
+ network tab.
249
+
250
+ UMI now owns `app/api/internal/session-data/route.ts`. Two things any implementation must
251
+ get right:
252
+
253
+ - **The 16 field names are a contract** (`JwtInspectPage` reads `ss.userId`, `ss.roles`,
254
+ `ss.idpAccessToken`, …). Rename one and that row silently goes `N/A` forever.
255
+ - **It returns the live IDP access AND refresh token.** The package's own `better-auth.ts`
256
+ names this exact endpoint as the leak path it refuses to put `id_token` behind. UMI gates
257
+ it on `ENABLE_TEST_ROUTES` — same fail-closed exact-match, read at request time, 404 not
258
+ 403. Verified both ways.
259
+
260
+ **Fix:** either ship the route (it is mechanical — `getSession` + `getSession` from
261
+ `lib/session-store`, both already exported), or make the page say `endpoint missing` instead
262
+ of swallowing the 404. Preferably both.
263
+
264
+ ## 14. `routes/auth/nextauth` is a trap. Do not mount it. — P1
265
+
266
+ **This one cost the most and looked the most correct.** The package ships
267
+ `routes/auth/nextauth`, its own docblock calls it "Ready-to-Use", and its usage example is
268
+ the obvious one-liner:
269
+
270
+ ```ts
271
+ export { GET, POST } from '@payez/next-mvp/routes/auth/nextauth'; // WRONG
272
+ ```
273
+
274
+ It signs users in. It never provisions them.
275
+
276
+ ```
277
+ routes/auth/nextauth GET -> toNextJsHandler(auth) — signs in. Stops.
278
+ createAuthGetHandler GET -> ba.GET + IDP token exchange — signs in AND provisions.
279
+ ```
280
+
281
+ Only `createAuthGetHandler` runs `exchangeOAuthForIdpTokens` after the OAuth callback. With
282
+ the re-export, Better Auth creates a **perfectly convincing session** — cookie, user id,
283
+ email, Google avatar — and the Redis record holding the IDP access token, refresh token and
284
+ roles is **never written**.
285
+
286
+ The damage surfaces nowhere near the cause:
287
+
288
+ - `/test-env/jwt-inspect` → `NO_REDIS_SESSION`, no tokens, no roles
289
+ - anything calling `getFreshIdpToken` → `NO_SESSION` **for a user who is visibly signed in**
290
+ - `record-login` → 401, so the app never creates its local user row
291
+
292
+ Correct wiring, and what idealvibe.online and idealresume.online **both** hand-write:
293
+
294
+ ```ts
295
+ export const GET = createAuthGetHandler('/login'); // your login path
296
+ export async function POST(request: Request) {
297
+ const ba = await getBetterAuthHandler();
298
+ return ba!.POST(request);
299
+ }
300
+ ```
301
+
302
+ Neither working site uses the re-export. That is not boilerplate they failed to factor out —
303
+ it is the only wiring that works.
304
+
305
+ **This row is also a correction to row 2 of this tab.** Row 2's advice — prefer the package
306
+ re-export over copying another site's file — is right for `settings` and wrong here, and I
307
+ followed it into this exact bug on UMI. A codemod that generates the "obvious" re-export
308
+ would ship this defect to every new consumer.
309
+
310
+ **Fix:** `routes/auth/nextauth` should either export the exchanging handler or refuse to
311
+ exist. A "Ready-to-Use" export that yields a half-provisioned session is worse than no
312
+ export, because it looks like the sanctioned path.
313
+
314
+ ## 15. `id_token` was dropped because the ALS was module-scoped — P1, CLOSED in 4.3.4
315
+
316
+ The most expensive one, and the package's own fault end to end.
317
+
318
+ `state` (the Better Auth instance) was moved to `globalThis` by 181814 **because this module
319
+ loads more than once per process** — instrumentation and the auth route are separate chunks
320
+ with separate registries. `idTokenScope`, the AsyncLocalStorage that carries the provider's
321
+ raw `id_token` from `getUserInfo` to the IDP exchange, was left in module scope.
322
+
323
+ So the two copies shared the instance and **not** the scope:
324
+
325
+ ```
326
+ copy A builds the instance -> the getUserInfo closure captures A's idTokenScope
327
+ copy B serves the callback -> createAuthGetHandler runs .run({}) on B's
328
+ getUserInfo fires, reads A -> getStore() undefined, `if (scope)` skips SILENTLY
329
+ the exchange reads B -> empty, so id_token is omitted from the body
330
+ ```
331
+
332
+ Sign-in succeeds at every visible step — cookie, user id, email, avatar. The IDP then
333
+ answers `401 UNVERIFIED_ASSERTION`, and its own log names the reason:
334
+ `reason="id_token missing"` (idp.payez.net, correlationId 0HNO2755RESR1, 2026-08-26).
335
+
336
+ **The consequence is a security control that shipped as a no-op.** 472fb75c added the raw
337
+ `id_token` because `/api/ExternalAuth/oauth-callback` would mint an IDP access+refresh pair
338
+ for any *asserted* email, on a public LoadBalancer — the only verifiable field in that body
339
+ is the provider's signed token. Wherever this module double-loads, that fix never arrived.
340
+
341
+ Fixed in 4.3.4: `idTokenScope` now lives on `globalThis` under the same pattern as `state`,
342
+ and the silent `if (scope)` skip warns and names its two causes.
343
+
344
+ **The lesson generalises past this bug: shared instance + per-copy request scope is the worst
345
+ combination.** Everything looks wired, and the one field that proves identity is dropped. If
346
+ any other module-scoped singleton in this package participates in a request, audit it the
347
+ same way.
348
+
349
+ ## 16. Tenant-side: one env var, two unrelated jobs — P2
350
+
351
+ Not this package's bug, but every consumer that has a marketing form will hit it.
352
+
353
+ `createVibeClient` resolves the collection as
354
+ `config.defaultCollection || process.env.VIBE_COLLECTION || 'vibe_app'`. The library default
355
+ is already correct. UMI set `VIBE_COLLECTION=payez_contact_list` for its **waitlist form**,
356
+ and that variable is global to the client — so it silently retargeted every collection call
357
+ in the app, including user identity. Sign-in queried the waitlist's collection for a `users`
358
+ table that does not live there.
359
+
360
+ Same rule as `CLIENT_ID`, from the other direction: there, one value had three names; here,
361
+ one name carries two values. Pin the collection at the call site.
362
+
363
+ ## 17. Tenant-side: vibe-client 0.2.3 sends a filter shape the API rejects — P2
364
+
365
+ `list({ filter })` serialises through `convertFiltersToVibeFormat`, which returns an **array**
366
+ `[{ field, operator, value }]`. `PayEz.Vibe.Public.Api` binds `filter` to a single
367
+ `VibeFilterNode` **object** (`VibeQueryModels.cs:63`), so the request fails to deserialise
368
+ before the query builder ever runs:
369
+
370
+ ```
371
+ 400 VALIDATION_ERROR $.filter
372
+ "The JSON value could not be converted to ...QueryBuilder.VibeFilterNode"
373
+ ```
374
+
375
+ The API is not being arbitrary — `VibeQueryBuilder.cs:355` documents this exact query as
376
+ supported (`{ "idp_user_id": { "eq": 22 } }`), and :94 accepts the flat
377
+ `{ field, operator, value }` form too. The array wrapper is the whole defect, and the API
378
+ change is deliberate.
379
+
380
+ **This affects every filtered query from 0.2.3, on all three sites.** UMI was only the first
381
+ to make one — measured in the IDP proxy logs, UMI's client was the sole source of `$.filter`
382
+ 400s in 24h while idealresume queried cleanly, because idealresume's calls in that window did
383
+ not filter.
384
+
385
+ UMI hand-rolls the single lookup against the proxy with the correct node, reusing the
386
+ library's `resolveConfig()`. That is a workaround with a cost: it duplicates the HMAC recipe
387
+ (`timestamp|method|endpoint`, key base64-decoded) because `generateHmacSignature` is not
388
+ exported, so a signing change in the library breaks that call with a 401. **The real fix is a
389
+ vibe-client release that emits a `VibeFilterNode`**; its source is not in this workspace.
390
+
391
+ ---
392
+
393
+ ## What the tab says about the package
394
+
395
+ Fourteen rows; **eleven are `auto` or `codemod`** — the package could do them, or generate
396
+ them, with no judgement call from the consumer. Only three are real consumer choices.
397
+
398
+ The three P1 route/page rows share one root cause: **the package can export a thing but cannot
399
+ mount it**, and nothing enumerates what is mountable. That is one fix, not three — a list, or
400
+ an `init` command that writes the re-exports.
401
+
402
+ Eight of the fourteen appear in **no doc at all**. `QUICK_START.md` is a stub with a single
403
+ heading. Row 14 is worse than undocumented — the wrong path is the one with the usage example.
404
+
405
+ The recurring shape, which is the same one this codebase keeps paying for: **things present,
406
+ plausible and inert.** A `ThemeProvider` that provides no theme. A template whose comment
407
+ promises a retry that never happens. A guard that greps as a gate and denies everything. An
408
+ exported page nothing mounts. None of them announce themselves; all of them look done.