create-zerotal 1.10.0 → 1.11.1
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.
- package/CHANGELOG.md +54 -0
- package/package.json +1 -1
- package/src/scaffold.ts +24 -5
- package/templates/admin/_gitignore +4 -1
- package/templates/admin/config/session.ts +16 -0
- package/templates/api/_gitignore +4 -1
- package/templates/flow/_gitignore +4 -1
- package/templates/flow/config/session.ts +2 -0
- package/templates/flow/package.json +1 -1
- package/templates/minimal/_gitignore +4 -1
- package/templates/minimal/package.json +1 -1
- package/templates/react/_gitignore +4 -1
- package/templates/react/config/session.ts +18 -0
- package/templates/react/package.json +5 -5
- package/templates/react/resources/css/app.css +4 -1
- package/templates/react/tests/pages.test.ts.tmpl +82 -0
- package/templates/vue/_gitignore +4 -1
- package/templates/vue/config/session.ts +18 -0
- package/templates/vue/package.json +4 -4
- package/templates/vue/resources/css/app.css +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,60 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.11.0] — 2026-08-31
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **The React scaffold ships a page-render test.** Every generated page is built once,
|
|
16
|
+
to prove it can be. The rest of a starter suite asserts values and status codes, and
|
|
17
|
+
a page can throw on its first paint while all of them pass — which is how a blank
|
|
18
|
+
page reaches production with a green suite. New projects inherited that gap; now
|
|
19
|
+
they inherit the check instead. One line per page.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **`config/session.ts` is scaffolded environment-aware**, so the first production
|
|
24
|
+
deploy no longer fails. The production config validator refuses to boot without
|
|
25
|
+
`session.secure`, which is right — but the scaffold shipped `secret`, `cookie` and
|
|
26
|
+
`lifetime` only, so every app built from it hit that refusal on its first deploy,
|
|
27
|
+
after uploading, installing and migrating, at the moment it tried to serve. It also
|
|
28
|
+
cannot simply be `true`: development runs on `http://localhost`, where a secure
|
|
29
|
+
cookie is never sent, and the symptom there is a login that appears to succeed and
|
|
30
|
+
bounces back to the sign-in page with nothing in any log. Now
|
|
31
|
+
`secure: env("APP_ENV", "development") === "production"`, with `httpOnly` and
|
|
32
|
+
`sameSite: "Lax"` — `Lax` because invitation and password-reset links arrive from a
|
|
33
|
+
mail client, and `Strict` withholds the cookie on exactly that navigation.
|
|
34
|
+
|
|
35
|
+
- **`--success` meets WCAG AA.** The light-palette token was `oklch(0.6 0.14 158)`,
|
|
36
|
+
which measures 3.1:1 as status text against the 10% success tint it is drawn on —
|
|
37
|
+
under the 4.5:1 AA needs. Measuring it against the surface *under* the tint is what
|
|
38
|
+
made it look like it passed. Now `oklch(0.504 0.11 158)`.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **`.env.example` no longer ships the key the project actually runs with.** Both
|
|
43
|
+
files got the same rendered content, so every scaffolded project committed a live,
|
|
44
|
+
working `APP_KEY` — `.gitignore` covers `.env` and not `.env.example`. And
|
|
45
|
+
`cp .env.example .env` is the first line of every deployment guide there is, so the
|
|
46
|
+
published key went on to sign production sessions. One key across a laptop and a
|
|
47
|
+
server is one compromise across both, and no strength check can catch it: as a
|
|
48
|
+
string the value is perfectly strong. The example now carries a placeholder naming
|
|
49
|
+
`key:generate`; `.env` still gets a fresh key, so a new app boots immediately.
|
|
50
|
+
|
|
51
|
+
- **`.gitignore` covers the SQLite sidecars.** It had `*.sqlite`, which does not match
|
|
52
|
+
`db.sqlite-wal` or `db.sqlite-shm`. WAL mode is on by default so both exist in every
|
|
53
|
+
project, and the write-ahead log holds pages not yet checkpointed into the main
|
|
54
|
+
file — real rows. An app found both in its first commit on a public host, and was
|
|
55
|
+
saved only by the WAL happening to be empty at that moment. Now `*.sqlite*`.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
|
|
59
|
+
- **`bun-plugin-tailwind` is pinned, and it and `tailwindcss` moved to
|
|
60
|
+
`dependencies`.** Pinned because `latest` on a build-critical package whose peer
|
|
61
|
+
declaration can take down a deploy is a combination worth removing. Moved because a
|
|
62
|
+
deploy that installs with `--production` and *then* builds on the server has no
|
|
63
|
+
devDependencies — so no Tailwind, and a page with no CSS.
|
|
64
|
+
|
|
11
65
|
## [1.7.2] — 2026-08-18
|
|
12
66
|
|
|
13
67
|
### Added
|
package/package.json
CHANGED
package/src/scaffold.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type Template = 'minimal' | 'api' | 'admin' | 'flow' | 'react' | 'vue';
|
|
|
13
13
|
// "^1.1.0" found for specifier "zerotal"` — the first thing anyone trying the
|
|
14
14
|
// framework saw. `scaffold.test.ts` now asserts the two agree, so CI fails rather
|
|
15
15
|
// than the user's install.
|
|
16
|
-
export const ZT_VERSION = "^1.
|
|
16
|
+
export const ZT_VERSION = "^1.11.1";
|
|
17
17
|
|
|
18
18
|
export interface ScaffoldOptions {
|
|
19
19
|
name: string;
|
|
@@ -28,6 +28,18 @@ function randomBase64(bytes: number): string {
|
|
|
28
28
|
return Buffer.from(crypto.getRandomValues(new Uint8Array(bytes))).toString('base64');
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* What the secrets read as in the committed `.env.example`.
|
|
33
|
+
*
|
|
34
|
+
* A value nobody could mistake for a working key, and one that names the command
|
|
35
|
+
* that produces a real one. `.env` still gets a freshly generated key, so a new
|
|
36
|
+
* app boots immediately — the example is the copy that goes to a git host.
|
|
37
|
+
*/
|
|
38
|
+
const PLACEHOLDER_SECRETS: Record<string, string> = {
|
|
39
|
+
'{{app_key}}': 'run-`bun zt.ts key:generate`-to-fill-this',
|
|
40
|
+
'{{session_secret}}': 'run-`bun zt.ts key:generate`-to-fill-this',
|
|
41
|
+
};
|
|
42
|
+
|
|
31
43
|
function tokens(opts: ScaffoldOptions): Record<string, string> {
|
|
32
44
|
const dbUrl: Record<Database, string> = {
|
|
33
45
|
sqlite: './database/db.sqlite',
|
|
@@ -93,12 +105,19 @@ export async function scaffold(opts: ScaffoldOptions): Promise<void> {
|
|
|
93
105
|
const content = applyTokens(raw, map);
|
|
94
106
|
await Bun.write(dest, content);
|
|
95
107
|
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
108
|
+
// `.env` gets the real secrets; `.env.example` gets placeholders.
|
|
109
|
+
//
|
|
110
|
+
// Both used to get the same rendered content, which put a live, working
|
|
111
|
+
// APP_KEY into the one file of the pair that `.gitignore` does *not* cover.
|
|
112
|
+
// Every scaffolded project therefore committed its own session-signing key,
|
|
113
|
+
// and `cp .env.example .env` — the first line of every deployment guide there
|
|
114
|
+
// is — carried that published key into production. One key across a laptop
|
|
115
|
+
// and a server is one compromise across both, and a strength check cannot
|
|
116
|
+
// catch it: as a string the value is perfectly strong. The problem is that
|
|
117
|
+
// this particular value was distributed.
|
|
100
118
|
if (dest.endsWith('.env.example')) {
|
|
101
119
|
await Bun.write(dest.replace(/\.env\.example$/, '.env'), content);
|
|
120
|
+
await Bun.write(dest, applyTokens(raw, { ...map, ...PLACEHOLDER_SECRETS }));
|
|
102
121
|
}
|
|
103
122
|
}
|
|
104
123
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
node_modules/
|
|
2
2
|
.zerotal/
|
|
3
|
-
|
|
3
|
+
# `*.sqlite` alone misses `db.sqlite-wal` and `db.sqlite-shm`. WAL mode is on by
|
|
4
|
+
# default, so both exist in every project, and the write-ahead log holds rows that
|
|
5
|
+
# have not been checkpointed into the main file yet — real ones.
|
|
6
|
+
*.sqlite*
|
|
4
7
|
.env
|
|
@@ -7,4 +7,20 @@ export default SessionConfig({
|
|
|
7
7
|
secret: env("APP_KEY", ""),
|
|
8
8
|
cookie: env("SESSION_COOKIE", "zerotal_session"),
|
|
9
9
|
lifetime: 60 * 24 * 7,
|
|
10
|
+
// `secure` gates the cookie on HTTPS, and the two environments need opposite
|
|
11
|
+
// answers. Production refuses to boot without it — the config validator is right
|
|
12
|
+
// to — but development runs on http://localhost, where a secure cookie is never
|
|
13
|
+
// sent at all, and the symptom there is a login that appears to succeed and then
|
|
14
|
+
// bounces back to the sign-in page with nothing in any log.
|
|
15
|
+
//
|
|
16
|
+
// Scaffolded environment-aware because every app ends up writing this line, and
|
|
17
|
+
// the ones that write it after their first failed production deploy write it at
|
|
18
|
+
// the worst possible moment.
|
|
19
|
+
secure: env("APP_ENV", "development") === "production",
|
|
20
|
+
httpOnly: true,
|
|
21
|
+
|
|
22
|
+
// `Lax`, not `Strict`. Invitation and password-reset links arrive from a mail
|
|
23
|
+
// client, and `Strict` withholds the cookie on exactly that first cross-site
|
|
24
|
+
// navigation — so the link lands the user on a sign-in page instead.
|
|
25
|
+
sameSite: "Lax",
|
|
10
26
|
});
|
package/templates/api/_gitignore
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
node_modules/
|
|
2
2
|
.zerotal/
|
|
3
|
-
|
|
3
|
+
# `*.sqlite` alone misses `db.sqlite-wal` and `db.sqlite-shm`. WAL mode is on by
|
|
4
|
+
# default, so both exist in every project, and the write-ahead log holds rows that
|
|
5
|
+
# have not been checkpointed into the main file yet — real ones.
|
|
6
|
+
*.sqlite*
|
|
4
7
|
.env
|
|
5
8
|
public/assets/
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
node_modules/
|
|
2
2
|
.zerotal/
|
|
3
|
-
|
|
3
|
+
# `*.sqlite` alone misses `db.sqlite-wal` and `db.sqlite-shm`. WAL mode is on by
|
|
4
|
+
# default, so both exist in every project, and the write-ahead log holds rows that
|
|
5
|
+
# have not been checkpointed into the main file yet — real ones.
|
|
6
|
+
*.sqlite*
|
|
4
7
|
.env
|
|
5
8
|
public/app.js
|
|
6
9
|
public/app.css
|
|
@@ -6,6 +6,8 @@ export default SessionConfig({
|
|
|
6
6
|
// Cookie sessions are signed, so the driver needs the app key.
|
|
7
7
|
secret: env("APP_KEY", ""),
|
|
8
8
|
cookie: "{{name}}_session",
|
|
9
|
+
// Seven days suits a demo. Anything holding a credential wants less — an app
|
|
10
|
+
// whose session carries a live mailbox password runs ten hours.
|
|
9
11
|
lifetime: 60 * 60 * 24 * 7, // 7 days
|
|
10
12
|
// Tied to the scheme rather than hardcoded: a `secure` cookie is never sent
|
|
11
13
|
// over plain HTTP, so setting it unconditionally would drop the session in
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
node_modules/
|
|
2
2
|
.zerotal/
|
|
3
|
-
|
|
3
|
+
# `*.sqlite` alone misses `db.sqlite-wal` and `db.sqlite-shm`. WAL mode is on by
|
|
4
|
+
# default, so both exist in every project, and the write-ahead log holds rows that
|
|
5
|
+
# have not been checkpointed into the main file yet — real ones.
|
|
6
|
+
*.sqlite*
|
|
4
7
|
.env
|
|
5
8
|
public/app.js
|
|
6
9
|
public/app.css
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
node_modules/
|
|
2
2
|
.zerotal/
|
|
3
|
-
|
|
3
|
+
# `*.sqlite` alone misses `db.sqlite-wal` and `db.sqlite-shm`. WAL mode is on by
|
|
4
|
+
# default, so both exist in every project, and the write-ahead log holds rows that
|
|
5
|
+
# have not been checkpointed into the main file yet — real ones.
|
|
6
|
+
*.sqlite*
|
|
4
7
|
.env
|
|
5
8
|
public/assets/
|
|
@@ -7,5 +7,23 @@ import { SessionConfig } from "zerotal/session";
|
|
|
7
7
|
export default SessionConfig({
|
|
8
8
|
secret: env("APP_KEY", ""),
|
|
9
9
|
cookie: "{{name}}_session",
|
|
10
|
+
// Seven days suits a demo. Anything holding a credential wants less — an app
|
|
11
|
+
// whose session carries a live mailbox password runs ten hours.
|
|
10
12
|
lifetime: 60 * 60 * 24 * 7, // 7 days
|
|
13
|
+
// `secure` gates the cookie on HTTPS, and the two environments need opposite
|
|
14
|
+
// answers. Production refuses to boot without it — the config validator is right
|
|
15
|
+
// to — but development runs on http://localhost, where a secure cookie is never
|
|
16
|
+
// sent at all, and the symptom there is a login that appears to succeed and then
|
|
17
|
+
// bounces back to the sign-in page with nothing in any log.
|
|
18
|
+
//
|
|
19
|
+
// Scaffolded environment-aware because every app ends up writing this line, and
|
|
20
|
+
// the ones that write it after their first failed production deploy write it at
|
|
21
|
+
// the worst possible moment.
|
|
22
|
+
secure: env("APP_ENV", "development") === "production",
|
|
23
|
+
httpOnly: true,
|
|
24
|
+
|
|
25
|
+
// `Lax`, not `Strict`. Invitation and password-reset links arrive from a mail
|
|
26
|
+
// client, and `Strict` withholds the cookie on exactly that first cross-site
|
|
27
|
+
// navigation — so the link lands the user on a sign-in page instead.
|
|
28
|
+
sameSite: "Lax",
|
|
11
29
|
});
|
|
@@ -12,20 +12,20 @@
|
|
|
12
12
|
"typecheck": "tsc --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@zerotal/devtools": "{{zerotal_version}}",
|
|
16
15
|
"@inertiajs/react": "^3.0.0",
|
|
16
|
+
"@zerotal/devtools": "{{zerotal_version}}",
|
|
17
17
|
"@zerotal/inertia": "{{zerotal_version}}",
|
|
18
|
+
"bun-plugin-tailwind": "0.1.2",
|
|
18
19
|
"react": "^19.0.0",
|
|
19
20
|
"react-dom": "^19.0.0",
|
|
21
|
+
"tailwindcss": "^4.0.0",
|
|
20
22
|
"zerotal": "{{zerotal_version}}"
|
|
21
23
|
},
|
|
22
24
|
"devDependencies": {
|
|
25
|
+
"@types/bun": "^1.3.14",
|
|
23
26
|
"@types/react": "^19.0.0",
|
|
24
27
|
"@types/react-dom": "^19.0.0",
|
|
25
|
-
"
|
|
26
|
-
"tailwindcss": "^4.0.0",
|
|
27
|
-
"typescript": "^5.8.0",
|
|
28
|
-
"@types/bun": "^1.3.14"
|
|
28
|
+
"typescript": "^5.8.0"
|
|
29
29
|
},
|
|
30
30
|
"engines": {
|
|
31
31
|
"bun": ">=1.3.14"
|
|
@@ -46,7 +46,10 @@
|
|
|
46
46
|
--accent: oklch(0.958 0.018 286);
|
|
47
47
|
--accent-foreground: oklch(0.36 0.14 286);
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
/* 0.504, not 0.6: measured as status *text* against the 10% success tint it is
|
|
50
|
+
drawn on, 0.6 is 3.1:1 — under the 4.5:1 WCAG AA needs. Measuring it against
|
|
51
|
+
the surface *under* the tint is what makes it look like it passes. */
|
|
52
|
+
--success: oklch(0.504 0.11 158);
|
|
50
53
|
--success-foreground: oklch(0.99 0.01 158);
|
|
51
54
|
|
|
52
55
|
--destructive: oklch(0.577 0.245 27.3);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every page is built once, to prove it can be.
|
|
3
|
+
*
|
|
4
|
+
* The rest of the suite asserts values and status codes, and a page can throw on
|
|
5
|
+
* its first paint while every one of them passes: the route answers `200`, the
|
|
6
|
+
* Inertia payload is correct, and the failure happens in a browser the test never
|
|
7
|
+
* opened. An app shipped a blank page to production with 614 green tests exactly
|
|
8
|
+
* that way — a layout callback read `page.props`, which the callback is not given.
|
|
9
|
+
*
|
|
10
|
+
* This proves one thing only: the component tree builds without throwing. That is
|
|
11
|
+
* precisely the thing nothing else here checks. It is not a DOM — `useEffect` does
|
|
12
|
+
* not run, and nothing clicks; for that, see `@zerotal/testing/browser`.
|
|
13
|
+
*
|
|
14
|
+
* Add a line when you add a page. The cost is one line and the bug it catches is a
|
|
15
|
+
* white screen your users find first.
|
|
16
|
+
*/
|
|
17
|
+
import { describe, test } from 'bun:test';
|
|
18
|
+
import { renderPage } from '@zerotal/inertia/testing';
|
|
19
|
+
import { defineRoutes } from 'zerotal/routes';
|
|
20
|
+
import { ROUTES } from '../types/routes.generated';
|
|
21
|
+
|
|
22
|
+
import About from '../resources/js/pages/about';
|
|
23
|
+
import Contact from '../resources/js/pages/contact';
|
|
24
|
+
import ForgotPassword from '../resources/js/pages/forgot-password';
|
|
25
|
+
import Home from '../resources/js/pages/home';
|
|
26
|
+
import Login from '../resources/js/pages/login';
|
|
27
|
+
import Profile from '../resources/js/pages/profile';
|
|
28
|
+
import Register from '../resources/js/pages/register';
|
|
29
|
+
import ResetPassword from '../resources/js/pages/reset-password';
|
|
30
|
+
|
|
31
|
+
// The same line `resources/js/app.tsx` runs in the browser. `defineRoutes` installs
|
|
32
|
+
// `route()` as a global, and the layout calls it — so without this every page throws
|
|
33
|
+
// `route is not defined`, which is a true statement about a test that has not
|
|
34
|
+
// finished setting up rather than about the page.
|
|
35
|
+
defineRoutes(ROUTES);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* What `Inertia.share()` puts on every page. A component that destructures one of
|
|
39
|
+
* these throws without it — a real failure, but not the one these tests are for.
|
|
40
|
+
* Keep this in step with `resources/js/types.ts`.
|
|
41
|
+
*/
|
|
42
|
+
const SHARED = {
|
|
43
|
+
auth: { user: null },
|
|
44
|
+
flash: { success: null, error: null },
|
|
45
|
+
errors: {},
|
|
46
|
+
old: {},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const PAGES: [string, unknown, Record<string, unknown>][] = [
|
|
50
|
+
['home', Home, { title: 'Home', message: 'Welcome' }],
|
|
51
|
+
['about', About, { title: 'About' }],
|
|
52
|
+
['contact', Contact, { title: 'Contact' }],
|
|
53
|
+
['login', Login, { title: 'Sign in' }],
|
|
54
|
+
['register', Register, { title: 'Create an account' }],
|
|
55
|
+
['forgot-password', ForgotPassword, { title: 'Forgot password' }],
|
|
56
|
+
['reset-password', ResetPassword, { title: 'Reset password', token: 'test-token' }],
|
|
57
|
+
['profile', Profile, { title: 'Profile' }],
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
describe('pages render', () => {
|
|
61
|
+
for (const [name, Page, props] of PAGES) {
|
|
62
|
+
test(`${name} builds without throwing`, async () => {
|
|
63
|
+
await renderPage(Page, props, { shared: SHARED, url: `/${name}` });
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A signed-in render as well, because a page that only ever renders as a guest has
|
|
70
|
+
* had half of it tested. `profile` reads `auth.user` and is the one that shows it.
|
|
71
|
+
*/
|
|
72
|
+
describe('pages render for a signed-in user', () => {
|
|
73
|
+
const user = { id: 1, name: 'Ada', email: 'ada@example.com' };
|
|
74
|
+
|
|
75
|
+
test('profile builds with a user', async () => {
|
|
76
|
+
await renderPage(
|
|
77
|
+
Profile,
|
|
78
|
+
{ title: 'Profile' },
|
|
79
|
+
{ shared: { ...SHARED, auth: { user } }, url: '/profile' },
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
});
|
package/templates/vue/_gitignore
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
node_modules/
|
|
2
2
|
.zerotal/
|
|
3
|
-
|
|
3
|
+
# `*.sqlite` alone misses `db.sqlite-wal` and `db.sqlite-shm`. WAL mode is on by
|
|
4
|
+
# default, so both exist in every project, and the write-ahead log holds rows that
|
|
5
|
+
# have not been checkpointed into the main file yet — real ones.
|
|
6
|
+
*.sqlite*
|
|
4
7
|
.env
|
|
5
8
|
public/assets/
|
|
@@ -7,5 +7,23 @@ import { SessionConfig } from "zerotal/session";
|
|
|
7
7
|
export default SessionConfig({
|
|
8
8
|
secret: env("APP_KEY", ""),
|
|
9
9
|
cookie: "{{name}}_session",
|
|
10
|
+
// Seven days suits a demo. Anything holding a credential wants less — an app
|
|
11
|
+
// whose session carries a live mailbox password runs ten hours.
|
|
10
12
|
lifetime: 60 * 60 * 24 * 7, // 7 days
|
|
13
|
+
// `secure` gates the cookie on HTTPS, and the two environments need opposite
|
|
14
|
+
// answers. Production refuses to boot without it — the config validator is right
|
|
15
|
+
// to — but development runs on http://localhost, where a secure cookie is never
|
|
16
|
+
// sent at all, and the symptom there is a login that appears to succeed and then
|
|
17
|
+
// bounces back to the sign-in page with nothing in any log.
|
|
18
|
+
//
|
|
19
|
+
// Scaffolded environment-aware because every app ends up writing this line, and
|
|
20
|
+
// the ones that write it after their first failed production deploy write it at
|
|
21
|
+
// the worst possible moment.
|
|
22
|
+
secure: env("APP_ENV", "development") === "production",
|
|
23
|
+
httpOnly: true,
|
|
24
|
+
|
|
25
|
+
// `Lax`, not `Strict`. Invitation and password-reset links arrive from a mail
|
|
26
|
+
// client, and `Strict` withholds the cookie on exactly that first cross-site
|
|
27
|
+
// navigation — so the link lands the user on a sign-in page instead.
|
|
28
|
+
sameSite: "Lax",
|
|
11
29
|
});
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@inertiajs/vue3": "^3.0.0",
|
|
16
16
|
"@zerotal/inertia": "{{zerotal_version}}",
|
|
17
|
+
"bun-plugin-tailwind": "0.1.2",
|
|
18
|
+
"tailwindcss": "^4.0.0",
|
|
17
19
|
"vue": "^3.5.0",
|
|
18
20
|
"zerotal": "{{zerotal_version}}"
|
|
19
21
|
},
|
|
20
22
|
"devDependencies": {
|
|
23
|
+
"@types/bun": "^1.3.14",
|
|
21
24
|
"@vue/compiler-sfc": "^3.5.0",
|
|
22
|
-
"
|
|
23
|
-
"tailwindcss": "^4.0.0",
|
|
24
|
-
"typescript": "^5.8.0",
|
|
25
|
-
"@types/bun": "^1.3.14"
|
|
25
|
+
"typescript": "^5.8.0"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"bun": ">=1.3.14"
|
|
@@ -46,7 +46,10 @@
|
|
|
46
46
|
--accent: oklch(0.958 0.018 286);
|
|
47
47
|
--accent-foreground: oklch(0.36 0.14 286);
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
/* 0.504, not 0.6: measured as status *text* against the 10% success tint it is
|
|
50
|
+
drawn on, 0.6 is 3.1:1 — under the 4.5:1 WCAG AA needs. Measuring it against
|
|
51
|
+
the surface *under* the tint is what makes it look like it passes. */
|
|
52
|
+
--success: oklch(0.504 0.11 158);
|
|
50
53
|
--success-foreground: oklch(0.99 0.01 158);
|
|
51
54
|
|
|
52
55
|
--destructive: oklch(0.577 0.245 27.3);
|