Skip to content

ci: upgrade pnpm to v11#17116

Open
denolfe wants to merge 327 commits into
3.xfrom
build/pnpm-v11
Open

ci: upgrade pnpm to v11#17116
denolfe wants to merge 327 commits into
3.xfrom
build/pnpm-v11

Conversation

@denolfe

@denolfe denolfe commented Jun 25, 2026

Copy link
Copy Markdown
Member

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 pnpm field in package.json, so configuration that lived there is relocated to pnpm-workspace.yaml.

Key Changes

Relocate root pnpm config to pnpm-workspace.yaml

overrides and onlyBuiltDependencies moved out of the now-ignored package.json#pnpm field into pnpm-workspace.yaml as overrides: and allowBuilds:. The pnpm field is removed.

Migrate the test/ workspace config

test/ installs standalone via pnpm i --ignore-workspace, so its graphql override moves to a new test/pnpm-workspace.yaml for the same reason.

Allow pnpm 11 in template engines

templates/blank and templates/website pinned engines.pnpm to ^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 the catalog: protocol is tracked separately (build/pnpm-catalog).

jacobsfletch and others added 30 commits May 10, 2026 21:52
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`
GermanJablo and others added 24 commits June 17, 2026 16:29
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.