ci: upgrade pnpm to v11#17116
Open
denolfe wants to merge 327 commits into
Open
Conversation
Adds optional `head` prop to RootLayout, letting consumers inject
scripts, meta tags, or links (e.g. next/script analytics) into the admin
panel's <head> from their `app/(payload)/layout.tsx`.
```tsx
import { RootLayout } from '@payloadcms/next/layouts'
import Script from 'next/script'
export default function Layout({ children }) {
return (
<RootLayout
config={config}
head={<Script src="https://example.com/analytics.js" strategy="afterInteractive" />}
importMap={importMap}
serverFunction={serverFunction}
>
{children}
</RootLayout>
)
}
```
**BREAKING: removes the deprecated `HTMLConverterFeature` and the
per-node `converters.html` API. Use `convertLexicalToHTML` and
`lexicalHTMLField` from the non-deprecated converter instead.**
## Migration
```diff
- import { HTMLConverterFeature, lexicalHTML, convertLexicalToHTML } from '@payloadcms/richtext-lexical'
+ import { lexicalHTMLField, convertLexicalToHTML } from '@payloadcms/richtext-lexical'
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
- HTMLConverterFeature({}),
],
}),
fields: [
{ name: 'content', type: 'richText' },
- lexicalHTML('content', { name: 'content_html' }),
+ lexicalHTMLField({ htmlFieldName: 'content_html', lexicalFieldName: 'content' }),
]
```
Custom nodes defining converters.html should export their converters
instead.
Lexical V4 redesign work Awaiting design: - Link modal - Inline dropdown menu --------- Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
…6557) ## Summary Adds documentation for converting legacy `var(--base)` tokens to `--spacer` tokens during UI4 migrations. The ui4 skill now includes a conversion table with common `var(--base)` multipliers and their `--spacer` equivalents, along with a spacer token value reference and conversion strategy guidance. The ui4-review skill adds `var(--base)` to its legacy variable detection and includes a quick conversion reference table that cross-references the ui4 skill for full details. `--base` is a legacy 20px spacing token being phased out in favor of the `--spacer-*` system.
Updates autosave component per v4 design. Test with `v4` and `autosave` collection.
1. Updates borders to match v4 vars 2. Adds Hierarchy to v4 test suite
Pin all remaining actions.
No changes were needed, this PR only adds the `emailAndUsername` component to the `v4` test suite for ease of review. Also adds misc updates to the v4 > components custom view.
Key changes:
- Banner types
- Default, Info → both absorbed into Default
- Error → renamed to Danger
- Brand (new)
- Warning (new)
- Added default use of ( i ) icon
## What Fixes the textarea field's error border being overridden by the default hover state. ## Why When a textarea field had a validation error, hovering over it would cause the default hover border style to override the error border, making the error state visually disappear on hover. ## Changes Updated the error state hover selector to `&:hover:not(:focus):not(:disabled)` to increase specificity and ensure the error border persists on hover.
Routine dependency upgrades across the monorepo, plus minor code adaptations for breaking changes. ### Core (`packages/payload`) - `@next/env`: `15.1.5` → `16.2.6` (major) - `jose`: `5.10.0` → `6.2.3` (major) - `pino`: `9.14.0` → `10.3.1` (major) - `file-type`: `21.3.4` → `22.0.1` (major) - `undici`: `7.24.4` → `7.25.0` - `ajv`: `8.18.0` → `8.20.0` - `ci-info`: `4.1.0` → `4.4.0` - `console-table-printer`: `2.12.1` → `2.15.0` - `get-tsconfig`: `4.8.1` → `4.14.0` - `ipaddr.js`: `2.2.0` → `2.3.0` - `pino-pretty`: `13.1.2` → `13.1.3` - `sanitize-filename`: `1.6.3` → `1.6.4` - `@types/minimist`: `1.2.2` → `1.2.5` - dev: `cross-env`: `7.0.3` → `10.1.0` ### Other packages - `@payloadcms/next`: `file-type`: `21.3.4` → `22.0.1` (major) - `@payloadcms/drizzle` + db adapters: `console-table-printer`: `2.12.1` → `2.15.0` - `@payloadcms/richtext-lexical`: - `react-error-boundary`: `4.1.2` → `6.1.1` (major) - `csstype`: `3.1.3` → `3.2.3` - `jsox`: `1.2.121` → `1.2.125` - `mdast-util-from-markdown`: `2.0.2` → `2.0.3` - `mdast-util-mdx-jsx`: `3.1.3` → `3.2.0` - `micromark-extension-mdx-jsx`: `3.0.1` → `3.0.2` ### Root / test / templates - `vitest`: `4.0.18`→ `4.1.6` (all workspaces & templates) - `@vitest/ui`: `4.1.2` → `4.1.6` - `@playwright/test`: `1.58.2` → `1.59.1` (root + all templates) - `cross-env`: `7.0.3` → `10.1.0` (all templates) (major) - `test`: `file-type`: `21.3.4` → `22.0.1` (major) ### Code adaptations - [packages/payload/src/uploads/endpoints/getFileFromURL.ts](packages/payload/src/uploads/endpoints/getFileFromURL.ts): only set `Content-Length` header when present. This was an actual bug, because '' is not a valid content-length header value. - [packages/richtext-lexical/src/field/Field.tsx](packages/richtext-lexical/src/field/Field.tsx): switch error boundary fallback to `FallbackProps` type and narrow `error` to `Error` - v6 of react-error-boundary now types it as `unknown` - [vitest.config.ts](vitest.config.ts): explicit `oxc.jsx` config so Vite 8 transforms JSX (workspace tsconfig sets `jsx: preserve` for Next.js). The vitest 4.0.18 => 4.1.6 upgrade bumps the internal vite version from 7 to 8.
Adds `inputStepper` to the v4 custom component view for ease of review. Note: currently the hover and pressed state are the same, this is how the v4 design has it.
Updates `IDLabel` component to match v4 design. - SCSS → CSS migration with design tokens - Pill-style layout with "ID" prefix in secondary color - Fixed 24px height - Added to v4 Components test page --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1214557558212518
Removes a deprecated `apiBasePath` in favour of internally handling it based on root config
Update thumbnail card to match v4 designs and add to v4 test suite.
Updates `copyToClipboard` element to v4 design.
Updates the `NoListResults` component and adds trash-enabled collection to `v4` test suite. **Testing** V4 test suite > Components > Patterns > No List Results
) With the new design, we no longer have a need for the `iconStyle` prop. This PR also removes some of the css complexity in the button. ## Changes ### Button component - Removes the `iconStyle` prop - Simplifies CSS by removing unused styles - Added all button styles to the tests/v4 components page ### Chip component (new) - Added new `Chip` component for pill-like UI elements - Semantic variants: `default`, `success`, `warning`, `danger` - `selected` prop for selection state (turns blue) - Supports `onClick`, `onRemove`, `icon`, and drag-and-drop via `elementProps` - Two sizes: `medium` (default) and `large` ### PillSelector component - Refactored to use `Chip` instead of `Button` - Improved drag-and-drop using `horizontalListSortingStrategy` for better UX in flex-wrap layouts - Converted SCSS to CSS with design tokens ## Breaking Changes - `iconStyle` prop removed from `Button` component --------- Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
Updates nav toggle to v4 design. --------- Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
#16611) file-type v22 uses `import(specifier)`, which Turbopack rejects with `Cannot find module as expression is too dynamic` when bundling. Adding it to `serverExternalPackages` keeps Next.js from bundling it and resolves the error in dev and build. <img width="1602" height="844" alt="screenshot 2026-05-13 at 13 52 18@2x" src="https://github.com/user-attachments/assets/a1b53c7c-43f6-4bc4-8477-cb829a038c0d" /> <img width="1696" height="650" alt="screenshot 2026-05-13 at 13 52 29@2x" src="https://github.com/user-attachments/assets/87208db2-bc1f-445f-b863-0823968d06c5" /> <img width="1708" height="868" alt="screenshot 2026-05-13 at 13 52 57@2x" src="https://github.com/user-attachments/assets/fce06658-6588-483a-a704-8a0c1670495e" />
Updates `locked` element to v4 design. Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
Update spinner to v4: - add new 32px size - update colour - add to `v4` test suite --------- Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
…16610) ## Summary Updates the document header bottom border to use `var(--color-border)` instead of `var(--theme-elevation-100)`. This aligns with the standardized border color variable used elsewhere in the UI.
## Summary Updates the Join field and RelationshipTable components to match the v4 design system. ## Changes ### RelationshipTable - Simplified pagination to Previous/Next buttons with "X – Y of Z" count - Updated button styles: "Add new" uses ghost, "Columns" uses secondary - Added hover state to drawer link edit icon - Fixed spacing by removing extra margin below table ### StatusCell - New component for rendering `_status` field as colored pills - Supports 4 variants: Published (blue), Draft (gray), Changed (yellow), Previously Published (gray) ### Table - Converted SCSS → CSS with `@layer payload-default` - Added condensed table variant (32px cell height, vertical borders) ### Other - Renamed `NavHamburger` → `NavSidebarToggle` with new sidebar icon - Converted SortColumn SCSS → CSS - Added `general:previous` and `general:next` translation keys --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1214061555627442 - https://app.asana.com/0/0/1214557558212614 - https://app.asana.com/0/0/1214557558212665 --------- Co-authored-by: Jessica Rynkar <jrynkar@figma.com>
## What? Fixes tooltip positioning and adds proper SVG caret with styling. ## Changes - Fixed `position-bottom` tooltip placement (was floating to top) - Fixed `position-left` and `position-right` positioning and caret rotation - Added `left` and `right` to position type union - Replaced pseudo-element caret with SVG component for better border/shadow support - Added customizable CSS variables: - `--caret-width`: caret width (default 15px) - `--caret-height`: caret height (default 7px) - `--caret-offset`: gap between tooltip and element (default 4px) - `--caret-border-color`: caret border color - Added drop-shadow filter on caret - Added 2px stroke border on caret (fill covers inner 1px for clean look)
### Summary
- Removes `useFacet` from the `connectOptions` type — Payload no longer
uses `$facet` aggregation anywhere so the option has no effect
- Cleans up the `connect.ts` workaround that was stripping `useFacet:
undefined` before passing options to mongoose
- Updates the DocumentDB deployment docs to remove the now-invalid
guidance to set `connectOptions.useFacet: false`
### Breaking change
If you have `useFacet` set in your `mongooseAdapter` config, remove it:
```diff
mongooseAdapter({
connectOptions: {
- useFacet: false,
// ...other options
}
})
Removed the toCSV and fromCSV hooks that were deprecated in v3 in favour of the new generic beforeImport and beforeExport hooks. --------- Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
# Overview
Adds a Claude Code CLI agent runner to `test/evals/` alongside the
existing direct-LLM runner. Real agent invocations exercise the Payload
skill the way users actually consume it: progressive disclosure of
`SKILL.md` + `reference/*.md` from a sandboxed workdir, rather than the
entire skill being injected into a system prompt.
Four eval lanes are now selectable via `EVAL_VARIANT`:
| Variant | Runner | Skill |
|---|---|---|
| `skill` (default) | direct LLM | system prompt |
| `baseline` | direct LLM | none |
| `agent-claude-code` | Claude Code CLI | `.claude/skills/payload/` in
workdir |
| `agent-claude-code-baseline` | Claude Code CLI | none |
## Key Changes
- **Dispatcher-based runner indirection** (`test/evals/runner/`)
- `runCodegenEval` becomes a `RunnerKind`-keyed dispatcher over
`Record<RunnerKind, CodegenRunner>`.
- Existing LLM body extracted to `runner/llm.ts` behind the new
`CodegenRunner` type.
- New `runner/claudeCode.ts` wraps the `claude` CLI with lazy init,
p-limit concurrency, a process-group-killable timeout, and
`'error'`/`'exit'` resolution guards so spawn failures (missing binary,
auth, hang) surface as actionable errors instead of test-worker
timeouts.
- **Sandboxed workdir per case** (`test/evals/runner/workdir.ts`)
- Each case gets a fresh `os.tmpdir()/payload-eval-*/` with `git init`
(fixed local identity) and an embedded skill tree copied verbatim from
`tools/claude-plugin/skills/payload/`.
- Defensive asserts refuse to run if the workdir escapes `os.tmpdir()`
or lands under `$HOME`.
- `getSkillTreeHash` walks the source tree (sorted) so skill content
changes invalidate cached results.
- **Sandboxed `claude` invocation**
- Agent spawns with `CLAUDE_CONFIG_DIR` overridden to a per-process
empty sandbox dir, blocking the developer's global `CLAUDE.md`,
installed skills, settings, and hooks from contaminating the eval.
- Auth probe at first agent-kind invocation, with a credentials-file
fallback for `~/.claude/.credentials.json` setups. Authentication
failures surface the CLI's actual stderr/stdout instead of a generic
message.
- **Cache + result type extensions** (`test/evals/cache.ts`,
`test/evals/types.ts`)
- `codegenKey` keyed on `runnerKind`, `modelId` (which encodes
`agentModel`/version for agent runs), `skillInstall`, and a conditional
skill-tree hash for runs that depend on skill content.
- `EvalResult` gains required `runnerKind` plus optional `skillInstall`,
`agentLog` (truncated), `agentExitCode`.
- `loadSkillContext` stays LLM-only; agents see the live filesystem
tree.
- **Variant taxonomy + dashboard surfacing** (`test/evals/variant.ts`,
dashboard components)
- Shared `getVariant(result)` classifies cache entries into one of four
lanes, with explicit fallback for unknown `RunnerKind` values.
- `Variant` widened to four values: `agent-baseline`, `agent-skill`,
`baseline`, `skill`.
- `CompareTable` buckets agent rows into the existing skill/baseline
columns (badge distinguishes lane in list view).
- **Scripts + docs** (`package.json`, `test/evals/README.md`)
- 14 new scripts mirroring the existing `:baseline` pattern:
`test:eval:agent`, `test:eval:agent:baseline`, and per-suite variants.
- README documents the four variants, required env vars
(`OPENAI_API_KEY` for scorer, `ANTHROPIC_API_KEY` for agent), and
optional knobs (`EVAL_AGENT_MODEL`, `EVAL_AGENT_CONCURRENCY`,
`EVAL_KEEP_WORKDIR`, `EVAL_NO_CACHE`).
## Design Decisions
**Sandbox via `CLAUDE_CONFIG_DIR`, not `--bare`.** The `--bare` flag
would force `ANTHROPIC_API_KEY`-only auth and skip keychain.
`CLAUDE_CONFIG_DIR` redirection is more invasive (it breaks macOS
keychain auth, forcing API-key use for agent lanes) but produces a
cleaner sandbox: no user skills, no global `CLAUDE.md`, no plugin
marketplace, no hooks. The trade-off is documented; agent runs require
`ANTHROPIC_API_KEY` set in the shell.
**Single-file readback, multi-file deferred.** The MVP runner enforces
"modify only `payload.config.ts`" via a prompt suffix and reads back
only that file. Multi-file agent edits (e.g. extracting a Collection
into its own file) would require validators and the scorer to operate on
a tree rather than a string, which is a separate phase.
**LLM scorer kept for agent runs.** Agent invocations produce a real
config diff that still needs grading. Building build-success scoring
would require a Payload-specific oracle the project doesn't have, so the
existing `scoreConfigChange` is reused. Consequence: both
`OPENAI_API_KEY` and `ANTHROPIC_API_KEY` are needed for agent variants.
**Per-process concurrency cap.** Agent runs are heavy (~30–120s,
external process). `pLimit(EVAL_AGENT_CONCURRENCY ?? 2)` at module scope
prevents the suite from forking dozens of `claude` processes. Vitest's
`eval` project has `fileParallelism: false`, so the module-level limiter
is process-wide.
**Verbatim skill install over concatenation.** The LLM runner injects a
concatenated `SKILL.md` + `reference/*.md` blob via system prompt
because the model has no tool access. The agent runner instead copies
the skill directory tree verbatim into
`workdir/.claude/skills/payload/`, letting the agent discover and read
reference files through its own `Read` tool. The cache key uses a
separate `getSkillTreeHash` (not the LLM concatenation) so both runners
invalidate on any skill change.
**`runnerKind` required on `EvalResult`.** Optional discriminants made
downstream code coerce via `?? 'llm'` at every read. Making it required
tightens the new-cache contract; read sites that consume legacy entries
keep the default-coercion for backward compatibility.
**`agentModel`/`agentVersion` not separate cache-key fields.** `modelId`
for agent runs is `claude-code/<agentModel>/<version>`, so version and
model changes invalidate via `modelId` alone. Adding them separately
would create silent divergence risk.
## Overall Flow
```mermaid
sequenceDiagram
participant Spec as eval.*.spec.ts
participant Variant as variantOptions.ts
participant Case as runCodegenCase
participant Dispatch as runCodegenEval
participant Agent as claudeCodeRunner
participant Workdir as workdir.ts
participant CLI as claude (CLI)
Spec->>Variant: resolveVariantOptions()
Variant-->>Spec: { kind, skillInstall, agentModel, ... }
Spec->>Case: runCodegenCase(testCase, label, opts)
Case->>Case: codegenKey({ runnerKind, modelId, skillInstall, ... })
Case->>Dispatch: runCodegenEval(instruction, starter, opts)
alt kind === 'llm'
Dispatch->>Dispatch: llmRunner.run (unchanged)
else kind === 'claude-code'
Dispatch->>Agent: claudeCodeRunner.run
Agent->>Agent: ensureInit (lazy, memoized)
Note over Agent: First call: create sandbox CLAUDE_CONFIG_DIR,<br/>capture version, auth-probe (creds-copy fallback)
Agent->>Workdir: materialize → gitInit → installSkill
Agent->>CLI: spawn(claude --print --model <m> --dangerously-skip-permissions)
Note over CLI: env: { CLAUDE_CONFIG_DIR=<sandbox> }<br/>cwd: <workdir>
CLI-->>Agent: stdout/stderr + exit code
Agent->>Workdir: readEntry(workdir)
Agent->>Workdir: cleanup(workdir)
Agent-->>Dispatch: { modifiedConfig, agentLog, agentExitCode }
end
Dispatch-->>Case: CodegenRunnerResult
Case->>Case: validateConfigTypes (tsc) → evaluateAssertions → scoreConfigChange (OpenAI)
Case-->>Spec: EvalResult (cached for next run)
```
Consolidates focus styling to use `--accessibility-focus-color` instead of direct color token references. ### Changes - Replace `--color-border-selected-strong` with `--accessibility-focus-color` in focus states - Replace `--color-border-selected` with `--accessibility-focus-color` in focus states - Update ClearIndicator to use 24px CircledXIcon ### Files updated - `packages/ui/src/elements/CodeEditor/index.css` - `packages/ui/src/elements/Collapsible/index.css` - `packages/ui/src/elements/ReactSelect/index.css` - `packages/ui/src/elements/ReactSelect/ClearIndicator/index.tsx` - `packages/ui/src/elements/ThumbnailCard/index.css` - `packages/ui/src/fields/Checkbox/index.css` - `packages/ui/src/fields/Code/index.css` - `packages/ui/src/fields/DateTime/index.css` - `packages/ui/src/fields/JSON/index.css`
…ing the document (#17030) ## What Fixes #17016. On an upload collection with drafts enabled, replacing the file and clicking **Save draft** on a published document used to: - flip the main document's `_status` from `published` to `draft` (effectively unpublishing it), and - delete the published file from storage. This only reproduced with **cloud storage adapters** (S3, Azure, GCS, R2, Vercel Blob, Uploadthing) and only when the file itself was reuploaded, which is why the earlier fix (#16853, tested with local storage) did not resolve it. ## Root cause The cloud storage `afterChange` hook persists adapter-returned upload metadata through a nested `payload.update`. Adapters such as S3 return the full `data` object from `handleUpload`, so `uploadMetadata` was the entire draft document (including `_status: 'draft'`). Because the nested update did not preserve the draft state, the draft document was written onto the **published main row**, unpublishing it. The same hook then deleted the previous file, which is still referenced by the published document. ## Fix In `packages/plugin-cloud-storage/src/hooks/afterChange.ts`: - Pass the originating draft state (`draft: isDraftSave`) to the nested `payload.update` so metadata is persisted to the draft version instead of the published main document. - Skip deleting the previous file when saving a draft over a published document (`isDraftOverPublished`), mirroring the existing core `isDraftOverPublished` guard, since the published file is still in use. Non-draft updates and collections without drafts are unaffected (`draft: false` is equivalent to the prior behavior). ## Tests Added a `draft-with-upload-cloud-storage` collection to the `versions` suite backed by a mock cloud storage adapter that mirrors the real S3 adapter (its `handleUpload` returns the full `data`). New integration tests cover: - Saving a draft with a new file does not unpublish the main document nor delete its file. - Publishing the draft afterwards correctly promotes the draft file. - A normal (non-draft) update still persists adapter metadata to the main document. ## Test plan - [x] `pnpm run test:int versions` (105 passed) - [x] New cloud storage draft tests pass - [x] Lint clean on changed files --------- Co-authored-by: German Jablonski <GermanJablo@users.noreply.github.com>
…#17033) Normalizes admin field description spacing for `radio` and Lexical `richText` fields so it matches other fields. Adds `admin.description` values to the v4 `Radio` and `RichText` test collections so this spacing is visible in test coverage. ## Radio Field ### Before <img width="471" height="612" alt="CleanShot 2026-06-17 at 11 41 39" src="https://github.com/user-attachments/assets/90972fec-4ba9-4d50-9598-20fd255e067e" /> ### After <img width="454" height="647" alt="CleanShot 2026-06-17 at 11 41 46" src="https://github.com/user-attachments/assets/6daf2a59-01d5-4351-80c8-10d1bf4b8ef5" /> ## Rich Text Field ### Before <img width="384" height="193" alt="CleanShot 2026-06-17 at 11 43 20" src="https://github.com/user-attachments/assets/53a2e810-29b5-44ad-95cd-b4fcb0b93dfb" /> ### After <img width="372" height="200" alt="CleanShot 2026-06-17 at 11 43 28" src="https://github.com/user-attachments/assets/dd5f575f-c4dc-4923-879b-01705b97eb52" /> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Styles custom upload form view for plugin-form-builder test suite. ### Before <img width="1220" height="1063" alt="CleanShot 2026-06-17 at 09 56 39" src="https://github.com/user-attachments/assets/3f87adf5-852d-492d-896e-5306d8a41a03" /> ### After <img width="1208" height="1061" alt="CleanShot 2026-06-17 at 10 07 51" src="https://github.com/user-attachments/assets/5c360822-056a-4a02-8173-d750e6c7298e" /> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Removes the suffix from the bulk publish/unpublish components in and updates internal list-selection usage to call and directly. and now accept both legacy and v4-style props through a single component surface, so custom components that only pass continue to work while the list views continue passing explicit selection state. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1214557558212719 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restores the intended padding on `.doc-controls--drawerHeaderActions` at mobile breakpoints. Inside the `@media (max-width: 1024px)` block, the base `.doc-controls` rule re-applies `padding` and `gap`. Since it shares the same specificity as `.doc-controls--drawerHeaderActions` but comes later in source order, it was overriding the modifier and reintroducing padding around the drawer header actions. Re-declaring the modifier after the base rule within the media query restores `padding: 0`, `gap: 0`, and `flex-wrap: nowrap`. #### Before <img width="609" height="256" alt="Screenshot 2026-06-17 at 12 09 41 PM" src="https://github.com/user-attachments/assets/8157ac39-ac38-478d-b004-c45f0b83c43a" /> ### After <img width="458" height="214" alt="Screenshot 2026-06-17 at 12 11 31 PM" src="https://github.com/user-attachments/assets/b4370fce-5257-4e64-a7a7-3010f77072ee" />
…shboard (#17025) Four changes, all under `test/evals/`: 1. Ability to run the direct llm evals with an anthropic API key (before, OpenAI was required). 2. Replace the ~28 `test:eval:*` scripts and the `EVAL_VARIANT` flag with **one `pnpm test:eval` command**, similar to what we did with `pnpm docker:start` 3. Reorganize the **dashboard around individual runs** instead of one confusing pile. 4. Let the **Claude Code agent runner work on machines whose org requires a login** (not an API key). ## 1. Anthropic API key support The codegen evals used to run on OpenAI only. - Added `@ai-sdk/anthropic` + Anthropic model presets, and bumped `ai` / `@ai-sdk/openai` - The `eval` project never loaded `.env` (it had no setup file); it does now, to load the api key env variable - Anthropic's structured output rejects `min`/`max` on numbers, so the score schemas drop them and clamp the values in code instead. ## 2. One `pnpm test:eval` command <img width="988" height="424" alt="screenshot 2026-06-16 at 11 53 34@2x" src="https://github.com/user-attachments/assets/3357d4e7-42bf-43da-a71c-b0dae40e3fee" /> - `EVAL_VARIANT` (which jammed "which harness", "model" and "skill on/off" into one value) is replaced by three independent options: **`EVAL_RUNNER`** (`llm` | `claude-code`), **`EVAL_SKILL`** (`on` | `off`), **`EVAL_MODEL`**. - The per-suite/per-variant scripts collapse into one launcher (`test/evals/cli.ts`, modeled on `docker:start`): run `pnpm test:eval` for an interactive picker, or pass `--runner` / `--skill` / `--model` / `--suite` to skip the prompts ## 3. Agent auth that works behind an org login The Claude Code agent runner authenticated with `ANTHROPIC_API_KEY`. That can't work on machines whose org requires a first-party login and rejects API keys. Fixed as an **automatic fallback** - the original path is untouched: - **Primary (unchanged):** API key in a fresh temp sandbox. Personal API-key setups behave exactly as before. - **Fallback:** if that's rejected, the runner uses a small login dir in your user config (`~/.config/payload-evals/claude-code` - outside the repo, so it can't be committed or wiped with eval output) and drops the API key so it uses that dir's login instead. You log in once: `CLAUDE_CONFIG_DIR=… claude auth login`. - Either way it stays a clean sandbox — your personal `~/.claude` (CLAUDE.md, skills, settings) is never used, so results aren't skewed. - The auth check runs **once before any test**, so a missing login stops the run immediately with the exact command to fix it, instead of failing every case. ## 4. Runs-based dashboard The dashboard used to lump every cached result together mixing models and skill settings - so the headline "pass rate" wasn't meaningful and "runs" were confusing. - Every eval run now gets a **`runId`**, so results group into real runs. - **Overview** lists runs, newest first. **Results** shows one run at a time (now with a Model column). **Compare** diffs any two runs, by category or by case. - **Cancelled runs are hidden.** The launcher tags a run "finished" only when it exits cleanly; a run you Ctrl+C never gets tagged, so the dashboard skips it instead of showing a half-finished run. - Fixed the "2 runs in the header vs 19 in Compare" mismatch - there's now one idea of a "run" everywhere. ## before <img width="2972" height="2076" alt="screenshot 2026-06-16 at 11 51 26@2x" src="https://github.com/user-attachments/assets/b8f89d79-6c38-4ce2-aac1-7e31862d0dd3" /> <img width="2974" height="2072" alt="screenshot 2026-06-16 at 11 51 33@2x" src="https://github.com/user-attachments/assets/4aa9f433-a5d3-4c8f-8f6f-e9fc9843598d" /> ## after <img width="2980" height="2076" alt="screenshot 2026-06-16 at 11 50 49@2x" src="https://github.com/user-attachments/assets/1d601ad5-bd48-4b08-a687-8dee95dd2030" /> <img width="2972" height="2078" alt="screenshot 2026-06-16 at 11 50 56@2x" src="https://github.com/user-attachments/assets/f6fd84c5-0159-4bd0-a690-fed44dbdd9a9" /> <img width="2976" height="2072" alt="screenshot 2026-06-16 at 11 51 07@2x" src="https://github.com/user-attachments/assets/8678bcba-4aea-4db6-bcf8-250e1d69ae87" />
fixes : #16987 ## Which branch should this PR target? - **`main`** — This is a translation bug fix in the current codebase (`4.0.0-beta.0`). The same issue also affects v3 users, so maintainers can backport to `3.x` if needed. ## What? Fixes incorrect translations in the Burmese (`my`) locale where ~88 strings were Malay/Indonesian instead of Burmese (Myanmar). The ISO 639-1 code `my` is Burmese/Myanmar, not Malay (`ms`). Several keys in `packages/translations/src/languages/my.ts` contained Malay text mixed in with correct Burmese translations. ## Why? Users selecting Burmese in the admin panel were seeing Malay/Indonesian UI text, which is confusing and makes the locale effectively broken for Burmese speakers. **Example:** Before (Malay): ```ts my.translations.general.noResultsDescription // "Sama ada tiada yang wujud atau tiada yang sepadan dengan penapis yang anda tentukan di atas." ``` After (Burmese): ```ts my.translations.general.noResultsDescription // "ရလဒ်မရှိပါ။ ဒေတာမရှိသေးသည်ဖြစ်စေ အထက်တွင် သတ်မှတ်ထားသော စစ်ထုတ်မှုများနှင့် ကိုက်ညီမှုမရှိသည်ဖြစ်စေ ဖြစ်နိုင်ပါသည်။" ``` ## How? Updated `packages/translations/src/languages/my.ts` and replaced Malay/Indonesian strings with proper Burmese translations across these namespaces: - `authentication` (6 strings) — e.g. `accountVerified`, `logoutSuccessful` - `dashboard` (5 strings) — e.g. `deleteWidget`, `editDashboard` - `error` (7 strings) — e.g. `documentNotFound`, `restoringTitle` - `general` (30+ strings) — e.g. `aboutToPermanentlyDelete`, `noResultsDescription`, `unsavedChanges` - `hierarchy` (6 strings) — e.g. `moveItemsToRootConfirmation` - `localization` (5 strings) - `upload` (3 strings) - `validation` (3 strings) - `version` (10+ strings) Also fixed `general.restoring`, which contained a corrupted Malay system-prompt fragment instead of the expected `Restoring...` string. All interpolation placeholders (`{{id}}`, `{{label}}`, `{{title}}`, etc.) and Payload-specific terms were preserved. ## Test plan - [x] Verified `my.translations.general.noResultsDescription` returns Burmese text - [x] Verified other updated keys (e.g. `authentication.accountVerified`, `dashboard.deleteWidget`) return Burmese text - [x] Confirmed no remaining Malay/Indonesian strings in `my.ts` - [ ] Switch admin locale to Burmese and spot-check affected UI areas (dashboard, trash/restore flows, version comparison)
…delete permission (#16989) Fixes #16975 ## What? `editMenuItems` defined on a collection were silently hidden when both `create` and `delete` permissions were denied, because the three-dot menu's visibility (`showDotMenu`) was gated entirely on those permissions. ## Why? Custom `editMenuItems` represent arbitrary business logic unrelated to CRUD operations. A collection that blocks creation (e.g. webhook-only) and deletion (e.g. audit trail) should still expose custom actions like "Resend confirmation email". ## How? Added `EditMenuItems` as an additional condition in `showDotMenu` so the popup renders whenever custom items are present, regardless of create/delete access.
Changes `SortColumn` header content to use `inline-flex` so hover styling only affects the text/icon cluster, not the full header cell hit area. This keeps header color changes scoped to intentional hover on the sort control and prevents visual bleed across the column header. ### Before https://github.com/user-attachments/assets/55565304-391c-48e5-9cbb-b42bfcd30c2d ### After https://github.com/user-attachments/assets/7795b5c0-bc34-4bdb-8202-4c3504489bbd
Makes the linked cell in drawer-rendered tables fill the entire `<td>`,
matching the list view where the whole cell is clickable rather than
just the text.
In drawer tables the linked column is the first cell, since drawers have
no row-select checkbox column. The `td:first-child` padding rules - in
particular the drawer variant `.drawer .table td:first-child`
(specificity `(0,3,1)`) - overrode `.cell--linked { padding: 0 }`
(specificity `(0,2,0)`), leaving padding on the `<td>` that the inner
element did not cover. Drawer linked cells also render a `<button>`
(with an `onSelect` handler) instead of an `<a>`, and a button with
`width: auto` only sizes to its text, so only the text was clickable.
The fix scopes the linked-cell rules to `td.cell--linked` with explicit
`:first-child` / `:last-child` padding resets so they win over the cell
padding rules, adds `width: 100%` to the inner `<a>`/`<button>` so
buttons stretch like anchors, and moves the first-cell gutter onto the
inner element so text alignment is unchanged.
Before:
The drawer linked cell renders a `<button>` sized to its text — only the
text is clickable.
After:
The `<button>` fills the full cell (width and height) with `cursor:
pointer`, matching the list view `<a>` behavior.
Rebuilds the Query Presets edit drawer to drive its group-by, columns,
and filter inputs with the list view's own controls — `GroupByButton`,
`ColumnSelectionButton`, and `WhereBuilder` — so editing a preset uses
the exact controls it configures.
Previously the drawer rendered a separate `GroupByBuilder` and a
`PillSelector`-based column picker that duplicated — and drifted from —
the real list controls. To share the real controls without state bleed,
they are first decoupled from `ListQuery`/`TableColumns` context and
turned into controlled components, then wired into the drawer via form
fields. `GroupByBuilder` and the old column picker are removed.
## Decoupling the controls from context
The list view controls — `WhereBuilder`, `GroupByButton`, and
`ColumnSelectionButton` — are decoupled from `ListQuery` and
`TableColumns` context, turning them into controlled components driven
purely by `value`/`columns` props and `onChange` callbacks.
Each control previously carried an internal `isFormMode` branch: when an
`onChange` prop was passed it wrote into form state, otherwise it
reached into `useListQuery`/`useTableColumns` and mutated the live
query. That dual path made the components hard to reuse and was the
source of state bleed between the list view and the Query Presets
drawer.
The context wiring now lives in three thin wrappers —
`ListWhereBuilder`, `ListGroupByButton`, and `ListColumnSelectionButton`
— that read from `useListQuery`/`useTableColumns` and pass
`value`/`columns` + `onChange` down to the underlying control. The list
view and `RelationshipTable` render the wrappers; the Query Presets
drawer renders the bare controls and binds them to form fields via
`useField`.
Before:
```tsx
// One component, two code paths
const isFormMode = typeof onChange === 'function'
const value = isFormMode ? valueProp : listQuery.query?.groupBy
const handleClear = useCallback(() => {
if (isFormMode) {
onChange('')
return
}
void listQuery.refineListData({ groupBy: '' })
}, [isFormMode, onChange, listQuery])
```
After:
```tsx
// GroupByButton — controlled, no context
const handleClear = useCallback(() => {
onChange('')
close()
}, [onChange, close])
// ListGroupByButton — wires context to the control
const { query, refineListData } = useListQuery()
return (
<GroupByButton
onChange={(groupBy) => refineListData({ groupBy, page: 1 })}
value={query?.groupBy ?? ''}
/>
)
```
`value`/`columns` and `onChange` are now required on the underlying
controls, so the call site always owns the state.
## Control composition
The Columns and Group By controls now share the same folder structure:
`index.tsx` exports the trigger button, `Popup.tsx` exports the panel
content.
- `ColumnsButton` → `ColumnSelection/index.tsx`
(`ColumnSelectionButton`)
- `ColumnSelector` → `ColumnSelection/Popup.tsx`
(`ColumnSelectionPopup`)
- `GroupByControl` → `GroupBy/index.tsx` (`GroupByButton`) +
`GroupBy/Popup.tsx` (`GroupByPopup`), splitting the previously inline
panel out of the button
- `ListColumnsButton` → `ListColumnSelectionButton`
- `ListGroupByControl` → `ListGroupByButton`
## Drawer layout
The drawer layout is regrouped to match the list controls: a `presets`
heading sits inline with the group-by and columns buttons via the new
`QueryPresetsHeading` UI field, and an `access` heading replaces the old
`Sharing settings` group label above the sharing controls.
## Drawer-specific `WhereBuilder` fixes
Fixes drawer-specific behavior surfaced by reusing the control:
- Condition mutations `structuredClone` the `value` prop before
splicing, so editing filters in the drawer no longer corrupts the list
view's `where` state that seeded it.
- `WhereBuilder` adds its first filter on a single click, where the
committed placeholder row was previously rejected by validation.
- Clearing a value on the drawer's uncommitted placeholder row no longer
re-commits an empty condition — empty `value` edits are ignored only on
that virtual first row, while field/operator picks still build it.
- The per-row remove button is now always rendered but **disabled** on
the drawer's uncommitted placeholder row (where there is nothing to
remove), rather than being hidden. A committed condition or a closable
panel keeps it enabled. This drops the previous `--no-actions`
grid-track collapse, keeping the layout stable.
- Renames the `WhereBuilder` `onClose` prop to `onEmpty`, since it
signals that the last condition was removed rather than closing anything
— the list view uses it to close the filter panel.
## Other supporting fixes
- `Popup` now detects a `position: fixed` ancestor (e.g. an open
`Drawer`) and positions itself with `fixed`, so popups no longer drift
when the background scrolls.
- The list `body` scroll-lock is reinforced in CSS, since faceless-ui's
JS lock could be cleared early when a sibling modal mounts closed.
- `DocumentDrawerHeader` skips the meta row entirely when there is no
status, timestamp, or autosave to show.
## Related
- Adds `ListWhereBuilder`, `ListGroupByButton`, and
`ListColumnSelectionButton` wrapper components.
- `QueryPresetsWhereField` now resolves `fields` from `getEntityConfig`
since the bare `WhereBuilder` no longer pulls them from context.
- Adds the `general:access` and `general:presets` translation keys
across all locales.
---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
- https://app.asana.com/0/0/1215779195608944
---------
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
…17060) ## Summary Markdown imported via `$convertFromMarkdownString` silently dropped CommonMark [hard line breaks](https://spec.commonmark.org/0.31.2/#hard-line-breaks): a line ending in two-or-more trailing spaces or a trailing backslash was merged into the previous line as a soft wrap, losing the intended `<br>`. This was reported by a customer importing markdown content. This backports the relevant parts of upstream lexical [PR #8402](facebook/lexical#8402) into Payload's forked `@lexical/markdown`. We maintain a fork of `@lexical/markdown` (rather than re-exporting it), and it is pinned to `lexical@0.41.0`, so the upstream fix (shipped in `0.45.0`) is not picked up by a dependency bump alone. ### What changed - **`MarkdownTransformers.ts`** - Adds `parseMarkdownHardLineBreak` to detect a trailing `\` or 2+ spaces marker. - Adds a `hardLineBreakState` node-state config and `$createMarkdownLineBreakNode`, which strips the marker from the text and stores it on the created `LineBreakNode`. - `normalizeMarkdown` no longer merges a following line into a line that ends with a hard break marker, and preserves the marker when merging. - The `QUOTE` transformer uses `$createMarkdownLineBreakNode` for multi-line quote joins. - **`MarkdownImport.ts`** uses `$createMarkdownLineBreakNode` instead of a plain `$createLineBreakNode` when joining adjacent blocks. - **`MarkdownExport.ts`** re-emits the stored marker (` \n` or `\\\n`) on export so the value round trips; line breaks without a marker still export as a plain `\n` (unchanged behavior). ### Note on `resetOnCopyNode` Upstream sets `resetOnCopyNode: true` on the node state. That option does not exist in `lexical@0.41.0` (it was added in `0.42.0`), so it is intentionally omitted here. It only affects copy/paste node duplication and is not required for correct markdown import/export. This is the v3/v4 backport into the fork. A separate, larger change (removing the fork and consuming upstream `@lexical/markdown` directly) is planned for v4 only and is not part of this PR. ## Test plan - [x] Added `LexicalMarkdownHardLineBreak.spec.ts` covering normalization, import (marker stripped + `LineBreakNode` created), and export round trip for both the trailing-spaces and backslash forms, plus a soft-wrap regression check. - [x] `vitest --project unit packages/richtext-lexical` (existing + 10 new tests pass). - [x] `eslint` + `prettier` clean on changed files. --------- Co-authored-by: German Jablonski <GermanJablo@users.noreply.github.com>
…keys directly (#17041) This PR removes MCP-specific API key management and makes MCP use Payload API keys directly. While this is a UX downgrade in the short term, it will be beneficial in the long run because - we're keeping the mcp plugin clean and _consistent_ with the rest of the payload code base / existing access control - we'll be prioritizing development of an RBAC plugin, which will provide a better, and more importantly consistent rbac experience that works for _both_ mcp tools and normal payload access control - we'll be able to introduce this without doing any breaking changes or keeping deprecated legacy code floating around ## MCP API key collection removed The `payload-mcp-api-keys` collection is gone, along with its admin UI, settings-menu entry, access field, custom API-key field, empty state, and plugin-specific translations. There is no separate MCP key document anymore. MCP requests now authenticate as the Payload user attached to the API key. ## Payload API key auth HTTP MCP requests now use Payload's normal API-key header: ```txt Authorization: <authCollectionSlug> API-Key <key> ``` MCP accepts any Payload user authenticated through API-key auth. The old `userCollection` option was removed, and the plugin no longer adds a default users collection implicitly. Fixes #16572 ## Tool access Tool visibility now comes from code instead of per-key admin settings. Built-in collection and global tools follow Payload access control, so callers only see operation tools they can use. Tools, prompts, resources, and built-in tool overrides can also define a new `access` callback for MCP-item-specific rules. This should be used instead of the previous API key collection, and can be used to build your own RBAC, which is how normal payload access control works. ## Migration Enable `auth.useAPIKey` on the auth collection whose users should call MCP, then use those user API keys for `/api/mcp`. Existing `payload-mcp-api-keys` documents no longer authenticate. The collection config has been removed and will need a database migration. `overrideApiKeyCollection` and `userCollection` were removed. --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1215781760309756
#17083) Update the Payload Claude skill so agents stop eagerly regenerating types. The dev server auto-generates types (`typescript.autoGenerate` defaults to `true`) and `payload build` now generates the import map and types before `next build`, so manual `generate:types` is rarely needed.
This adds the missing MCP built-in tools for counting documents, duplicating documents, finding distinct values, and working with collection and global versions. Version tools are only registered when at least entity has versioning enabled. Fixes #15881 --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1215843579924530 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stretches the `.card__click` overlay anchor to fill the entire card so the full surface is hoverable and clickable. The overlay is a `Button` rendered at the default `size="medium"`, so `.btn--size-medium` applies a fixed `height`. That selector has the same specificity as `.card__click` (`0,1,0`) but is emitted later in the production bundle, so it won the cascade and collapsed the anchor to button height - leaving only the top portion of the card interactive. The tie resolved differently in dev, so the bug only surfaced in production builds. Raising the overlay selector to `.card .card__click` (`0,2,0`) makes the `height: auto` override win deterministically, independent of bundle order, so `inset: 0` stretches the anchor across the full card. Before: <img width="569" height="314" alt="Screenshot 2026-06-22 at 2 26 00 PM (1)" src="https://github.com/user-attachments/assets/a78e9938-0e50-463c-a49a-c0b81f3a3d96" /> After: <img width="469" height="210" alt="Screenshot 2026-06-22 at 2 40 54 PM" src="https://github.com/user-attachments/assets/359bc4d5-3d4d-49ff-aa85-b57ccb80fafe" />
#17093) ## What Adds `prepare-run-test-against-prod:ci:frozen`, a variant of `prepare-run-test-against-prod:ci` that installs the prod test app with a frozen lockfile. It is identical to `prepare-run-test-against-prod:ci` except that it does not delete `test/pnpm-lock.yaml` and runs `pnpm i --frozen-lockfile` instead of a plain `pnpm i`. Both still pack the workspace packages, run `test/setupProd.ts`, and install with `--ignore-workspace`. ## Why The existing `:ci` script deletes the lockfile and installs non-frozen on purpose. As I understand it, that is so the prod test resolves the newest version of every transitive dependency at install time, simulating a real consumer installing published Payload from scratch. That surfaces breakages from upstream dependency updates early, before they reach Payload users, which is valuable for Payload's own CI. That same behavior is a problem when the install does not go straight to the public npm registry but through a private mirror or proxy (for example a corporate Artifactory). Mirrors sync from npm per package and can briefly lag, so a non-frozen resolution can request a freshly published transitive version that the mirror has not synced yet and fail with `ERR_PNPM_NO_MATCHING_VERSION`. The version exists on npm but not yet on the mirror, and because the resolution always chases the newest, the failure recurs intermittently and is outside the consumer's control. For that case, being on the bleeding edge is not the goal; reproducibility is. A frozen install against a committed lockfile requests exact, already-mirrored versions, so it never asks the mirror for something it does not have. This variant gives mirror-based consumers (such as Figma's CI, which installs Payload through Artifactory to run the Content API compatibility suite) a deterministic option, without changing the default. ## Notes - `test/pnpm-lock.yaml` is not committed in this repo, so this script is meant for consumers that generate and commit their own `test/pnpm-lock.yaml`, resolved through their mirror. Generate it once with the existing non-frozen flow, commit it, then use `:frozen` on subsequent runs and refresh it when dependencies change. - No existing script or default behavior changes; this only adds an opt-in script.
Removed deprecated `chainMethods` exported from `@payloadcms/drizzle`. This was deprecated in favor of using drizzle dynamic query building in PR #11923 --- - To see the specific tasks where the Asana app for GitHub is being used, see below: - https://app.asana.com/0/0/1214260152843240
… payload skill (#17102) # Overview Updates the `payload` skill with guidance on versions, `slugField()`, and `position: 'sidebar'`. ## Key Changes #### Encourage drafts/versions by default `versions: { drafts: true }` is now the default for content collections across `SKILL.md` and the collections reference. Examples rely on the auto-injected `_status` field and drop hand-rolled `status` selects. #### Default to `slugField()` for slugs All slug examples use the `slugField()` helper instead of a hand-rolled `{ name: 'slug', type: 'text', unique: true }` field, with a note that the helper generates from `title` by default and needs `slugField({ useAsSlug: 'name' })` when there is no `title` field. #### Add `position: 'sidebar'` guidance New guidance on sidebar placement: short, at-a-glance fields (status, category, author, date) belong there; long fields (description, rich text) stay in the main document area. #### Consistency sweep Removed remaining anti-pattern examples across `FIELDS.md`, `COLLECTIONS.md`, and `ACCESS-CONTROL.md` (custom publish-state selects, hand-rolled slugs) so the reference docs match the new defaults.
# Overview Resolves the high-severity vulnerabilities reported by `pnpm audit --prod` (via `.github/workflows/audit-dependencies.sh high`) that are fixable at the dependency level. ## Key Changes **Bump undici to 7.28.0 in packages/payload** Direct bump within the same major. Fixes GHSA-vmh5-mc38-953g / CVE-2026-9697 **Let @vercel/blob's undici resolve to 6.27.0** Dropping the root pin lets the existing `^6.23.0` range resolve to the patched version. Fixes GHSA-vxpw-j846-p89q **Bump nodemailer to ^9.0.1 in email-nodemailer and payload-cloud** Direct bump across the 8 to 9 major. Fixes GHSA-p6gq-j5cr-w38f **Override @types/request>form-data to ^2.5.6** Parent-scoped override. Fixes GHSA-hmw2-7cc7-3qxx / CVE-2026-12143 **Drop manual Content-Length headers from the storage-s3 client-upload tests** undici 7.28.0 rejects a manually-set `Content-Length` on a `fetch` body, so the tests let `fetch` derive it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Upgrades the package manager from pnpm 10 to pnpm 11 (
11.9.0) on the 3.x line, matching the main-line upgrade in #17114.The load-bearing change: pnpm v11 silently ignores the
pnpmfield inpackage.json, so configuration that lived there is relocated topnpm-workspace.yaml.Key Changes
Relocate root pnpm config to
pnpm-workspace.yamloverridesandonlyBuiltDependenciesmoved out of the now-ignoredpackage.json#pnpmfield intopnpm-workspace.yamlasoverrides:andallowBuilds:. Thepnpmfield is removed.Migrate the
test/workspace configtest/installs standalone viapnpm i --ignore-workspace, so itsgraphqloverride moves to a newtest/pnpm-workspace.yamlfor the same reason.Allow pnpm 11 in template engines
templates/blankandtemplates/websitepinnedengines.pnpmto^9 || ^10, which rejected v11 across the whole workspace. Widened to^9 || ^10 || ^11.Design Decisions
pnpm v11's supply-chain defaults (
minimumReleaseAge,blockExoticSubdeps) are left on; install completed without needing any opt-out, and the lockfile is unchanged.The
$-reference override syntax is retained. It is deprecated in v11 but still functional; converting to thecatalog:protocol is tracked separately (build/pnpm-catalog).