@stacksjs/defaults 0.70.365 → 0.70.366

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.
@@ -354,20 +354,19 @@ drawers must use the shared `dashboard-modal-layer` class so their interactive
354
354
  surface starts beside that sidebar and returns to `left: 0` on mobile and in
355
355
  the Craft native-sidebar shell.
356
356
 
357
- Use the shared `Dashboard/UI/Modal` and `Dashboard/UI/ConfirmDialog`
358
- components when possible. A custom overlay root must use this shape:
359
-
360
- ```html
361
- <div class="fixed inset-y-0 overflow-y-auto right-0 z-[55] dashboard-modal-layer">
362
- <button class="absolute inset-0" aria-label="Close dialog"></button>
363
- <!-- dialog panel -->
364
- </div>
365
- ```
366
-
367
- Do not solve sidebar overlap by increasing z-index alone. That places the
368
- dialog above the sidebar without centering it in the available content area.
369
- Keep overlay children `absolute`, not `fixed`, so they remain bounded by the
370
- sidebar-aware root.
357
+ Use `Dashboard/UI/Modal`, `Dashboard/UI/Drawer`, and
358
+ `Dashboard/UI/ConfirmDialog` for page dialogs, inspectors, forms, and
359
+ confirmations. They own native `<dialog>` behavior, scroll locking, focus
360
+ restoration, Escape and backdrop handling, accessibility labels, and the
361
+ sidebar-aware boundary. `Dashboard/Modals/BaseModal` and
362
+ `Dashboard/Modals/Popups/Alert` are compatibility wrappers over that same
363
+ primitive, not alternate overlay implementations.
364
+
365
+ Do not add a page-owned `fixed inset-0` overlay. A purpose-built application
366
+ surface such as the global command palette or mobile navigation may own a
367
+ custom layer only when the shared dialog or drawer semantics do not fit. It
368
+ must still use `dashboard-modal-layer` and provide complete keyboard, focus,
369
+ and ARIA behavior. Do not solve sidebar overlap by increasing z-index alone.
371
370
 
372
371
  ### Live dashboard audit
373
372
 
@@ -378,6 +377,9 @@ the project root:
378
377
  bun storage/framework/defaults/ai/skills/stacks-dashboard/scripts/audit.ts
379
378
  # Or target a non-default origin:
380
379
  bun storage/framework/defaults/ai/skills/stacks-dashboard/scripts/audit.ts --base-url http://127.0.0.1:3002
380
+
381
+ # Exercise hydrated navigation, console errors, failed requests, and layout:
382
+ bun storage/framework/defaults/ai/skills/stacks-browse/scripts/browse.ts crawl http://localhost:3002/ --max 500 --settle 350 --summary
381
383
  ```
382
384
 
383
385
  Pass a base URL as the first argument when the dashboard is not on
@@ -388,6 +390,13 @@ dashboard API. It fails on missing page renders, invalid fragment contracts,
388
390
  empty or non-HTML pages, unresolved component tags, 5xx or method-mismatch
389
391
  APIs, HTML API fallbacks, invalid JSON, and HTTP-200 error payloads.
390
392
 
393
+ The dependency-free browser crawl follows the rendered link graph in a real
394
+ browser and fails on non-200 pages, console errors, failed subrequests, or
395
+ horizontal overflow. Seed source-only routes with repeated `--path` flags,
396
+ including optional or parameterized pages that the current data set does not
397
+ link. The HTTP audit and browser crawl cover different boundaries, so run both
398
+ for exhaustive dashboard work.
399
+
391
400
  Run this after dashboard route, Action, STX, model, migration, or dev-server
392
401
  changes. Record provider-backed or destructive success paths as explicit
393
402
  environment boundaries. Never replace a failed live contract with sample data
@@ -1,5 +1,5 @@
1
1
  import { Action } from '@stacksjs/actions'
2
- import { Auth, createTwoFactorChallenge, getTwoFactorState } from '@stacksjs/auth'
2
+ import { Auth, authCookie, createTwoFactorChallenge, getTwoFactorState } from '@stacksjs/auth'
3
3
  import { User } from '@stacksjs/orm'
4
4
  import { response } from '@stacksjs/router'
5
5
  import { schema } from '@stacksjs/validation'
@@ -66,6 +66,15 @@ export default new Action({
66
66
  // The legacy `token` field is kept for backward compatibility
67
67
  // with clients that haven't been updated yet — it shadows
68
68
  // `access_token` and will be removed in a future major.
69
+ //
70
+ // The same token also goes out as an httpOnly cookie, matching
71
+ // SocialCallbackAction. Without it, how a browser ends up signed in
72
+ // depended on which way it signed in: OAuth left a cookie, email+password
73
+ // left only JSON, and a server-rendered page cannot read JSON — it posts a
74
+ // form, follows a redirect, and comes back carrying nothing but cookies.
75
+ // So the first authenticated document render had no way to identify the
76
+ // user (#2306). The body is unchanged, so an API client that ignores the
77
+ // cookie behaves exactly as before.
69
78
  return response.json({
70
79
  access_token: result.token,
71
80
  refresh_token: result.refreshToken,
@@ -77,6 +86,6 @@ export default new Action({
77
86
  email: user?.email,
78
87
  name: user?.name,
79
88
  },
80
- })
89
+ }, { headers: { 'Set-Cookie': authCookie(result.token) } })
81
90
  },
82
91
  })
@@ -1,5 +1,5 @@
1
1
  import { Action } from '@stacksjs/actions'
2
- import { refreshToken } from '@stacksjs/auth'
2
+ import { authCookie, refreshToken } from '@stacksjs/auth'
3
3
  import { response } from '@stacksjs/router'
4
4
  import { schema } from '@stacksjs/validation'
5
5
 
@@ -25,12 +25,16 @@ export default new Action({
25
25
  refreshExpiresInDays: 30, // 30 day refresh token
26
26
  })
27
27
 
28
+ // Rotation invalidates the token the cookie was carrying, so a cookie
29
+ // left untouched here would go stale at the exact moment the session was
30
+ // meant to be extended: the browser keeps presenting a revoked token
31
+ // while the JSON body holds a live one it cannot read (#2306).
28
32
  return response.json({
29
33
  access_token: result.plainTextToken,
30
34
  refresh_token: result.refreshToken,
31
35
  token_type: 'Bearer',
32
36
  expires_in: result.expiresIn,
33
- })
37
+ }, { headers: { 'Set-Cookie': authCookie(result.plainTextToken) } })
34
38
  }
35
39
  catch (error: any) {
36
40
  return response.unauthorized(error.message || 'Invalid or expired refresh token')
@@ -1,5 +1,5 @@
1
1
  import { Action } from '@stacksjs/actions'
2
- import { Auth, register } from '@stacksjs/auth'
2
+ import { Auth, authCookie, register } from '@stacksjs/auth'
3
3
  import { dispatch } from '@stacksjs/events'
4
4
  import { response } from '@stacksjs/router'
5
5
  import { schema } from '@stacksjs/validation'
@@ -53,6 +53,10 @@ export default new Action({
53
53
  // were signed out an hour into their first session while every other
54
54
  // user refreshed normally (stacksjs/stacks#2212). The legacy `token`
55
55
  // alias stays for backward compatibility.
56
+ // Registering signs the account in, so it is a session-issuing path like
57
+ // the other three and carries the cookie for the same reason: the very
58
+ // next thing a server-rendered app does is redirect to an authenticated
59
+ // page (#2306).
56
60
  return response.json({
57
61
  access_token: result.token,
58
62
  refresh_token: result.refreshToken,
@@ -64,7 +68,7 @@ export default new Action({
64
68
  email: user?.email,
65
69
  name: user?.name,
66
70
  },
67
- })
71
+ }, { headers: { 'Set-Cookie': authCookie(result.token) } })
68
72
  }
69
73
 
70
74
  return response.error('Registration failed')
@@ -1,5 +1,5 @@
1
1
  import { Action } from '@stacksjs/actions'
2
- import { Auth, consumeTwoFactorChallenge, verifyTwoFactorLoginCode } from '@stacksjs/auth'
2
+ import { Auth, authCookie, consumeTwoFactorChallenge, verifyTwoFactorLoginCode } from '@stacksjs/auth'
3
3
  import { response } from '@stacksjs/router'
4
4
  import { schema } from '@stacksjs/validation'
5
5
 
@@ -41,6 +41,10 @@ export default new Action({
41
41
 
42
42
  const user = result.user
43
43
 
44
+ // This is where a 2FA account's session actually begins — LoginAction
45
+ // deliberately mints nothing for these users until the code is verified —
46
+ // so the cookie belongs here too. Setting it only on LoginAction would
47
+ // have left every 2FA account exactly where it started (#2306).
44
48
  return response.json({
45
49
  access_token: result.token,
46
50
  refresh_token: result.refreshToken,
@@ -52,6 +56,6 @@ export default new Action({
52
56
  email: user?.email,
53
57
  name: user?.name,
54
58
  },
55
- })
59
+ }, { headers: { 'Set-Cookie': authCookie(result.token) } })
56
60
  },
57
61
  })
@@ -2,7 +2,7 @@
2
2
  "publisher": "Stacks",
3
3
  "name": "vscode-stacks",
4
4
  "displayName": "Stacks",
5
- "version": "0.70.365",
5
+ "version": "0.70.366",
6
6
  "description": "A modern Stacks development environment.",
7
7
  "license": "MIT",
8
8
  "funding": "https://github.com/sponsors/chrisbbreuer",
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/defaults",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.365",
5
+ "version": "0.70.366",
6
6
  "description": "The complete managed Stacks application scaffold, including runtime defaults, AI guidance, editor metadata, and npm-backed project support files.",
7
7
  "author": "Chris Breuer",
8
8
  "license": "MIT",
@@ -17,8 +17,6 @@ async function handleForgotPassword(data) {
17
17
  }
18
18
  catch (err) {
19
19
  error.set(err instanceof Error ? err.message : 'Failed to send reset link')
20
- // eslint-disable-next-line no-console
21
- console.error(err)
22
20
  }
23
21
  finally {
24
22
  isLoading.set(false)
@@ -27,7 +27,7 @@ async function handleLogin(credentials) {
27
27
  navigate(safeRedirect())
28
28
  }
29
29
  catch (err) {
30
- error.set(err.message || 'An error occurred during login')
30
+ error.set(err instanceof Error ? err.message : 'An error occurred during login')
31
31
  }
32
32
  finally {
33
33
  isLoading.set(false)
@@ -27,9 +27,7 @@ async function handleRegister(credentials) {
27
27
  navigate(safeRedirect())
28
28
  }
29
29
  catch (err) {
30
- error.set(err.message || 'An error occurred during registration')
31
- // eslint-disable-next-line no-console
32
- console.error(err)
30
+ error.set(err instanceof Error ? err.message : 'An error occurred during registration')
33
31
  }
34
32
  finally {
35
33
  isLoading.set(false)