@jskit-ai/agent-docs 0.1.84 → 0.1.87
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/guide/agent/app-setup/authentication.md +53 -80
- package/guide/agent/app-setup/initial-scaffolding.md +5 -4
- package/guide/agent/app-setup/working-with-the-jskit-cli.md +5 -17
- package/guide/agent/generators/crud-generators.md +0 -18
- package/package.json +1 -1
- package/patterns/ui-testing.md +68 -60
- package/reference/autogen/tooling/create-app.md +8 -0
- package/reference/autogen/tooling/jskit-cli.md +3 -16
|
@@ -961,39 +961,40 @@ That explains why the login screen and auth guard runtime both care about `/api/
|
|
|
961
961
|
|
|
962
962
|
It is also why the shell widget can react cleanly to auth state without storing raw session tokens in client state. The browser just asks the app for the current session view, and the app derives that from its cookies plus the selected provider.
|
|
963
963
|
|
|
964
|
-
### Authenticated Playwright testing
|
|
964
|
+
### Authenticated Playwright testing
|
|
965
965
|
|
|
966
|
-
JSKIT
|
|
966
|
+
JSKIT supports two intentionally separate authenticated-browser paths:
|
|
967
967
|
|
|
968
|
-
|
|
968
|
+
- a private localhost exchange for direct local Playwright runs
|
|
969
|
+
- runner-provided Playwright storage state for managed previews
|
|
969
970
|
|
|
970
|
-
-
|
|
971
|
-
- create a session for an existing user through the local app
|
|
972
|
-
- let the browser keep the resulting HTTP-only cookies
|
|
973
|
-
- navigate to the protected page and verify the feature
|
|
974
|
-
- record the Playwright run through `jskit app verify-ui` so `jskit doctor` can verify the receipt later
|
|
971
|
+
Neither path drives a live external login provider. Both put the resulting HTTP-only cookies into the same Playwright browser context that runs the feature assertions.
|
|
975
972
|
|
|
976
|
-
The
|
|
973
|
+
The generated `playwright.config.mjs` is a thin delegate to `@jskit-ai/jskit-cli/test/playwright`. The published helper owns the changing setup behavior:
|
|
977
974
|
|
|
978
|
-
-
|
|
979
|
-
-
|
|
980
|
-
-
|
|
981
|
-
- The route only looks up an existing user. It does not create one.
|
|
975
|
+
- without `PLAYWRIGHT_BASE_URL`, it builds the app and starts the local server on `http://127.0.0.1:4173`
|
|
976
|
+
- with `PLAYWRIGHT_BASE_URL`, it uses that managed preview and does not start another server
|
|
977
|
+
- with `JSKIT_PLAYWRIGHT_STORAGE_STATE`, it loads the supplied authenticated state into Playwright contexts
|
|
982
978
|
|
|
983
|
-
|
|
979
|
+
Tests should therefore navigate with relative paths such as `page.goto("/w/acme/admin/contacts")`.
|
|
980
|
+
|
|
981
|
+
#### Direct local dev-auth login
|
|
982
|
+
|
|
983
|
+
The local dev auth bypass remains deliberately narrow:
|
|
984
|
+
|
|
985
|
+
- it is available only outside production
|
|
986
|
+
- the request must arrive locally
|
|
987
|
+
- it selects an existing user and never creates one
|
|
988
|
+
- it requires both CSRF protection and a private exchange secret
|
|
989
|
+
|
|
990
|
+
Enable it for the Playwright process and the local server it starts:
|
|
984
991
|
|
|
985
992
|
```bash
|
|
986
993
|
AUTH_DEV_BYPASS_ENABLED=true
|
|
987
994
|
AUTH_DEV_BYPASS_SECRET=replace-this-with-a-local-dev-secret
|
|
988
995
|
```
|
|
989
996
|
|
|
990
|
-
When enabled
|
|
991
|
-
|
|
992
|
-
```text
|
|
993
|
-
POST /api/dev-auth/login-as
|
|
994
|
-
```
|
|
995
|
-
|
|
996
|
-
The request body must contain either:
|
|
997
|
+
When enabled, the app exposes `POST /api/dev-auth/login-as`. The body contains exactly one existing-user identity:
|
|
997
998
|
|
|
998
999
|
```json
|
|
999
1000
|
{ "userId": "7" }
|
|
@@ -1005,70 +1006,45 @@ or:
|
|
|
1005
1006
|
{ "email": "ada@example.com" }
|
|
1006
1007
|
```
|
|
1007
1008
|
|
|
1008
|
-
The
|
|
1009
|
+
Do not call that route from `page.evaluate()`. The route requires the private `x-jskit-dev-auth-secret` header as well as the CSRF token. Putting the secret into browser JavaScript, client-visible environment, query parameters, or page globals would defeat the exchange boundary.
|
|
1009
1010
|
|
|
1010
|
-
|
|
1011
|
-
{
|
|
1012
|
-
"ok": true,
|
|
1013
|
-
"userId": "7",
|
|
1014
|
-
"username": "Ada Example",
|
|
1015
|
-
"email": "ada@example.com"
|
|
1016
|
-
}
|
|
1017
|
-
```
|
|
1011
|
+
Use the published Node-side helper instead:
|
|
1018
1012
|
|
|
1019
|
-
|
|
1013
|
+
```ts
|
|
1014
|
+
import { expect, test } from "@playwright/test";
|
|
1015
|
+
import { loginAsExistingUser } from "@jskit-ai/auth-web/test/playwright";
|
|
1020
1016
|
|
|
1021
|
-
|
|
1017
|
+
test("authenticated contacts filters", async ({ page }) => {
|
|
1018
|
+
await loginAsExistingUser(page, { email: "ada@example.com" });
|
|
1022
1019
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1020
|
+
await page.goto("/w/acme/admin/contacts");
|
|
1021
|
+
await expect(page.getByRole("heading", { name: "Contacts" })).toBeVisible();
|
|
1022
|
+
});
|
|
1023
|
+
```
|
|
1026
1024
|
|
|
1027
|
-
|
|
1025
|
+
`loginAsExistingUser()` runs from the Playwright Node process. It uses the request client attached to `page.context()`, reads `csrfToken` from `GET /api/session`, sends that token plus `x-jskit-dev-auth-secret` to the login route, and leaves the response cookies in the same browser context. It reads `AUTH_DEV_BYPASS_SECRET` from the Node process by default and refuses to send it to a non-local URL.
|
|
1028
1026
|
|
|
1029
|
-
|
|
1030
|
-
2. call `/api/session` to read `csrfToken`
|
|
1031
|
-
3. call `/api/dev-auth/login-as` with `credentials: "include"` and the `csrf-token` header
|
|
1032
|
-
4. navigate to the protected route and run the assertions
|
|
1027
|
+
That last restriction is important. A managed preview URL is not a direct localhost app connection, even when its backend eventually runs on the same infrastructure. Project code must not receive the managed host's private exchange authority.
|
|
1033
1028
|
|
|
1034
|
-
|
|
1029
|
+
#### Managed preview authentication
|
|
1035
1030
|
|
|
1036
|
-
|
|
1037
|
-
await page.goto("/");
|
|
1031
|
+
A managed host authenticates outside the project browser context. It performs its trusted identity exchange, writes the resulting cookies and origins to a temporary Playwright storage-state file, and starts the project test with:
|
|
1038
1032
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
throw new Error(`Session bootstrap failed: ${sessionResponse.status}`);
|
|
1045
|
-
}
|
|
1033
|
+
```bash
|
|
1034
|
+
PLAYWRIGHT_BASE_URL=https://managed-preview.example.test \
|
|
1035
|
+
JSKIT_PLAYWRIGHT_STORAGE_STATE=/secure/temp/playwright-state.json \
|
|
1036
|
+
playwright test tests/e2e/contacts.spec.ts
|
|
1037
|
+
```
|
|
1046
1038
|
|
|
1047
|
-
|
|
1048
|
-
const csrfToken = String(sessionPayload?.csrfToken || "");
|
|
1049
|
-
if (!csrfToken) {
|
|
1050
|
-
throw new Error("Missing csrfToken from /api/session.");
|
|
1051
|
-
}
|
|
1039
|
+
JSKIT only consumes that runner-neutral contract. It does not detect a particular host, call host-specific commands, proxy private headers, or install a browser.
|
|
1052
1040
|
|
|
1053
|
-
|
|
1054
|
-
method: "POST",
|
|
1055
|
-
credentials: "include",
|
|
1056
|
-
headers: {
|
|
1057
|
-
"content-type": "application/json",
|
|
1058
|
-
"csrf-token": csrfToken
|
|
1059
|
-
},
|
|
1060
|
-
body: JSON.stringify({ email })
|
|
1061
|
-
});
|
|
1041
|
+
The storage-state file is a secret because it can contain authenticated cookies. The managed runner must create it with appropriately restricted access, must not print or commit it, and must delete it after the run.
|
|
1062
1042
|
|
|
1063
|
-
|
|
1064
|
-
throw new Error(`Dev login failed: ${loginResponse.status} ${await loginResponse.text()}`);
|
|
1065
|
-
}
|
|
1066
|
-
}, { email: "ada@example.com" });
|
|
1043
|
+
Tests using managed state do not call `loginAsExistingUser()`. They begin with the runner-provided identity already present and navigate using relative paths. An ordinary browser or proxy request to `/api/dev-auth/login-as` without the private header must continue to fail with `403 Dev auth exchange is not authorized.`
|
|
1067
1044
|
|
|
1068
|
-
|
|
1069
|
-
```
|
|
1045
|
+
#### Recording the result
|
|
1070
1046
|
|
|
1071
|
-
|
|
1047
|
+
After the actual Playwright flow succeeds, record it through JSKIT:
|
|
1072
1048
|
|
|
1073
1049
|
```bash
|
|
1074
1050
|
npx jskit app verify-ui \
|
|
@@ -1077,20 +1053,17 @@ npx jskit app verify-ui \
|
|
|
1077
1053
|
--auth-mode dev-auth-login-as
|
|
1078
1054
|
```
|
|
1079
1055
|
|
|
1080
|
-
|
|
1056
|
+
Use `--auth-mode session-bootstrap` when a managed runner supplied authenticated storage state.
|
|
1057
|
+
|
|
1058
|
+
`jskit app verify-ui` executes the command and records the command, auth-mode label, feature, and changed UI files. The `--auth-mode` value describes how the Playwright command was authenticated. It does not create a session, inject a secret, or alter the browser context.
|
|
1059
|
+
|
|
1060
|
+
For local pre-merge review, follow the recorded run with:
|
|
1081
1061
|
|
|
1082
1062
|
```bash
|
|
1083
1063
|
npx jskit doctor --against origin/main
|
|
1084
1064
|
```
|
|
1085
1065
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
That flow is preferable to driving the real sign-in form in feature tests because it keeps the test focused on the UI feature being added, not on an external auth dependency. If a chunk changes user-facing UI and the flow requires login, the expected JSKIT review standard is:
|
|
1089
|
-
|
|
1090
|
-
- use Playwright
|
|
1091
|
-
- record the run with `jskit app verify-ui`
|
|
1092
|
-
- use the local dev auth bypass or another local session bootstrap path
|
|
1093
|
-
- exercise the actual changed behavior, not only page load
|
|
1066
|
+
This keeps feature tests focused on the changed UI while preserving the security boundary around session creation.
|
|
1094
1067
|
|
|
1095
1068
|
## When you later switch to Supabase
|
|
1096
1069
|
|
|
@@ -162,7 +162,6 @@ The most important parts look like this:
|
|
|
162
162
|
"server": "node ./bin/server.js",
|
|
163
163
|
"server:all": "node ./bin/server.js",
|
|
164
164
|
"server:home": "SERVER_SURFACE=home node ./bin/server.js",
|
|
165
|
-
"devlinks": "jskit app link-local-packages",
|
|
166
165
|
"dev": "vite",
|
|
167
166
|
"dev:all": "vite",
|
|
168
167
|
"dev:home": "VITE_SURFACE=home vite",
|
|
@@ -206,9 +205,11 @@ The most important parts look like this:
|
|
|
206
205
|
|
|
207
206
|
Published JSKIT libraries and tooling support Node.js 22 from 22.12.0 onward, Node.js 24, and Node.js 26. Newly generated applications deliberately require Node 26: their app-level `engines` contract, `.nvmrc`, and JSKIT-managed verification workflow all name that runtime. The app-level contract is the runtime boundary for the app and its installed JSKIT runtime packages, while independently consumed JSKIT CLI and tooling packages retain the wider supported range. The dependency on `@local/main` points at `file:packages/main`, which means your app already contains its own local JSKIT package. The maintenance scripts are also useful to notice early, because they show an important ownership boundary in JSKIT.
|
|
208
207
|
|
|
209
|
-
`verify`, `jskit:update`,
|
|
208
|
+
`verify`, `jskit:update`, and `release` are intentionally thin wrappers. They stay in `package.json` because they are convenient app-local shortcuts, but the real implementation lives in `jskit app ...`, not in copied scaffold scripts.
|
|
210
209
|
|
|
211
|
-
That matters because JSKIT maintenance policy changes over time. If the scaffold copied a large shell script into every app, existing apps would freeze the old behavior forever. By delegating to `jskit app verify`, `jskit app update-packages`,
|
|
210
|
+
That matters because JSKIT maintenance policy changes over time. If the scaffold copied a large shell script into every app, existing apps would freeze the old behavior forever. By delegating to `jskit app verify`, `jskit app update-packages`, and `jskit app release`, the app keeps the nice `npm run` shortcuts while the maintained behavior stays in the installed CLI package.
|
|
211
|
+
|
|
212
|
+
The Playwright scaffold follows the same rule. `playwright.config.mjs` delegates to `@jskit-ai/jskit-cli/test/playwright`, and the starter browser specs delegate their shared responsive checks to published JSKIT helpers. The generated files stay small while later JSKIT package updates can change local server startup, managed `PLAYWRIGHT_BASE_URL` handling, and `JSKIT_PLAYWRIGHT_STORAGE_STATE` support without copying that logic into each new app.
|
|
212
213
|
|
|
213
214
|
`jskit app verify` is worth noticing specifically. Linting, tests, and builds check your source code and runtime behavior. The JSKIT part of that flow runs `doctor`, which checks JSKIT-managed app state: installed package visibility, lock-file-backed managed files, and other JSKIT-specific health rules. It is there because a JSKIT app is not only code. It is also a descriptor-driven managed project.
|
|
214
215
|
|
|
@@ -817,7 +818,7 @@ That is why you saw `@jskit-ai/kernel` and `@jskit-ai/http-runtime` earlier in `
|
|
|
817
818
|
|
|
818
819
|
### Other files and options
|
|
819
820
|
|
|
820
|
-
The remaining files are easier to understand once you know the core pieces above. `vite.config.mjs` configures the frontend build and the `/api` proxy used during development. `index.html` is the HTML shell Vite uses to mount Vue. `tests/` contains basic smoke tests so the app has a verification path from day one. The `scripts/` directory is intentionally small because JSKIT maintenance helpers such as `verify`, `jskit:update`,
|
|
821
|
+
The remaining files are easier to understand once you know the core pieces above. `vite.config.mjs` configures the frontend build and the `/api` proxy used during development. `index.html` is the HTML shell Vite uses to mount Vue. `tests/` contains basic smoke tests so the app has a verification path from day one. The `scripts/` directory is intentionally small because JSKIT maintenance helpers such as `verify`, `jskit:update`, and `release` are package-owned CLI commands rather than copied app scripts.
|
|
821
822
|
|
|
822
823
|
The `create-app` command also accepts a few other flags that are useful without changing the basic meaning of this chapter's setup. `--title <text>` lets you replace the browser title and other template text with a friendlier app name. `--target <path>` lets you choose a different output directory instead of the default `./exampleapp`. `--tenancy-mode <mode>` can seed `none`, `personal`, or `workspaces`; for this chapter we intentionally use `none` so the first scaffold stays small and non-workspace. `--minimal` selects the bare `minimal-shell` template instead of the default shell-web app template. `--force` allows writing into a non-empty target directory when you know that is what you want. `--dry-run` prints the planned file writes without touching the filesystem, which is useful when you want to inspect what the generator would do. `-h` or `--help` prints the command help.
|
|
823
824
|
|
|
@@ -53,7 +53,6 @@ The important examples are:
|
|
|
53
53
|
|
|
54
54
|
- `npm run verify`
|
|
55
55
|
- `npm run jskit:update`
|
|
56
|
-
- `npm run devlinks`
|
|
57
56
|
- `npm run release`
|
|
58
57
|
|
|
59
58
|
In the current scaffold, those scripts are intentionally thin:
|
|
@@ -63,7 +62,6 @@ In the current scaffold, those scripts are intentionally thin:
|
|
|
63
62
|
"scripts": {
|
|
64
63
|
"verify": "jskit app verify && npm run --if-present verify:app",
|
|
65
64
|
"jskit:update": "jskit app update-packages",
|
|
66
|
-
"devlinks": "jskit app link-local-packages",
|
|
67
65
|
"release": "jskit app release"
|
|
68
66
|
}
|
|
69
67
|
}
|
|
@@ -71,12 +69,11 @@ In the current scaffold, those scripts are intentionally thin:
|
|
|
71
69
|
|
|
72
70
|
That is a deliberate design choice.
|
|
73
71
|
|
|
74
|
-
The app keeps the handy `npm run` names, but the real maintenance policy lives in the installed CLI package instead of copied shell scripts inside the app. That means if JSKIT later changes how package updates
|
|
72
|
+
The app keeps the handy `npm run` names, but the real maintenance policy lives in the installed CLI package instead of copied shell scripts inside the app. That means if JSKIT later changes how package updates or baseline verification should work, apps can pick up the new behavior by updating `@jskit-ai/jskit-cli` instead of hand-editing frozen scaffold files.
|
|
75
73
|
|
|
76
74
|
This gives you a clean ownership split:
|
|
77
75
|
|
|
78
76
|
- app-owned scripts still describe how *this app* runs, builds, and tests
|
|
79
|
-
- `npm run devlinks` re-applies local checkout links after `npm install` when you are testing an app against this monorepo
|
|
80
77
|
- JSKIT-owned wrapper scripts delegate framework maintenance to `jskit app ...`
|
|
81
78
|
|
|
82
79
|
That split is worth keeping in mind through the rest of the guide. When you see `npm run verify`, read it as "run the app's JSKIT baseline verification policy, then any app-specific extra verification hook".
|
|
@@ -116,14 +113,9 @@ npm run jskit:update
|
|
|
116
113
|
|
|
117
114
|
After it succeeds, npm dependencies and descriptor-managed app state are current together. If a managed app-owned file is missing, a required saved option can no longer be resolved, or another package update cannot be applied safely, the app-wide update fails instead of leaving that package silently stale. `--dry-run` reports which installed packages would be reapplied without invoking their update lifecycle.
|
|
118
115
|
|
|
119
|
-
|
|
116
|
+
At the start of a real update, JSKIT resolves and installs the latest `@jskit-ai/jskit-cli`, then hands the rest of the operation to that app-local CLI process. This keeps the one-command contract even when the updater itself has changed: the current code, not the already-running older process, owns package reapplication, migrations, workspace refreshes, and CI synchronization.
|
|
120
117
|
|
|
121
|
-
|
|
122
|
-
npm install --save-dev --save-exact @jskit-ai/jskit-cli@latest
|
|
123
|
-
npm run jskit:update
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
After that bootstrap, normal future upgrades use only `npm run jskit:update`. Do not work around the first upgrade by editing `.jskit/lock.json`; the new updater must reapply the descriptor contract and write managed state itself.
|
|
118
|
+
Do not pre-install the CLI separately and do not edit `.jskit/lock.json`. Run `npm run jskit:update`; the updater owns its bootstrap and writes managed state through the normal package lifecycle.
|
|
127
119
|
|
|
128
120
|
The update reports elapsed progress for registry and install work. It also refreshes JSKIT-managed migrations and CI after root package changes. Preview the complete operation without changing manifests, descriptors, the lockfile, migrations, or CI with:
|
|
129
121
|
|
|
@@ -571,12 +563,6 @@ npm install
|
|
|
571
563
|
|
|
572
564
|
`jskit add` changes the app. `npm install` downloads the dependencies that the changed app requires.
|
|
573
565
|
|
|
574
|
-
If the app is being tested against a local JSKIT checkout with linked packages, run the local-link wrapper again after every `npm install`:
|
|
575
|
-
|
|
576
|
-
```bash
|
|
577
|
-
npm run devlinks
|
|
578
|
-
```
|
|
579
|
-
|
|
580
566
|
### `add bundle`
|
|
581
567
|
|
|
582
568
|
Bundles are a convenience layer for catalog shortcuts. Prefer package commands in setup docs and normal app work, especially for auth, because provider ownership is clearer when the selected provider package is installed explicitly.
|
|
@@ -818,6 +804,8 @@ That command does two things:
|
|
|
818
804
|
- runs the targeted Playwright command you give it
|
|
819
805
|
- writes a receipt describing the verified feature, auth mode, and current dirty UI file set
|
|
820
806
|
|
|
807
|
+
The auth mode is receipt metadata, not an authentication implementation. `jskit app verify-ui` does not log a user in, inject a secret, or rewrite the Playwright context. A direct local run uses `loginAsExistingUser()` from `@jskit-ai/auth-web/test/playwright`; a managed runner supplies authenticated state through `JSKIT_PLAYWRIGHT_STORAGE_STATE`. The generated `playwright.config.mjs` consumes that state and honors `PLAYWRIGHT_BASE_URL` without starting a second server.
|
|
808
|
+
|
|
821
809
|
`doctor` then compares the current changed UI files to that receipt. If you edit the UI again afterwards, the receipt is stale and `doctor` tells you to rerun `jskit app verify-ui`.
|
|
822
810
|
|
|
823
811
|
For local pre-merge review, use `--against <base-ref>` so JSKIT compares against the branch delta instead of only the current dirty worktree. The common shape is:
|
|
@@ -314,12 +314,6 @@ So before you build or run the app again, install that new local package:
|
|
|
314
314
|
npm install
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
-
If you are verifying the guide against a local JSKIT checkout and have already been using local package links, rerun:
|
|
318
|
-
|
|
319
|
-
```bash
|
|
320
|
-
npm run devlinks
|
|
321
|
-
```
|
|
322
|
-
|
|
323
317
|
The same rule applies after later server scaffolds such as `addresses` and `comments`. The UI generator can still read the generated resource file directly, but the app runtime needs the local package install boundary to be completed before the CRUD can boot normally.
|
|
324
318
|
|
|
325
319
|
For standard CRUDs, that file is intentionally compact. It uses `defineCrudResource(...)` from `@jskit-ai/resource-crud-core`, authors the canonical `schema` / `searchSchema` / `defaultSort` / `autofilter` shape once, and lets JSKIT derive the standard CRUD operation contracts from it.
|
|
@@ -435,12 +429,6 @@ Then install the generated local package:
|
|
|
435
429
|
npm install
|
|
436
430
|
```
|
|
437
431
|
|
|
438
|
-
If you are developing against a local JSKIT checkout, relink your app to the local packages before you run the UI:
|
|
439
|
-
|
|
440
|
-
```bash
|
|
441
|
-
npm run devlinks
|
|
442
|
-
```
|
|
443
|
-
|
|
444
432
|
### Step 3: refine the generated lookup metadata by hand
|
|
445
433
|
|
|
446
434
|
Open `packages/addresses/src/shared/addressResource.js` and add `labelKey: "fullName"` to the generated `contactId` relation:
|
|
@@ -617,12 +605,6 @@ Then install the generated local package:
|
|
|
617
605
|
npm install
|
|
618
606
|
```
|
|
619
607
|
|
|
620
|
-
If you are developing against a local JSKIT checkout, relink the app before you run the UI:
|
|
621
|
-
|
|
622
|
-
```bash
|
|
623
|
-
npm run devlinks
|
|
624
|
-
```
|
|
625
|
-
|
|
626
608
|
### Step 3: refine the generated lookup metadata by hand
|
|
627
609
|
|
|
628
610
|
Open `packages/comments/src/shared/commentResource.js` and add `labelKey: "fullName"` to the generated `contactId` relation:
|
package/package.json
CHANGED
package/patterns/ui-testing.md
CHANGED
|
@@ -11,80 +11,88 @@ Rules:
|
|
|
11
11
|
|
|
12
12
|
- Any chunk that adds or changes user-facing UI must include a Playwright flow that exercises the changed behavior before the chunk is done.
|
|
13
13
|
- Generator or package template UI changes must be checked at compact phone, tablet-ish medium, and expanded desktop widths.
|
|
14
|
-
- For generated UI,
|
|
15
|
-
- Apps with `shell-web` installed should start from
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
- Do not
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- Make the bootstrap request from the same browser context that will run the assertions so the auth cookies land in the page session.
|
|
28
|
-
- Use stable seeded users or fixtures for Playwright. Do not depend on whatever account happens to exist in a developer's browser or external auth provider.
|
|
29
|
-
|
|
30
|
-
Recommended flow:
|
|
14
|
+
- For generated UI, check horizontal overflow, clipped or invisible text, duplicate navigation, broken route placement, and tap targets under 48 px.
|
|
15
|
+
- Apps with `shell-web` installed should start from `tests/e2e/adaptive-shell.spec.ts` and extend it with feature-specific assertions.
|
|
16
|
+
- Generated `playwright.config.mjs` delegates to `@jskit-ai/jskit-cli/test/playwright`. Do not copy base-URL, web-server, or storage-state logic into app tests.
|
|
17
|
+
- Use relative paths such as `page.goto("/home")`. The shared config owns the browser base URL.
|
|
18
|
+
- A managed runner supplies `PLAYWRIGHT_BASE_URL`. When it is set, JSKIT does not start another app server.
|
|
19
|
+
- A managed runner supplies an authenticated context through `JSKIT_PLAYWRIGHT_STORAGE_STATE`. Treat that file as a temporary secret: do not commit it, print it, or retain it after the run.
|
|
20
|
+
- Do not install a browser when the environment provides a managed browser runner.
|
|
21
|
+
|
|
22
|
+
## Direct local authentication
|
|
23
|
+
|
|
24
|
+
Use the development-only dev auth bypass when Playwright is talking directly to an app running on localhost.
|
|
25
|
+
|
|
26
|
+
The app must start with:
|
|
31
27
|
|
|
32
28
|
```bash
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
AUTH_DEV_BYPASS_ENABLED=true
|
|
30
|
+
AUTH_DEV_BYPASS_SECRET=replace-this-with-a-local-dev-secret
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The test uses the published Node-side helper:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { expect, test } from "@playwright/test";
|
|
37
|
+
import { loginAsExistingUser } from "@jskit-ai/auth-web/test/playwright";
|
|
38
|
+
|
|
39
|
+
test("authenticated feature", async ({ page }) => {
|
|
40
|
+
await loginAsExistingUser(page, { email: "ada@example.com" });
|
|
41
|
+
await page.goto("/w/acme/admin/contacts");
|
|
42
|
+
await expect(page.getByRole("heading", { name: "Contacts" })).toBeVisible();
|
|
43
|
+
});
|
|
37
44
|
```
|
|
38
45
|
|
|
39
|
-
|
|
46
|
+
`loginAsExistingUser()`:
|
|
47
|
+
|
|
48
|
+
- uses the request client belonging to the same Playwright browser context
|
|
49
|
+
- fetches the CSRF token from `GET /api/session`
|
|
50
|
+
- sends the CSRF token and private `x-jskit-dev-auth-secret` header to `POST /api/dev-auth/login-as`
|
|
51
|
+
- leaves the resulting HTTP-only cookies in that browser context
|
|
52
|
+
- refuses to send the secret to a non-local URL
|
|
53
|
+
|
|
54
|
+
The helper reads `AUTH_DEV_BYPASS_SECRET` from the Playwright Node process by default. Never pass that secret through `page.evaluate()`, browser globals, query parameters, or client-visible environment variables.
|
|
55
|
+
|
|
56
|
+
The route only selects an existing user. Seed a stable user or fixture before the test. The bypass must never be enabled in production.
|
|
57
|
+
|
|
58
|
+
## Managed-host authentication
|
|
59
|
+
|
|
60
|
+
A managed preview must not expose `AUTH_DEV_BYPASS_SECRET` to project code or forward an ordinary browser request to the private login exchange. The host performs its trusted identity exchange outside the application browser context, writes a temporary Playwright storage-state file, and launches the test with:
|
|
40
61
|
|
|
41
62
|
```bash
|
|
42
|
-
|
|
63
|
+
PLAYWRIGHT_BASE_URL=https://managed-preview.example.test \
|
|
64
|
+
JSKIT_PLAYWRIGHT_STORAGE_STATE=/secure/temp/playwright-state.json \
|
|
65
|
+
playwright test tests/e2e/contacts.spec.ts
|
|
43
66
|
```
|
|
44
67
|
|
|
45
|
-
The Playwright
|
|
68
|
+
The generated config applies that state to Playwright contexts and omits its local `webServer`. Tests then navigate with relative paths and begin with the runner-provided identity already authenticated.
|
|
46
69
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const sessionPayload = await sessionResponse.json();
|
|
59
|
-
const csrfToken = String(sessionPayload?.csrfToken || "");
|
|
60
|
-
if (!csrfToken) {
|
|
61
|
-
throw new Error("Missing csrfToken from /api/session.");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const loginResponse = await fetch("/api/dev-auth/login-as", {
|
|
65
|
-
method: "POST",
|
|
66
|
-
credentials: "include",
|
|
67
|
-
headers: {
|
|
68
|
-
"content-type": "application/json",
|
|
69
|
-
"csrf-token": csrfToken
|
|
70
|
-
},
|
|
71
|
-
body: JSON.stringify({ email })
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
if (!loginResponse.ok) {
|
|
75
|
-
throw new Error(`Dev login failed: ${loginResponse.status} ${await loginResponse.text()}`);
|
|
76
|
-
}
|
|
77
|
-
}, { email: "ada@example.com" });
|
|
70
|
+
Do not call `loginAsExistingUser()` against a managed preview. It is deliberately localhost-only. An ordinary request to `/api/dev-auth/login-as` without the private exchange header must return `403`.
|
|
71
|
+
|
|
72
|
+
## Recording verification
|
|
73
|
+
|
|
74
|
+
After the Playwright command succeeds, record it with:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx jskit app verify-ui \
|
|
78
|
+
--command "npx playwright test tests/e2e/contacts.spec.ts -g filters" \
|
|
79
|
+
--feature "contacts filters" \
|
|
80
|
+
--auth-mode dev-auth-login-as
|
|
78
81
|
```
|
|
79
82
|
|
|
80
|
-
|
|
83
|
+
Use `--auth-mode session-bootstrap` when a managed runner supplied authenticated storage state.
|
|
84
|
+
|
|
85
|
+
`jskit app verify-ui` runs the command and records its command, auth-mode label, feature, and changed UI files in `.jskit/verification/ui.json`. The auth-mode option describes how the command was authenticated; it does not create a session or modify the Playwright context.
|
|
86
|
+
|
|
87
|
+
For local pre-merge review, follow the recorded run with:
|
|
81
88
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
```bash
|
|
90
|
+
npx jskit doctor --against origin/main
|
|
91
|
+
```
|
|
85
92
|
|
|
86
93
|
Do not mark the chunk done if:
|
|
87
94
|
|
|
88
95
|
- the feature changed user-facing UI but no Playwright flow ran
|
|
89
96
|
- the Playwright flow skipped the changed behavior itself
|
|
90
|
-
-
|
|
97
|
+
- a managed environment was bypassed by installing another browser
|
|
98
|
+
- an authenticated flow was left untested without clearly reporting the missing session-bootstrap seam
|
|
@@ -116,6 +116,10 @@ Exports
|
|
|
116
116
|
Exports
|
|
117
117
|
- None
|
|
118
118
|
|
|
119
|
+
### `templates/base-shell/playwright.config.mjs`
|
|
120
|
+
Exports
|
|
121
|
+
- None
|
|
122
|
+
|
|
119
123
|
### `templates/base-shell/server.js`
|
|
120
124
|
Exports
|
|
121
125
|
- `createServer()`
|
|
@@ -266,6 +270,10 @@ Exports
|
|
|
266
270
|
Exports
|
|
267
271
|
- None
|
|
268
272
|
|
|
273
|
+
### `templates/minimal-shell/playwright.config.mjs`
|
|
274
|
+
Exports
|
|
275
|
+
- None
|
|
276
|
+
|
|
269
277
|
### `templates/minimal-shell/server.js`
|
|
270
278
|
Exports
|
|
271
279
|
- `createServer()`
|
|
@@ -512,15 +512,6 @@ Exports
|
|
|
512
512
|
Local functions
|
|
513
513
|
- `shouldRewriteScript(currentValue = "", scriptName = "", force = false)`
|
|
514
514
|
|
|
515
|
-
### `src/server/commandHandlers/appCommands/linkLocalPackages.js`
|
|
516
|
-
Exports
|
|
517
|
-
- `runAppLinkLocalPackagesCommand(ctx = {}, { appRoot = "", options = {}, stdout })`
|
|
518
|
-
Local functions
|
|
519
|
-
- `collectDeclaredPackageNames(packageJson = {})`
|
|
520
|
-
- `verifySymlinkTarget(targetPath = "", sourceDir = "", { packageName = "" } = {})`
|
|
521
|
-
- `replaceWithSymlink(targetPath = "", sourceDir = "", { packageName = "" } = {})`
|
|
522
|
-
- `maybeLinkCompanionPackages({ appRoot = "", repoRoot = "", stdout, createCliError })`
|
|
523
|
-
|
|
524
515
|
### `src/server/commandHandlers/appCommands/migrateSourceMutations.js`
|
|
525
516
|
Exports
|
|
526
517
|
- `buildCrudFormFieldPushMigration(sourceText = "")`
|
|
@@ -558,11 +549,7 @@ Exports
|
|
|
558
549
|
- `formatUtcReleaseTimestamp(date = new Date())`
|
|
559
550
|
- `resolveLocalJskitBin(appRoot = "")`
|
|
560
551
|
- `runLocalJskit(appRoot, args = [], { stdout, stderr, createCliError, quiet = false } = {})`
|
|
561
|
-
- `runLocalJskitAsync(appRoot, args = [], { stdout, stderr, createCliError, quiet = false } = {})`
|
|
562
|
-
- `resolveLocalRepoRoot({ appRoot = "", explicitRepoRoot = "" } = {})`
|
|
563
|
-
- `discoverLocalPackageMap(repoRoot = "")`
|
|
564
|
-
- `linkPackageBinEntries({ appRoot, packageDirName, sourceDir, stdout } = {})`
|
|
565
|
-
- `resolveSymlinkType()`
|
|
552
|
+
- `runLocalJskitAsync(appRoot, args = [], { env = {}, stdout, stderr, createCliError, quiet = false } = {})`
|
|
566
553
|
Local functions
|
|
567
554
|
- `ensureCommandSucceeded(result, label, { createCliError, cwd = "", stdout, stderr, quiet = false } = {})`
|
|
568
555
|
|
|
@@ -752,11 +739,11 @@ Local functions
|
|
|
752
739
|
- `collectPlacementComponentTokensFromManagedRecords(installedPackageRecords = [])`
|
|
753
740
|
- `renderWrappedShellCommand(binaryName, args = [], { maxWidth = 100, continuationIndent = " " } = {})`
|
|
754
741
|
- `runLocalProjectBinary(binaryName, args = [], { appRoot, io, pathModule = path, createCliError, explanation = "", dryRun = false } = {})`
|
|
755
|
-
- `installAppDependenciesForHook({ appRoot,
|
|
742
|
+
- `installAppDependenciesForHook({ appRoot, io, pathModule = path, createCliError, dryRun = false } = {})`
|
|
756
743
|
- `resolvePackageOptionInputForInstall({ packageEntry, existingInstall, packageInlineOptions, appRoot, readFileBufferIfExists })`
|
|
757
744
|
- `validateHookResult(result = {}, { packageId = "", hookLabel = "" } = {})`
|
|
758
745
|
- `loadInstallHook({ packageEntry, appRoot, hookSpec, hookLabel = "" } = {})`
|
|
759
|
-
- `createInstallHookHelpers({ ctx, appRoot, io, appPackageJson
|
|
746
|
+
- `createInstallHookHelpers({ ctx, appRoot, io, appPackageJson } = {})`
|
|
760
747
|
- `invokeInstallHook({ packageEntry, appRoot, hookSpec, hookLabel, hookContext, createCliError } = {})`
|
|
761
748
|
|
|
762
749
|
### `src/server/commandHandlers/packageCommands/create.js`
|