@jskit-ai/agent-docs 0.1.83 → 0.1.85

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.
@@ -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 with the dev auth bypass
964
+ ### Authenticated Playwright testing
965
965
 
966
- JSKIT ships a development-only auth bootstrap path specifically so authenticated UI can be verified in Playwright without depending on a real live login flow through an external auth provider.
966
+ JSKIT supports two intentionally separate authenticated-browser paths:
967
967
 
968
- This is the standard path the agent should use for authenticated browser tests:
968
+ - a private localhost exchange for direct local Playwright runs
969
+ - runner-provided Playwright storage state for managed previews
969
970
 
970
- - enable the dev auth bypass in development
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 feature is intentionally narrow.
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
- - It is development-only.
979
- - It must never be enabled in production.
980
- - JSKIT rejects boot if `AUTH_DEV_BYPASS_ENABLED=true` while `NODE_ENV=production`.
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
- The environment variables are:
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 outside production, the app exposes:
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 response is intentionally small:
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
- ```json
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
- Behind the scenes, JSKIT creates the same HTTP-only auth cookies that the normal login flow would create. That means Playwright should not try to read raw tokens. It should bootstrap the session in the browser context, then navigate normally.
1013
+ ```ts
1014
+ import { expect, test } from "@playwright/test";
1015
+ import { loginAsExistingUser } from "@jskit-ai/auth-web/test/playwright";
1020
1016
 
1021
- One subtle point matters here:
1017
+ test("authenticated contacts filters", async ({ page }) => {
1018
+ await loginAsExistingUser(page, { email: "ada@example.com" });
1022
1019
 
1023
- - `/api/dev-auth/login-as` is still an unsafe `POST`
1024
- - JSKIT still expects a CSRF token
1025
- - the browser can get that token from `/api/session`
1020
+ await page.goto("/w/acme/admin/contacts");
1021
+ await expect(page.getByRole("heading", { name: "Contacts" })).toBeVisible();
1022
+ });
1023
+ ```
1026
1024
 
1027
- So the normal Playwright shape is:
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
- 1. open a same-origin page first
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
- For example:
1029
+ #### Managed preview authentication
1035
1030
 
1036
- ```ts
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
- await page.evaluate(async ({ email }) => {
1040
- const sessionResponse = await fetch("/api/session", {
1041
- credentials: "include"
1042
- });
1043
- if (!sessionResponse.ok) {
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
- const sessionPayload = await sessionResponse.json();
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
- const loginResponse = await fetch("/api/dev-auth/login-as", {
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
- if (!loginResponse.ok) {
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
- await page.goto("/w/acme/admin/contacts");
1069
- ```
1045
+ #### Recording the result
1070
1046
 
1071
- In practice, the preferred wrapper is:
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
- For local pre-merge review, the next step after recording that Playwright run is usually:
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
- Advanced CI pipelines can use the same `--against` contract too, but JSKIT does not scaffold hosted auth/database/browser verification by default.
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
 
@@ -210,6 +210,8 @@ Published JSKIT libraries and tooling support Node.js 22 from 22.12.0 onward, No
210
210
 
211
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`, `jskit app link-local-packages`, and `jskit app release`, the app keeps the nice `npm run` shortcuts while the maintained behavior stays in the installed CLI package.
212
212
 
213
+ 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.
214
+
213
215
  `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
216
 
215
217
  The starter scaffold also writes `.github/workflows/jskit-verify.yml`. JSKIT generates and owns that workflow as a projection of installed package `ci` contracts. The baseline runs checkout, Node 26 setup, `npm ci`, and `npm run verify`. Packages can add job environment values, service containers, and explicit `before-verify` steps without taking ownership of the whole YAML file. For example, the database runtime adds migrations before verification. Its MySQL driver adds a MariaDB service with synthetic CI-only credentials and `DB_CLIENT=mysql2`; its Postgres driver adds the equivalent Postgres service and `DB_CLIENT=pg`.
@@ -106,6 +106,25 @@ npm run release -- --registry https://registry.example.com
106
106
 
107
107
  When the app declares npm workspaces, the command asks npm for the workspace graph and aligns JSKIT references in each workspace `package.json` and `package.descriptor.mjs` to the latest major range, such as `0.x`. Descriptor dependency mutations are included whether they use a direct string or a conditional `{ version, when }` record. It then runs `npm update --workspaces` for those packages so `package-lock.json` reflects the aligned ranges. Non-JSKIT dependencies and the public descriptor format are left alone.
108
108
 
109
+ Updating npm packages is only half of a JSKIT upgrade. Package descriptors also own managed files, source and text mutations, lock metadata, migrations, and CI contributions. After installing newer root packages, `update-packages` compares their target versions with the installed records in `.jskit/lock.json`. It reapplies each changed installed package through the same `jskit update package ...` lifecycle used for a manual update. The newly installed local CLI performs that work, saved package options are reused, and customized app-owned files remain protected by the normal ownership checks. The updater reloads the lock between packages, so a dependency already upgraded while reapplying another package is not applied twice.
110
+
111
+ This means the normal existing-app upgrade is one command:
112
+
113
+ ```bash
114
+ npm run jskit:update
115
+ ```
116
+
117
+ 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
+
119
+ There is one unavoidable bootstrap detail for apps whose installed CLI predates managed package reapplication: a CLI process that is already running cannot adopt code npm installs underneath it. Upgrade the CLI once, then run the app-wide update with the new process:
120
+
121
+ ```bash
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.
127
+
109
128
  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:
110
129
 
111
130
  ```bash
@@ -781,7 +800,7 @@ Good times to run it manually include:
781
800
  - when a package appears installed in the lock but starts behaving as if it is missing
782
801
  - when you want a fast JSKIT-specific health check without waiting for a full test suite
783
802
 
784
- One important nuance: `doctor` is checking for broken JSKIT ownership and visibility, not trying to stop you from editing app-owned files. For example, a managed file that still exists but whose contents changed is normally fine. The problem is when JSKIT expects a managed file to exist and it is gone, or when the installed package state no longer resolves cleanly.
803
+ One important nuance: `doctor` is checking for broken JSKIT ownership and visibility, not trying to stop you from editing app-owned files. For example, a managed file that still exists but whose contents changed is normally fine. An app-owned file explicitly installed by a package can also live under `packages/main/src/server/` without being mistaken for undeclared domain logic. The file remains part of managed state, so deleting it is still an error. Unrelated server files under `packages/main` continue to receive the normal feature-lane warning.
785
804
 
786
805
  There is one intentional exception for user-facing UI work.
787
806
 
@@ -799,6 +818,8 @@ That command does two things:
799
818
  - runs the targeted Playwright command you give it
800
819
  - writes a receipt describing the verified feature, auth mode, and current dirty UI file set
801
820
 
821
+ 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.
822
+
802
823
  `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`.
803
824
 
804
825
  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:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/agent-docs",
3
- "version": "0.1.83",
3
+ "version": "0.1.85",
4
4
  "description": "Distributed JSKIT agent references, prompts, guides, and generated reference maps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -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, the browser checks should cover horizontal overflow, clipped text, invisible text, duplicate navigation, broken route placement, and tap targets under 48 px.
15
- - Apps with `shell-web` installed should start from the generated `tests/e2e/adaptive-shell.spec.ts` smoke and extend it for feature-specific assertions.
16
- - Record that Playwright run with `jskit app verify-ui --command "<playwright command>" --feature "<label>" --auth-mode <mode>`.
17
- - `jskit doctor` expects `.jskit/verification/ui.json` to match the current dirty UI file set when UI files are changed.
18
- - For local pre-merge review, follow the recorded Playwright run with `jskit doctor --against <base-ref>` so `doctor` compares against the branch delta instead of only the local dirty worktree.
19
- - Advanced CI setups may also use `--against <base-ref>`, but JSKIT does not scaffold hosted browser/auth/database verification by default.
20
- - Do not rely on a live external auth provider for Playwright verification of normal app features.
21
- - For authenticated UI in the standard JSKIT auth stack, use the development-only dev auth bypass route instead.
22
- - The standard route is `POST /api/dev-auth/login-as`.
23
- - The request body must include either `{ userId }` or `{ email }`.
24
- - The route is available only when `AUTH_DEV_BYPASS_ENABLED=true` and `AUTH_DEV_BYPASS_SECRET` is set outside production.
25
- - This route must never be enabled in production.
26
- - Because the route is an unsafe POST, fetch `csrfToken` from `/api/session` first and send it back as the `csrf-token` header.
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
- npx jskit app verify-ui \
34
- --command "npx playwright test tests/e2e/contacts.spec.ts -g filters" \
35
- --feature "contacts filters" \
36
- --auth-mode dev-auth-login-as
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
- Local pre-merge follow-up:
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
- npx jskit doctor --against origin/main
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 command itself should follow this setup shape when login is needed:
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
- ```ts
48
- await page.goto("/");
49
-
50
- await page.evaluate(async ({ email }) => {
51
- const sessionResponse = await fetch("/api/session", {
52
- credentials: "include"
53
- });
54
- if (!sessionResponse.ok) {
55
- throw new Error(`Session bootstrap failed: ${sessionResponse.status}`);
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
- After that bootstrap:
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
- - navigate to the protected page
83
- - exercise the new UI behavior
84
- - assert the actual outcome the chunk introduced
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
- - authenticated UI work was left untested because no local auth bootstrap path was available and that gap was not called out
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()`
@@ -572,7 +572,9 @@ Exports
572
572
 
573
573
  ### `src/server/commandHandlers/appCommands/updatePackages.js`
574
574
  Exports
575
+ - `collectChangedInstalledPackageIds(lock = {}, latestVersions = new Map())`
575
576
  - `formatElapsedTime(elapsedMilliseconds = 0)`
577
+ - `reapplyChangedInstalledPackages({ appRoot, createCliError, dryRun, latestVersions, loadLockFile, stderr, stdout })`
576
578
  - `runAppUpdatePackagesCommand(ctx = {}, { appRoot = "", options = {}, stdout, stderr })`
577
579
  - `runWithProgress(task, { activity, progressIntervalMs = PROGRESS_INTERVAL_MS, stdout, step } = {})`
578
580
  Local functions