The notes now land in a source-controlled changelog instead of a scratch file
that rides the release branch. One file per lane, because the lanes version
independently: a shared file would interleave `1.70.0`, `angular/0.5.0` and
`channels/0.9.0` into one unreadable sequence.
monorepo -> CHANGELOG.md
angular -> packages/angular/CHANGELOG.md
channels -> packages/channels/CHANGELOG.md
`write-changelog.ts` prepends this release's section on the release branch,
create-pull-request commits it (a tracked file, always staged), and
`extract-release-notes.ts` reads the section back in the publish job as the
GitHub Release body. The changelog is therefore both the durable record and the
review surface: editing a section on the release PR changes what ships.
release-notes.md goes back to being ignored, so the same notes never exist as
two editable copies.
Also deletes 29 changesets-era changelogs that no tooling had written since
April. They stopped at 1.55.2 while the lane shipped 1.69.3, and
packages/angular/CHANGELOG.md still claimed 1.54.3 from before that lane split
onto its own 0.x line. Their content stays recoverable from git history. A test
pins the tracked changelog set to the lanes so they cannot creep back and
contradict the real versions.
Extraction never fails the publish job: it runs after npm publish, so a miss
annotates loudly and falls through to the existing bodyless-release fallback
rather than stranding the tag.
Committed with --no-verify: the pre-commit nx lane cannot run in this worktree
(packages/core and packages/channels-ui have no node_modules, and
`nx run @copilotkit/core:build` fails identically with the tree clean). The only
change under packages/** is deleting orphan markdown that no build or test
reads.
## Summary
Follow-up to #5025 addressing @marthakelly's [review
suggestion](https://github.com/CopilotKit/CopilotKit/pull/5025#discussion_r3307285578):
add oxlint `RuleTester` coverage for the
`copilotkit/no-single-arg-zod-record` rule. Since the underlying Zod 4
incompatibility is **type-level** (no runtime test can catch a
regression), this lint rule is the real safety net, so it's worth
testing directly.
## Cases (via `oxlint/plugins-dev` `RuleTester`)
**valid**
- two-arg `z.record(z.string(), z.unknown())` — no false positive
- two-arg with `.optional()` chain
- single-arg `.record()` on a non-`z` object (`cache.record(entry)`) —
confirms the rule is scoped to the `z` alias and won't over-fire
**invalid**
- single-arg `z.record(z.unknown())` → fires + autofix output
`z.record(z.string(), z.unknown())`
- chained `z.record(z.unknown()).optional()` → fixes the inner call
- `z.record(...spread)` → reports **without** a fix (`output: null`)
## Node version gate (important)
oxlint's `RuleTester` requires **Node ≥ 22** (it throws at parse time on
older runtimes). The CI unit matrix includes a **Node 20** job, so the
cases are gated to skip below Node 22 — verified locally: **6/6 pass on
Node 22**, **skips cleanly on Node 20**. The lint rule itself is still
exercised on every Node version through the `oxlint` job; only these
RuleTester unit tests are gated.
Also extends the `react-ui` vitest `include` to pick up co-located
`oxlint-rules/**/*.test.mjs`.
## Test plan
- [x] `vitest run` on the new file under Node 22 → 6 passed
- [x] same under Node 20 → 1 skipped (gate works; Node 20 CI job stays
green)
- [x] `oxlint` clean on the new test + config
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Closes#2615.
## Problem
The v1 thumbs callbacks were typed `(message: Message) => void`, so a
consumer
received the message but not *what the click did*. A custom
`AssistantMessage`
that keeps its own toggle state — the case in the issue — had nowhere to
put
that value:
```tsx
onClick={() => {
setReactionValue((v) => (v === "like" ? null : "like"));
onThumbsUp?.(message); // 👈 no way to say whether this applied or retracted
}}
```
The only workaround was counting clicks per message.
The built-in path had the matching gap. `Chat.tsx` already tracked
`messageFeedback`, but `handleThumbsUp` unconditionally wrote
`"thumbsUp"`, so
the state was write-only: clicking an active button re-applied the same
value
and there was no way to un-vote.
These callbacks are live API — `RenderMessage` forwards `onThumbsUp`,
`onThumbsDown` and `feedback` to whatever component is passed as the
`AssistantMessage` prop, which is exactly the customisation the issue
describes.
## Fix
Add an optional second argument reporting the state the click
transitions to:
```ts
onThumbsUp?: (message: Message, isActive?: boolean) => void;
```
- The built-in `AssistantMessage` derives it from the message's current
`feedback`, so a second click on an active button now reports `false`
and
**retracts** the feedback rather than re-applying it.
- A custom `AssistantMessage` can pass its own value straight through.
- The parameter is optional and appended, so existing one-argument
handlers
keep type-checking and keep working unchanged.
`onFeedbackGiven` fires only on the applying click — its signature is
`(messageId, "thumbsUp" | "thumbsDown")` and has no way to express a
retraction, so reporting one would be a lie.
Note this is a small behavioural change to the built-in buttons:
previously a
repeat click was a no-op, now it clears the vote. That is what makes the
reported state meaningful, and it matches the toggle UX in the issue.
The toggle logic lives in a new `./feedback` module. This package's
vitest
project runs `environment: "node"` and only collects `*.test.ts`, so
there is no
component-rendering harness here — extracting the pure functions is what
makes
the behaviour testable at all.
## Testing
`packages/react-ui` — full suite, including the 10 new cases:
```
✓ src/components/chat/feedback.test.ts (10 tests) 3ms
✓ src/components/chat/Markdown.test.ts (29 tests) 5ms
✓ src/components/chat/Markdown.xss.test.ts (13 tests) 182ms
✓ src/css/sidebar-full-height.test.ts (4 tests) 2ms
Test Files 10 passed (10)
Tests 68 passed (68)
```
Coverage: activation from no feedback, deactivation on the
already-active
button, switching to the opposite button, retraction removing the map
entry
rather than storing a falsy value, other messages left untouched, no
mutation of
the previous map, referential stability when nothing changes, and a
toggle
round-trip.
**Mutation checks** — broke each mechanism and confirmed the tests fail,
so they
are not self-fulfilling:
| Mutation | Result |
| --- | --- |
| `isActivatingClick` → `return true` | `Tests 1 failed \| 9 passed` |
| `applyFeedbackClick` retraction branch → `if (false)` | `Tests 3
failed \| 7 passed` |
Both were reverted and the suite returned to 68 passing.
**Types/build** — `tsc --noEmit` clean, `tsdown` clean, and the widened
signature reaches the published types:
```
$ grep -n "onThumbsUp" dist/index.d.mts
102: onThumbsUp?: (message: Message, isActive?: boolean) => void;
185: onThumbsUp?: (message: Message, isActive?: boolean) => void;
272: onThumbsUp?: (message: Message, isActive?: boolean) => void;
```
The thumbs callbacks only received the message, so a consumer could not
tell whether a click applied feedback or retracted it. Custom
AssistantMessage implementations that track their own toggle state had
no way to pass that value through, and the built-in feedback state was
write-only: clicking an active button re-applied the same value.
Add an optional second argument reporting the state the click
transitions to. The built-in AssistantMessage derives it from the
message's current feedback, which also makes the built-in buttons
toggle; a custom AssistantMessage may pass its own value. The argument
is optional, so existing one-argument handlers are unaffected.
The toggle logic is extracted to ./feedback so it can be unit tested —
this package's vitest project runs in a node environment and has no
component-rendering harness.
Closes#2615
The `.copilotKitHeader` rule declared `padding-right` only inside
`@media (min-width: 640px)`, so below that breakpoint the header's
`space-between` children ran flush to the viewport edge and the close
button had no gutter.
Move the horizontal padding into the unconditional rule so it applies at
every viewport. The value matches the 24px the sm breakpoint already
used, so wide layouts are unchanged.
Fixes#2493
Fixes#261 (open since March 2024). Supersedes #4622 — @ashish4143
diagnosed the same wrappers and is credited as co-author on the commit.
## The bug
`CopilotSidebar` wraps consumer content in two divs:
- `.copilotKitSidebarContentWrapper` (`Sidebar.tsx`) — only sets
`overflow`, `margin-right`, `transition`
- `.copilotKitModalChildrenWrapper` (`Modal.tsx`) — **has no CSS rule
anywhere in the repo**
Both are auto-height blocks, so a child's `height: 100%` has no definite
containing block to resolve against and collapses to content height.
## The fix
An opt-in `fullHeightChildren` prop on `CopilotSidebar` that adds a
modifier class to the content wrapper. Two deliberate choices, both from
the review on #4622:
- **Opt-in, not default.** The content wrapper wraps the *entire*
consumer app. Making it a fixed-height flex column for everyone would
reflow apps that never asked for it.
- **A viewport unit, not `height: 100%`.** `100%` only resolves if every
ancestor (`html`/`body`/`#root`) also declares a height — react-ui
neither sets that nor can guarantee it, so `100%` would silently no-op
in a stock Next.js app. `min-height: 0` on the children wrapper clears
the flex-item `min-height: auto` floor so tall content scrolls inside
the child rather than stretching the wrapper past the viewport.
```tsx
<CopilotSidebar fullHeightChildren>
<div style={{ height: "100%" }}>...</div>
</CopilotSidebar>
```
## Testing
**Unit** — `packages/react-ui/src/css/sidebar-full-height.test.ts` (4
tests), in the repo's existing CSS-contract style. Guards both halves:
the escape hatch's rules, and that the default wrapper stays
auto-height. Also asserts the height is *not* `100%`, since that's the
regression that would make the whole feature a silent no-op.
```
✓ src/css/sidebar-full-height.test.ts (4 tests)
Test Files 9 passed (9) Tests 58 passed (58) # full react-ui suite
```
`npx tsc --noEmit` → exit 0. `oxlint` on changed files → 0 warnings, 0
errors.
**Live in Chrome** — the acceptance criterion from the #4622 review: a
stock app where **nothing** declares a height on `html`/`body`/`#root`,
loading the real built `dist/index.css` (not the source CSS), standards
mode, 762px viewport. DOM per `Sidebar.tsx:92` + `Modal.tsx:143`.
| case | child `height:100%` measures |
|---|---|
| default (no opt-in) | **17px** — collapsed, i.e. behavior unchanged
for existing consumers |
| `fullHeightChildren` | **762px** — exactly the viewport |
| `fullHeightChildren`, content 3000px tall | **762px**, scrolls inside
the child (`min-height: 0` holds) |
Also confirmed on the opt-in path: `.copilotKitSidebar` stays `position:
fixed`, and the expanded push-aside `margin-right` is still `448px`
(28rem), so the sidebar's own layout is untouched.
**Docs** — `CopilotSidebar.mdx` is auto-generated from `Sidebar.tsx`;
regenerated via `scripts/docs/gen.ts` and committed only the new
`fullHeightChildren` entry (the generator also surfaces unrelated
pre-existing drift in other reference pages, left out of this PR).
## Not covered
The issue mentions a "works in Safari, not Chrome" symptom. I verified
in Chromium only — the mechanism above is spec behavior rather than a
Chrome quirk, but I haven't measured WebKit.
Children of `CopilotSidebar` cannot use `height: 100%`. Both wrappers the
sidebar puts around your app -- `.copilotKitSidebarContentWrapper` and
`.copilotKitModalChildrenWrapper` (which had no CSS rule at all) -- are
auto-height blocks, so a percentage height on a child has no definite
containing block and collapses to content height.
Add an opt-in `fullHeightChildren` prop that gives the content wrapper a
one-viewport height and lets the children wrapper fill it. It is opt-in
because the content wrapper wraps the entire consumer app, and giving
every react-ui sidebar user a flex column with a fixed height would
reflow apps that never asked for it.
The height is a viewport unit, not `100%`: `100%` only resolves when
every ancestor (html/body/#root) also declares a height, which react-ui
neither sets nor can guarantee, so it would silently no-op in a stock
Next.js app. `min-height: 0` on the children wrapper clears the flex-item
`min-height: auto` floor so tall content scrolls inside the child instead
of stretching the wrapper past the viewport.
Fixes#261
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Ashish Shaw <77574570+ashish4143@users.noreply.github.com>
## Summary
- lowers CopilotSidebar-scoped CSS selector specificity with
`:where(.copilotKitSidebar)`
- keeps the existing sidebar styles and layout behavior unchanged
- adds a regression test to keep sidebar selectors easy for app CSS to
override
## Root cause
Sidebar-specific selectors such as `.copilotKitSidebar
.copilotKitWindow` were more specific than ordinary user overrides like
`.copilotKitWindow`, so user CSS could lose precedence in Chrome.
Fixes#263.
## Validation
- `pnpm -C packages/react-ui test`
- `pnpm exec oxfmt --check
packages/react-ui/src/css/sidebar-specificity.test.ts
packages/react-ui/src/css/window.css
packages/react-ui/src/css/header.css
packages/react-ui/src/css/input.css`
- `pnpm exec oxlint
packages/react-ui/src/css/sidebar-specificity.test.ts`
Per BenTaylorDev's review on #5805, dropping the specificity of the
sidebar variant created a tie at desktop widths with the
@media (min-width:640px) base rule (both (0,1,0)); the later-authored
base won and reverted the sidebar header's top corners from square
(border-radius:0) back to rounded 8px. Re-assert the media-scoped
sidebar override inside the @media block (mirroring window.css) so the
sidebar header stays square at all widths.
Bumps react-syntax-highlighter from ^15.6.1 to ^16.1.1. The v16 line
pulls refractor 5 and prismjs ^1.30.0, keeping react-ui's syntax
highlighting dependency chain current for downstream consumers.
The public API used by CodeBlock (the Prism/Light exports) is unchanged,
and highlighting renders the same across common languages.
Closes#2823.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Problem
The published declaration files for `@copilotkit/react-core`,
`@copilotkit/react-ui`, and `@copilotkit/react-textarea` contain imports
that TypeScript cannot resolve, so **`attw` (Are The Types Wrong)
reports `InternalResolutionError` across every resolution mode**
(`node10` / `node16` / `bundler`). In `@copilotkit/react-core` this was
being **masked in CI** by `--ignore-rules internal-resolution-error` on
the package's `attw` script — so the existing `check:packages` gate
looked green while consumers under `moduleResolution:
bundler`/`node16`/`nodenext` got broken types (the symptom reported in
#3324: `has no exported member 'useAgent'`, etc.).
Two distinct artifacts leaked into the emitted `.d.ts` / `.d.cts` /
`.d.mts` (neither affects the JS bundles):
1. **Side-effect CSS imports** — `import "./index.css"` is intentionally
kept in the JS so styles auto-load for bundler consumers, but
`rolldown-plugin-dts` also left it in the declarations, where TypeScript
can't resolve a `.css` as a typed module.
2. **Extensionless relative `./context` import** —
`@copilotkit/react-core/v2/headless` re-exports the externalized context
module; the JS bundle correctly externalizes it to
`@copilotkit/react-core/v2/context`, but the declaration kept the
relative `./context`, which is invalid in ESM declarations.
> Note: this is **not** the missing-`exports.types`-condition theory
from #3324. tsdown deliberately relies on co-located `.d.mts`/`.d.cts`
siblings; `@copilotkit/core` already resolves cleanly. The real defects
are the two leaked imports above.
## Fix
A small tsdown `build:done` hook post-processes the emitted declarations
**on disk** (after every format is written, so it catches both `.d.mts`
and `.d.cts`):
- strips side-effect CSS imports from declarations (JS keeps them);
- rewrites the relative `./context` import to the
`@copilotkit/react-core/v2/context` package path (matching how the JS
bundle externalizes it).
Also:
- **Removed the `--ignore-rules internal-resolution-error` band-aid**
from `react-core`'s `attw` script so the existing CI gate validates for
real.
- **Dropped the dead `codeSplitting` option** from the UMD configs —
tsdown never reads it (it's a rolldown-only key), and it was failing
`tsc` in the configs that type-check themselves. UMD output is unchanged
(single file).
## Verification
- All three packages build; **no CSS or relative-`./context` imports
remain in any declaration**, while the JS bundles still contain them
(styles auto-load preserved).
- `attw` + `publint` pass for all packages **with no suppression**
(`react-core`'s `/v2`, `/v2/headless`, `/v2/context` are green for
node16-cjs/esm/bundler).
- Unit tests pass.
- A standalone consumer project (real tarball install, `skipLibCheck:
false`) type-checks the public APIs — including `useAgent` /
`useFrontendTool` / `useConfigureSuggestions` — cleanly under **both
`bundler` and `nodenext`**, and the headless↔context class is nominally
identical.
## Out of scope (follow-ups)
- `@copilotkit/react-native`: its `--ignore-rules
internal-resolution-error` currently suppresses nothing (no IRE) and it
has a separate `NoResolution` flag.
- `@copilotkit/vue`: a large, genuine set of `.vue`/relative-import
declaration errors unrelated to this change.
Relates to #3324.
🤖 Generated with [Claude Code](https://claude.com/claude-code)