Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
ac7deb4
Initial custom agent
compulim Mar 16, 2026
4d03e49
Request vs. props
compulim Mar 16, 2026
bc7e28e
Add more instructions
compulim Mar 17, 2026
3d2e355
Upgrade avatarMiddleware to polymiddleware
compulim Mar 19, 2026
446b23b
Keep original request object
compulim Mar 19, 2026
b808d04
Fix ESLint
compulim Mar 19, 2026
7244388
Fix ESLint
compulim Mar 19, 2026
1ee8956
Move to macOS 26
compulim Mar 19, 2026
d431dd3
Fix legacy avatar bridge
compulim Mar 19, 2026
d5c6b46
Deprioritize default activity middleware
compulim Mar 20, 2026
8ee929b
Patch attachmentLayout instead of via activityMiddleware
compulim Mar 20, 2026
f767501
Add helper hooks
compulim Mar 23, 2026
c0bc32a
Add iterateEquals
compulim Mar 23, 2026
5619932
Use useMemoIterable and polymiddleware
compulim Mar 23, 2026
d4c112c
Add watch deps
compulim Mar 23, 2026
7f0f9aa
Fix signature change
compulim Mar 23, 2026
8d0cf16
Refactor singleToArray nd OneOrMany
compulim Mar 23, 2026
26a8cbe
Fix AddFullBundle rerendering
compulim Mar 23, 2026
696eb98
Clean up singleToArray
compulim Mar 23, 2026
a165f97
Fix typings
compulim Mar 23, 2026
004af6c
Use undefined if array is empty
compulim Mar 23, 2026
f8c2274
Clean up polymiddleware array
compulim Mar 23, 2026
d23385f
Fix avatarMiddleware
compulim Mar 23, 2026
5ce745c
Fix middleware.length
compulim Mar 23, 2026
356004f
Fix ESLint
compulim Mar 23, 2026
bcaee94
Add displayName
compulim Mar 23, 2026
d6b8fe7
Fix test
compulim Mar 23, 2026
7315971
Add waitFor
compulim Mar 23, 2026
86d6d43
Clean up useMemoIterable
compulim Mar 23, 2026
37bd3f6
Fix import
compulim Mar 23, 2026
89644bc
Remove no-magic-numbers
compulim Mar 23, 2026
94649ac
Should only show 1 message
compulim Mar 23, 2026
10b6ffc
Fix runHook twice
compulim Mar 24, 2026
4add090
Fix window.React
compulim Mar 24, 2026
494b95b
Clean up
compulim Mar 24, 2026
22403b7
Add test
compulim Mar 24, 2026
97775f4
Clean up tests
compulim Mar 25, 2026
8fe980e
Skip test
compulim Mar 25, 2026
173be90
Use styleOptions at init
compulim Mar 25, 2026
5326629
Add changing middleware tests
compulim Mar 25, 2026
f6f835b
Add tests
compulim Mar 25, 2026
4b0bfe9
Fix type portability
compulim Mar 25, 2026
7fcdd2b
Add AvatarPolymiddlewareProxy
compulim Mar 25, 2026
5d3bb3e
Clean up
compulim Mar 25, 2026
3bb3b71
Add useBuildRenderAvatarCallback test
compulim Mar 25, 2026
63655d4
Clean up
compulim Mar 25, 2026
8e1ce63
Clean up
compulim Mar 25, 2026
68ed0a9
Update doc
compulim Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ overrides:
no-unused-vars: off
'@typescript-eslint/no-unused-vars':
- error
- argsIgnorePattern: ^_$
- argsIgnorePattern: ^_
varsIgnorePattern: ^_

no-empty-function: off
Expand Down
45 changes: 45 additions & 0 deletions .github/agents/polymiddleware-promoter.agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
name: Polymiddleware promoter
description: Upgrade middleware to polymiddleware
argument-hint: The existing middleware to upgrade
# tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo'] # specify the tools this agent can use. If not set, all enabled tools are allowed.
---

<!-- Tip: Use /create-agent in chat to generate content with agent assistance -->

You are a developer upgrading middleware to polymiddleware.

Polymiddleware is newer, while middleware is older and should be upgraded.

Middleware and polymiddleware are pattern for plug-ins and our customization story. There are 2 sides: writing the middleware and using the middleware. Web Chat write and use the middleware. 3P developers write middleware and pass it to Web Chat.

Polymiddleware is a single middleware that process multiple types of middleware. Middleware is more like `request => (props => view) | undefined`, while polymiddleware is `init => (request => (props => view) | undefined) | undefined`.

The middleware philosophy can be found at https://npmjs.com/package/react-chain-of-responsibility.

When middleware receive a request, it decides if it want to process the request. If yes, it will return a React component. If no, it will pass it to the next middleware.

Definition of polymiddleware are at `packages/api-middleware/src/index.ts`.

Definition of middleware are scattered around but entrypoint at `packages/api/src/hooks/Composer.tsx`.

- You MUST upgrade all the usage of existing middleware to polymiddleware
- You MUST write a legacy bridge to convert existing middleware into polymiddleware, look at `packages/api/src/legacy`
- All tests MUST be visual regression tests, expectations MUST live inside the generated PNGs
- You MUST NOT update any existing PNGs, as it means breaking existing feature
- You MUST write migration tests: write a old middleware and pass it, it should render as expected because the code went through the new legacy bridge
- You MUST write polymiddleware test: write a new polymiddleware and pass it, it should render
- For each category of test, you MUST test it in 4 different way:
1. Add new UI that will process new type of requests
- You MUST verify existing middleware does not process that new type of request, only new polymiddleware does
2. Delete existing UI: request processed by existing middleware should no longer process
3. Replace UI that was processed by existing middleware, but now processed by a new middleware
4. Decorate existing UI but wrapping the result from existing middleware, commonly with a border component
- "request" vs. "props"
- Code processing the request MUST NOT call hooks
- Code processing the request decide to render a React component or not
- Code processing the props MUST render, minimally, `<Fragment />` or `null`, they are processed by React
- Request SHOULD contains information about "should render or not"
- Props SHOULD contains information about "how to render"
- You MUST NOT remove the existing middleware from `<Composer>`, however, print a deprecation warn-once, then bridge it to the polymiddleware
- You SHOULD NOT export the `<XXXProvider>`, `XXXProviderProps`, and `extractXXXEnhancer`
2 changes: 1 addition & 1 deletion .github/workflows/pull-request-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ jobs:
strategy:
matrix:
os:
- macos-latest
- macos-26
- ubuntu-latest
- windows-latest
runs-on: ${{ matrix.os }}
Expand Down
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- Prefer uppercase for acronyms instead of Pascal case, e.g. `getURL()` over `getUrl()`
- The only exception is `id`, e.g. `getId()` over `getID()`
- Use fewer shorthands, only allow `min`, `max`, `num`
- Prefer ternary operator over one-liner `if` statement

### Design

Expand All @@ -35,6 +36,7 @@
### Typing

- TypeScript is best-effort checking, use `valibot` for strict type checking
- Use TypeScript CLI instead of `tsc`
- Use `valibot` for runtime type checker, never use `zod`
- Assume all externally exported functions will receive unsafe/invalid input, always check with `valibot`
- Avoid `any`
Expand Down Expand Up @@ -100,9 +102,15 @@ export { MyComponentPropsSchema, type MyComponentProps };
- Use `@testduet/given-when-then` package instead of xUnit style `describe`/`before`/`test`/`after`
- Prefer integration/end-to-end testing than unit testing
- Use as realistic setup as possible, such as using `msw` than mocking calls
- Use `emulateIncomingActivity` and `emulateOutgoingActivity` to emulate conversation

## PR instructions

- Run new test and all of them must be green
- Run `npm run precommit` to make sure it pass all linting process
- Add changelog entry to `CHANGELOG.md`, follow our existing format

## Code review

- Code should use as much immutable (via `Object.freeze()`) as possible, DO NOT trust `readonly`
- All inputs SHOULD be validated
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Breaking changes in this release:
- 💥 Root-level (unconnected) `Claim` entity is being deprecated, in PR [#5564](https://github.com/microsoft/BotFramework-WebChat/pull/5564), by [@compulim](https://github.com/compulim). It will be removed on or after 2027-08-29
- Use `entities[@id=""][@type="Message"].citation[@type="Claim"]` instead
- 💥 `activityStatusMiddleware.nextVisibleActivity` and `activityStatusMiddleware.sameTimestampGroup` is removed after deprecation, in PR [#5565](https://github.com/microsoft/BotFramework-WebChat/issues/5565), by [@compulim](https://github.com/compulim)
- 💥 `avatarMiddleware` is being deprecated in favor of [`polymiddleware`](./docs/MIDDLEWARE.md). It will be removed on or after 2028-03-18, related to PR [#5779](https://github.com/microsoft/BotFramework-WebChat/pull/5779)

### Added

Expand Down Expand Up @@ -108,8 +109,10 @@ Breaking changes in this release:
- Updated `BasicSendBoxToolbar` to rely solely on `disableFileUpload`.
- Added support for livestreaming via `entities[type="streaminfo"]` in PR [#5517](https://github.com/microsoft/BotFramework-WebChat/pull/5517) by [@kylerohn](https://github.com/kylerohn) and [@compulim](https://github.com/compulim)
- Added `polymiddleware`, a new [universal middleware for every UIs](./docs/MIDDLEWARE.md), by [@compulim](https://github.com/compulim) in PR [#5515](https://github.com/microsoft/BotFramework-WebChat/pull/5515) and [#5566](https://github.com/microsoft/BotFramework-WebChat/pull/5566)
- Legacy middleware is prioritized over polymiddleware
- Added `polymiddleware` to `<ThemeProvider>`
- Currently supports activity middleware and the new error box middleware
- Supports avatar middleware, by [@compulim](https://github.com/compulim) in PR [#5779](https://github.com/microsoft/BotFramework-WebChat/pull/5779)
- New internal packages, by [@compulim](https://github.com/compulim) in PR [#5515](https://github.com/microsoft/BotFramework-WebChat/pull/5515)
- `@msinternal/botframework-webchat-api-middleware` for middleware branch of API package
- `@msinternal/botframework-webchat-debug-theme` package for enabling debugging scenarios
Expand Down Expand Up @@ -348,6 +351,7 @@ Breaking changes in this release:
- Removed legacy test harness, in PR [#5655](https://github.com/microsoft/BotFramework-WebChat/issues/5655), by [@compulim](https://github.com/compulim)
- All tests are now either using `html2` test harness or simple unit tests
- Legacy and `html` (html1) test harness are all migrated to `html2`
- `avatarMiddleware` is being deprecated in favor of [`polymiddleware`](./docs/MIDDLEWARE.md). It will be removed on or after 2028-03-18, related to PR [#5779](https://github.com/microsoft/BotFramework-WebChat/pull/5779)

### Fixed

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
<!DOCTYPE html>
<!--
Polymiddleware does not support request modification.
Thus, modified `renderAttachment()` from legacy activity middleware is not honored.
This test is testing against "modified attachmentRenderer", thus it is failing.
Polymiddleware could support modified attachment polymiddleware when we finish the following work:
- Upgrade to attachment polymiddleware
- Support cascaded polymiddleware
- Today, polymiddleware are set in <APIComposer>, cascading means we need 2 x <APIComposer>
- Tomorrow, we should allow cascading through <ThemeProvider polymiddleware={}>
- That also means, default poly/middleware should be materialized in <ThemeProvider>, instead of <APIComposer>
-->
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
Expand Down Expand Up @@ -32,57 +47,63 @@
run(async function () {
await host.windowSize(undefined, 1280, document.getElementById('webchat'));

const activityMiddleware = () => next => (...renderActivityArgs) => {
const [{ activity }] = renderActivityArgs;
const activityMiddleware =
() =>
next =>
(...renderActivityArgs) => {
const [{ activity }] = renderActivityArgs;

const renderActivity = next(...renderActivityArgs);
const renderActivity = next(...renderActivityArgs);

if (/^1/.test(activity.id)) {
return (children, ...renderAttachmentArgs) => (
<div className="decorated-activity">
{renderActivity(
(...renderAttachmentArgs) => (
<div className="decorated-attachment">{children(...renderAttachmentArgs)}</div>
),
if (/^1/.test(activity.id)) {
return (children, ...renderAttachmentArgs) => (
<div className="decorated-activity">
{renderActivity(
(...renderAttachmentArgs) => (
<div className="decorated-attachment">{children(...renderAttachmentArgs)}</div>
),
...renderActivityArgs
)}
</div>
);
} else if (/^2/.test(activity.id)) {
return (children, ...renderAttachmentArgs) =>
renderActivity(
(...renderAttachmentArgs) => {
const [firstArg] = renderAttachmentArgs;
const {
attachment,
attachment: { contentType }
} = firstArg;

if (/^image\//.test(contentType)) {
return (
<figure className="attachment-figure">
{children(...renderAttachmentArgs)}
<figcaption className="attachment-figure-caption">
{children(
{
...firstArg,
attachment: {
...attachment,
contentType: 'application/octet-stream'
}
},
...renderAttachmentArgs
)}
</figcaption>
</figure>
);
}

return children(...renderAttachmentArgs);
},
...renderActivityArgs
)}
</div>
);
} else if (/^2/.test(activity.id)) {
return (children, ...renderAttachmentArgs) =>
renderActivity((...renderAttachmentArgs) => {
const [firstArg] = renderAttachmentArgs;
const {
attachment,
attachment: { contentType }
} = firstArg;

if (/^image\//.test(contentType)) {
return (
<figure className="attachment-figure">
{children(...renderAttachmentArgs)}
<figcaption className="attachment-figure-caption">
{children(
{
...firstArg,
attachment: {
...attachment,
contentType: 'application/octet-stream'
}
},
...renderAttachmentArgs
)}
</figcaption>
</figure>
);
}

return children(...renderAttachmentArgs);
}, ...renderActivityArgs);
}

return renderActivity;
};
);
}

return renderActivity;
};

WebChat.renderWebChat(
{
Expand All @@ -106,8 +127,7 @@
role: 'bot'
},
id: '1.0',
text:
'Decorating activity of **carousel layout**. Also decorate attachment without using attachment middleware.',
text: 'Decorating activity of **carousel layout**. Also decorate attachment without using attachment middleware.',
timestamp: 0,
type: 'message'
},
Expand All @@ -123,8 +143,7 @@
role: 'bot'
},
id: '1.1',
text:
'Decorating activity of **stacked layout**. Also decorate attachment without using attachment middleware.',
text: 'Decorating activity of **stacked layout**. Also decorate attachment without using attachment middleware.',
timestamp: 0,
type: 'message'
},
Expand Down
Loading
Loading