From ac7deb4910418b6099662390cf3c91182879077e Mon Sep 17 00:00:00 2001 From: William Wong Date: Mon, 16 Mar 2026 20:18:01 +0000 Subject: [PATCH 01/11] Initial custom agent --- .../agents/polymiddleware-promoter.agent.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/agents/polymiddleware-promoter.agent.md diff --git a/.github/agents/polymiddleware-promoter.agent.md b/.github/agents/polymiddleware-promoter.agent.md new file mode 100644 index 0000000000..22f3446799 --- /dev/null +++ b/.github/agents/polymiddleware-promoter.agent.md @@ -0,0 +1,37 @@ +--- +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. +--- + + + +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 From 4d03e49757d6d2337caf94e8d513491179da7145 Mon Sep 17 00:00:00 2001 From: William Wong Date: Mon, 16 Mar 2026 20:43:45 +0000 Subject: [PATCH 02/11] Request vs. props --- .github/agents/polymiddleware-promoter.agent.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/agents/polymiddleware-promoter.agent.md b/.github/agents/polymiddleware-promoter.agent.md index 22f3446799..0658141ffa 100644 --- a/.github/agents/polymiddleware-promoter.agent.md +++ b/.github/agents/polymiddleware-promoter.agent.md @@ -35,3 +35,9 @@ Definition of middleware are scattered around but entrypoint at `packages/api/sr 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, `` or `null`, they are processed by React + - Request SHOULD contains information about "should render or not" + - Props SHOULD contains information about "how to render" From bc7e28eb6c95604478458570dc6f412d82e06eee Mon Sep 17 00:00:00 2001 From: William Wong Date: Tue, 17 Mar 2026 19:17:11 +0000 Subject: [PATCH 03/11] Add more instructions --- .github/agents/polymiddleware-promoter.agent.md | 2 ++ AGENTS.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/agents/polymiddleware-promoter.agent.md b/.github/agents/polymiddleware-promoter.agent.md index 0658141ffa..4a868b0ceb 100644 --- a/.github/agents/polymiddleware-promoter.agent.md +++ b/.github/agents/polymiddleware-promoter.agent.md @@ -41,3 +41,5 @@ Definition of middleware are scattered around but entrypoint at `packages/api/sr - Code processing the props MUST render, minimally, `` 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 ``, however, print a deprecation warn-once, then bridge it to the polymiddleware +- You SHOULD NOT export the ``, `XXXProviderProps`, and `extractXXXEnhancer` diff --git a/AGENTS.md b/AGENTS.md index cf4fafc8de..e2eb002bbb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,6 +35,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` @@ -100,6 +101,7 @@ 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 From 3d2e355651147aa5f32c5e646d7402cf9f73c273 Mon Sep 17 00:00:00 2001 From: William Wong Date: Thu, 19 Mar 2026 07:56:37 +0000 Subject: [PATCH 04/11] Upgrade avatarMiddleware to polymiddleware --- .eslintrc.yml | 2 +- AGENTS.md | 6 ++ .../avatar/legacyAvatarMiddleware/addNew.html | 77 ++++++++++++++ .../addNew.html.snap-1.png | Bin 0 -> 11829 bytes .../legacyAvatarMiddleware/decorate.html | 80 ++++++++++++++ .../decorate.html.snap-1.png | Bin 0 -> 13585 bytes .../avatar/legacyAvatarMiddleware/delete.html | 53 ++++++++++ .../delete.html.snap-1.png | Bin 0 -> 9477 bytes .../legacyAvatarMiddleware/replace.html | 78 ++++++++++++++ .../replace.html.snap-1.png | Bin 0 -> 11580 bytes .../avatar/polymiddleware/addNew.html | 80 ++++++++++++++ .../polymiddleware/addNew.html.snap-1.png | Bin 0 -> 11546 bytes .../avatar/polymiddleware/decorate.html | 77 ++++++++++++++ .../polymiddleware/decorate.html.snap-1.png | Bin 0 -> 13018 bytes .../avatar/polymiddleware/delete.html | 58 ++++++++++ .../polymiddleware/delete.html.snap-1.png | Bin 0 -> 9477 bytes .../avatar/polymiddleware/replace.html | 77 ++++++++++++++ .../polymiddleware/replace.html.snap-1.png | Bin 0 -> 11555 bytes .../src/PolymiddlewareComposer.tsx | 20 +++- .../src/avatarPolymiddleware.tsx | 92 ++++++++++++++++ packages/api-middleware/src/index.ts | 15 +++ packages/api-middleware/src/legacy.ts | 2 + .../src/legacy/avatarMiddleware.ts | 19 ++++ packages/api/src/boot/internal.ts | 1 + packages/api/src/boot/middleware.ts | 16 +++ packages/api/src/hooks/Composer.tsx | 44 +++++--- .../src/hooks/internal/WebChatAPIContext.ts | 2 - .../api/src/hooks/useCreateAvatarRenderer.ts | 41 ++++--- .../createAvatarPolymiddlewareFromLegacy.tsx | 89 ++++++++++++++++ packages/bundle/src/boot/actual/middleware.ts | 18 +++- packages/component/src/Activity/Avatar.tsx | 2 +- packages/component/src/Composer.tsx | 11 +- .../src/Middleware/Avatar/DefaultAvatar.tsx | 62 +++++++++++ .../Avatar/createCoreMiddleware.tsx | 100 +++++------------- .../hooks/useRenderActivityProps.ts | 28 +++-- 35 files changed, 1027 insertions(+), 123 deletions(-) create mode 100644 __tests__/html2/middleware/avatar/legacyAvatarMiddleware/addNew.html create mode 100644 __tests__/html2/middleware/avatar/legacyAvatarMiddleware/addNew.html.snap-1.png create mode 100644 __tests__/html2/middleware/avatar/legacyAvatarMiddleware/decorate.html create mode 100644 __tests__/html2/middleware/avatar/legacyAvatarMiddleware/decorate.html.snap-1.png create mode 100644 __tests__/html2/middleware/avatar/legacyAvatarMiddleware/delete.html create mode 100644 __tests__/html2/middleware/avatar/legacyAvatarMiddleware/delete.html.snap-1.png create mode 100644 __tests__/html2/middleware/avatar/legacyAvatarMiddleware/replace.html create mode 100644 __tests__/html2/middleware/avatar/legacyAvatarMiddleware/replace.html.snap-1.png create mode 100644 __tests__/html2/middleware/avatar/polymiddleware/addNew.html create mode 100644 __tests__/html2/middleware/avatar/polymiddleware/addNew.html.snap-1.png create mode 100644 __tests__/html2/middleware/avatar/polymiddleware/decorate.html create mode 100644 __tests__/html2/middleware/avatar/polymiddleware/decorate.html.snap-1.png create mode 100644 __tests__/html2/middleware/avatar/polymiddleware/delete.html create mode 100644 __tests__/html2/middleware/avatar/polymiddleware/delete.html.snap-1.png create mode 100644 __tests__/html2/middleware/avatar/polymiddleware/replace.html create mode 100644 __tests__/html2/middleware/avatar/polymiddleware/replace.html.snap-1.png create mode 100644 packages/api-middleware/src/avatarPolymiddleware.tsx create mode 100644 packages/api-middleware/src/legacy/avatarMiddleware.ts create mode 100644 packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx create mode 100644 packages/component/src/Middleware/Avatar/DefaultAvatar.tsx diff --git a/.eslintrc.yml b/.eslintrc.yml index 0ac026ba64..eb12c3c9d9 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -55,7 +55,7 @@ overrides: no-unused-vars: off '@typescript-eslint/no-unused-vars': - error - - argsIgnorePattern: ^_$ + - argsIgnorePattern: ^_ varsIgnorePattern: ^_ no-empty-function: off diff --git a/AGENTS.md b/AGENTS.md index e2eb002bbb..b469f44c1c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 @@ -108,3 +109,8 @@ export { MyComponentPropsSchema, type MyComponentProps }; - 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 diff --git a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/addNew.html b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/addNew.html new file mode 100644 index 0000000000..a0d557c4da --- /dev/null +++ b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/addNew.html @@ -0,0 +1,77 @@ + + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/addNew.html.snap-1.png b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/addNew.html.snap-1.png new file mode 100644 index 0000000000000000000000000000000000000000..c716774beed621211f6518d4c3b9d669190dab9e GIT binary patch literal 11829 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 + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/decorate.html.snap-1.png b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/decorate.html.snap-1.png new file mode 100644 index 0000000000000000000000000000000000000000..d12ac4461a5d0c7aa0ea828d79dd20eff4a21ca7 GIT binary patch literal 13585 zcmeI3XHb)0*X}`75EN8EkZMDkfTHv&2q?WHT}4`u-n)XxU#cJ-k=_zWq=ZgT=`D1S z7J3W41_(*c_Br!>Iq#hJob&O_JekbEK<2*pz4u;gUDt2zeAd=fxkAlCO-4p`<;C-7 zx@2VJc4TDd-cy|ef06a^+#w^oO7`N}Q+=Or8`G2ljMfvlU8%(L*Pl~mQ)PSG^W~h= zWnl1limm8^m7LaK;nU`aI)+Nq20uLoc=)h&b#)AFg^YKb z^mg{D!gV*Utj}jCPT9_$ZxWBcHm8!NaGsghza=Ivk^Q{$hy4AuU*tcdX31VDyO2G+ zD^8Yjjg72_ik8ggJSExV5Q_IV|MTzvOv`_^!+*lyKiT0wS@HjeYT$Jga@X;L)B#ee zZXkc#rM(ItbSi4u9M*CY^l8+6sjlXSD6RY&(-%+xBko#9sin@+YPjC?M>n`>8!!z_ zhjweoAs9S!=Gh!#ZKkZ`CxVW>;`2$M|xHCqZ6&D+4bMd);2cx zxw#7q3#HD?LPA5W-n=KEk z)y|oY(ieEQ*_l6ptK&lyu`W;x@bdBs3kyGZ&@;CZpOlmoAAg;db)C%A%cR- z(BC~G>UHQKchc62%sOT;=;P)D1D+7pc0}|>-4LCQ?@iVy5?C$Q4Kn>iFeRqE} zBl>^2zKfj1OtYIWL|nTm;k|7dWI6j|F=ZKw0x!m@oaku>btBH!k>zEPV4XtXc za%dP9PvanN%okdw>Sc=;->$=VSLA*-J7^GHO_!^i*j!x^rV}UQvUnYGcW85CC8l&t ze=jaAqdhjz!9v12$xd$~OLRS5qI`I>p|e_wjLY2 zmmPnMwTK~IXL_rqTt0_36#r7YSeT!=pO)Wd{7F!5VDx#wus0c*YTj9>6-f$pTX#j2 zz2RW`WaY=#_@Ryyopx4s6~h_|lGndJve18d8&?s@*~^1rwWHCBjdCaM@az*b9OWb}^#FE0G%8}1fn~ek$EoZ;<`t)~4?6W1an*Hsu0AG@IyEpwT z$A+#qw%O=j(}X!``OFd@UN?HBXFa3rv8Xa<=3X>8$?WrmpLa56pJ%N5Ba7S}g6iBH z=Lf!3Z^(GOTdShjWYzDf{b5dsxS48fBqnh}Ta+ZyWqFnHXp?0o+(SQ>8b6ncR>X^# z(+BTl>+q9~mg=VE*nP`LVVopl{1C2W+P)A~d|)o<`GFrwB;c#s< z)fN@9E%m15A7Se*pCKkQmtQnO4YLotHYf5MRg{%!Sw(v?ti+~0^l)3#>^uA5=`}Ay zq9)VYxwm>ulBf9K3hK32oek*)APJeZq7t@gX6~=83EwK;R0q+bvUPI#u8lQGl89P{ z0piYhxdO)xsW)?XtqiCC6r2he4T}fjxxJyUGjvx&>10zRJ+bM|DBp!>vAXN_huD!T ztfC(BI!p9y_T3FL``)9Hgf@HF*N}4*bt16&Uda4Q<6K@)$vhWyj-)CWQqwU5x|T@amrcn zvq}5xR82Y!m7EGYPF4|)7h)TwP=upNS65dEw9;w1?oDe11tqQe^XJU60X|>vYdnE5 zPgFZwepg{+V>{~=n=uO}9O~%kWJr0B!U~^;_~FK1E#IfNK_nyva^)4Ar=HWT&LaolRSznM)bJ)PTP%W zH=a`=bfL1RqF;oYWpkY|q^v(ghc$=OS^6#|s>HAvm%sh2DXn4V-Yb%tdV(@jvdtJB zlQ?Uplj}(r(^C2ZL!cxjBqYWl6T=jA(k=r^OxD)cGN-G#+1c6rMsPM6zk_u|69#TB zRoV|T5;EVBBb~Xo8jiuu6R9i%4-=Dsf!dg^H(E;6$yMf+UGB?7%(s>pR}^Sv5@$$6 zBQ=cALbNoUIw9t1=*42_9(Zu)={lrkQxHalD}Zn~CgXoJyb;4LRoPqXz5*FDAufK^ zlHF~gzrymQAA*|+Zlc)OJ4x8@Sx7$pNa5QgVcj+%6TCT{=cRrechJ)|a)*O;-M=-Q zg?+PPD9?RC)eo!ra!YDcF*K0(D)X!3cLN_2=xv>q%R&?dLf)U@@Mx)R9vTL6jIDan zJ)LT&=}YwYeQd|pEWmU{MMZhf1XjNrFHd8aaLdCM@YzbA?KH#X9Y#xZ(w?c{l_Lw( zQ*-u>U9;W}Y&`Ch&b8?hN>tI2$|G41g8a!!p!oL zzg%EgZzdnSn2WZ89OG;l2pU}xcjiSW+FwYUUOXW)z5Vj;>7fE)q_c*9z5Eikee=- z`Ocj?APhaaG()wBjiBzPGPGr?{W;!H|Vy7_pYRhx=(+P zgcwp?cCjS>m^3Yy-Tk7h>-0|VuBfob1a`q&@5Fs-@R(FvS(nq&R1n4XXRqo=#AsW! z^R9T8?RV)Y_xqxvhGlyPLs~0lMZayy*H`$ouxIb4YRVpOJsB&8IYPS^B3_e-1c~b9 z@&(0gV^qM&H?cK4b1%hC^426Rm-Fm$RxstfrRo?+W}^_aAabQSS1U;C_f)jMvm=xD zv0a;(#6?Te6F+NM4fVp7<72nw(QUD97`VXr##?*2zET!=zx_2+G-i!A+*R<2 z!{|mupX~advRu+BX(3k5?=Vcd*ea*|A4rjf;;R*O2DV|!wVfSZqc>t{5U(xNX_{jg zTeT7K)ckrhrEgsIn^tPVFmpsumEFL6{WfYJA?m)YPI!lDsHKPy$YfD@$-nIlX9Y22QJ!)d)>b$8p9`)!Tgkpi>{)F0@4}e!8p!u{%)d zHeefh8O2uu@h&QWWk9x7>8JAo^j{oy3pEXM!j1C_p$)V=l>s4lbJB73Mx~EVrXLf3 z#=V!ipFmh>>0xu`@pV|fA+WJGIAzAqDVer=mUrk#6ovf`{K_XIo<9^hW8ZsL72sAF z*El7SOKiIM21wK=HpNe)LDbm2&jm4dQi_=Pi1y0JhYIrvc?3Zxpd23_r#-PB6sVqZ z>yyD1eJM@~X>)BTw;r&|Qi)Dxb*g!4RSnU|D=dWFTta3)F)5LA885d6C7Dh1T}qpS z-0AAwZ3|VI5e@%?)kj_%FO#RS<4A{O3TlBSgi41SE5y%|ZTh&mvlat)(-eWS)!Do{ zN*4-os692^GYjuqBPs3tXclU!9-*xHXT7o5DVloManM!^ai3c-klRFtOf~IHE_R{C z7cXC7^gATD!1?k>42Kgj08T-sJ;-pCO~&k|xZ7W?RKJ_9AncMZ6HBiqw?Sz(0 z{w#W3|5Ho_x{Zq8UL>E3vvV4mSkv)tnDcagiNmR9RAj|Mzfmj)&WL^ zc@;Cc$qcKz{yjXL@dWC!ohp<$1y3};zV=n4DijIsgRj>x_vBgSWOIS!B2)dLa5-83B%FW6f5`ild|QeWZmfhE zi-y*$e$j&0sbg-wQi&8+Xfo>TQv9<`T)|@G&!h z8wEQqf=RdF?%)&_H0b|jGK@p-nqs}3aG`>m30mXn$y3`yhcZnwH!FlhTxRA<2;qUP z=%VtcI&Vt!Wy1Ke=tgP6zqh`f#TSrNWkK+&zGoN$l zkKjg;|E!z64K})*nwr|#*}1m1Mk|**J3E__lJZl&tzlMgV|GUkk6};Sig7v!jA$?0 z#Mt`Q%(4jHL*pJ-e&_c_($x2m^AOM$E@a})-}Z40=Z+6=|5xy8%u5>sqFyuYNvCzN zj0s3=G{NnykxXKsbbqm2yFBZWe;oFnUCN71(!(x8((~JZk!7IgV#hVgtfG*MbQ~M1 z6-tpe450cic6WbKa^B`7_ySJHf>(CD+bZ{LPd1FtiQ8`6^FWs>w*%?j&cn1&iF#Rfr^4%f%T?E2-qkrQz9 z`kdTcPa(>0oIy(hF+dd=G)DEwk)%aLkU@k~FgJp)tV#F-igV|N6?E>{neCc>9y9< zWoHF3`=3ih{5ENNJE+=g)5KGkx{-e6&^kdJQ!@C=G$-m$LMh0)d|p0778>WpwGz5! zwa4V6smzaz;AX{ZX(A4gY}GudW!;8md!6U{leoCJ?AJ^N>LTVyhyjv)xPXe9TDOH^ zxk8$OeqwK99EJFycfFW;Jzuf;(-oxUp`C()fj{6*2Yi(#BzZ1UzmGYS7I}KG2tJ8C0th z%TcHHOG@|3A(&%!;2?k_$oXYzDL~7p;dwc2l$@s!L>Cgwt=O?NfXfYwZ2uQ%>C7Zg zz$F|dxa07$9#EXEce7k8^1R8yZTadh;RQ8OGPV}uxYq6IKvD$wah_6J)w(XBOmOXm zw?_tlNdG6nBZOdirozwhYiuhSJv}{(i;KSlevqM&|BhaKp()$9+4zgKclq4~)g*Os zlOItt+bEX>RFME)L5v%(xheUG+%qS3I9j&9pbIK!pknZ#f{?KJ5$RPXbX@mP`Fy)C z_rHkDAj#GC_(DE-<>j@swV7-s%w6B|#Lw*YwHJ!8ps;a5V-`Q&jFmmcoXppJS!dZe zwi-%?hLy_HJ~_28T4MCHGRyuu%;V!@$Ou#-;c^uzrYCbM2DPm#S-HyesUs zH^1=NP|Jv~qE(D@Vz@PMP62yk8P&9>B|#a)H9Y#qD>|7)WvDsR{Oo|=osWTAe&fq%O8GECC`$nqSUJ^GNr04^g2}_UL0~qrM^p3#yJ}Hp%W#} z%N@Ga`}b$I&>z7E8}hiPvh7a;AKWczXL3eQ2PCPqRQ|G{pBZiod>nos@8x|TtXFwH z+PhP+u@k>ca>pdO3&+UX>eYz`1t{06W$5jk{{6&ePzRncTG!MVc;v@ZIMjxWTzLE< znAgWTgo5W6ZH&A9{-M-uJ6mm3wfeJCsx77`Kytm!t|w%9!oal_N8SIY>lA&s?^ zc6Z!sMe-%kNu69XV`pu9`_=~Oto>-EadH$U?oY57>LMC%YDyhyiFPUY>_p{`*}-E+f$>9eFbgv5BU|`7}nTAP zfx6fh%Zc!$<&gH7bgZ@)6&5ZBz=JdRtjv3+dL~FpP|yiGTwH2e4S!iS|MLR{qdWi& z&VzZMZJbdj%Ypc#jG*U@6(_bxkxire{(}z`lpI0Fucu`sB~Q~F;3s2E#3jqkzu0F! zg5QG-RFB}DUzCr9V`7b=mPgC!&VOqq;3SYIwQp4ag@UA)LO&QZd~34LWTC}uAba^Z zyE|B#9t4@XVMjD#C_g%xH{ts~wI~)Ox^Hm-^b_mQ*@36KEw0TFPREJb>*9}PlPG`N zc|GHKTK{-rYU(!4mFx8MNECXG6ATQ~;L8_c7I-)Ul!?UHC0bf^S6AI?P9%rVEV+hR z(8{d+#FLeXQc(2xuAxq``~q~BE{)fFBf9u=m7{e7cs%O7w`GCySQ{!N z;hIPz@nRUCX5%T}l?<9253~Wmw)`X8w*vG!NU23}__rdW_GaVRLad$bqvO8HQ^WkfS zAXERPL?aLD(bR%&x6KJ8U`D>H{n>P~$Fou4Wq{9V$(}5pt|N@}th*9;ErSWjJMwcm zuv36PhJX^P#z+#4UrJhyC~Nb}N7IUgC1^uF=>xiF)gA*RkKvSfB+z9?8{-vf$$}HV ztzu=4z*DYuAOkwS+!ipYWCngl;RldQS~7 zastBe)XQ6uf2Po<(m~sG>!PK(`%&Fu)gk}Ii#6TYX9iS{>;jbk%>w9%r|mLIFUN&$ zFSUr=3Y~o)Ha|P5x0bLo12qiwqb6!)6&t_|!BTmMpq(9HhNXiiW}t98SE(lQF`Xi{=fyc?FS#j7G+0u(IAApPtz5Y5ot<|3r$k9nXrAlWPJVBu*GuPU?=qo`F8JzMnc6C$xg-!d z0D)ksj{7)Czt5_Y5)$%UI%}WVB1sD7WXK0@PI+!iT@v2VXC1XeJ3B6<%HZcUcEk5i z`+|x4Kol0^bG=&Fy{EF1V*K8M1i|r5V5O_xYjf{DpFYoq%65jai-GqrE>+UtE(KlQ zGD?U2iyFkJ2Ku4|k~#CXnrW1qW1@WJ#{@&EgOUKo@6~p;m6GXrZU$f<(y?fAud=eT zh{VFoAjuVl!~4`P<&1oFxjwf=vq~*!x?4wAtsUM%QzO~WhWK7ieRph)U<6ylVCInLA1uvo{EnSXH-u{I$G6Juf%qt3Uts`(JUHiEt0ulG4XR-#Nkx^vm z%BURJRMEljUpe$OJdUZ3k0%0bR(ocprBF@oB3!l}f79W}Bb~&jM(>=x{slT-S{7$# zK#7vxTV|x|NWV&JBYl>eyKUOilJ}eytnNT3@(NA`4}jt~dZcu>g>SxQ1Jhq-)qWO? zJateN9CntW0no{)QvMjHxAtjp|Fkp zblV`R0f+GybH+3P0sI$!-YF92c@-#f98AK`!0NMIzpTUSHGApJ`!d2Vx*C)YYkJbc zDhB{J7NPt*Hq|>L51#ntMD=GOA`qCptgKrGGR_UV%870Xu`K+lkE}jj)bkhubO)wm z>S@X#2u*>!R9O%LYEmX}L3QM}e(*lyXcvAOs>%_I9bFvutfx~>xb(g3=&iXs7TU5m z_X``VPI^?_!M}o8``rAui0Bj@WPSLPlEQ32?WybtfWz7GE{kBNbKuB*k1E)!VjY7e zrAEI)eMf=cDfyq|M)b6Mb*-Og46?NEezK2TM&Y-H>}LwKI1L=zqge~J`xT;zMO=HW zOqTB^t2C}qvqR$*t^t6&FIQ(!uXp98wTVcw2lHwDa9n4__ngr$O-DKm*V<>K2VtWr z($3CLR?`85QSW{nc@an~OP|JNGl418u`k zSMr(%8(+Ntm(m;3q)j$0W&{7RX`OnHo-fwI3+Cfv2bQUvgqnIR1#Nd`__Ls_+6hgNe9-PeG z5E`Yj^mVguRWV<>jbf^0V)^BYxh<}maEIuf7qsFfw>qeKQqtzzNsu+4*~e!*!s}At z*wGWge~dPw_*fYjIkd~(+0>_fCSYTn>D{roypPrHiQ%U~EPpM-zfs1+a7z>L0&w`< zyAuA02i`6rY5_g?tcMl-%7RC}Oylj0m%x2HdwSG0H8r)gypQuQP*S$Hw4A?q12LQ; z+{XTG1E1W-9iMrp2)bSBaBZx%g9vA)qMaSPv+3_`R|gvGI9`tSD*w!-xe$k%FXA(O z{eJMN5y1^%Z_%sZ7};@x`~T(~nG41jTU;2!CUO1x^*tb>kUO}EreG4F2a)^HUz_eX zNZnY;u+0oSg4b`kDFd}<+USQ=6g6QJN3^!L({ak4ovg$LfBk3T7HDo20dbhXt5@Yb z1LqnT7%;E*GH|y7!~y^s(n()%We8~tR7Pq#IxTf|bu~4b%a=jzwly>RmLz0SX*XJ8 z3>iLu;lkQhBAQw5OCy9k4Eg>pNGDCi&9%P ziZnv?8^TJVzqy2k-P&W=F{RY^3Bw1#P6KqZs(L$wqC;cl1k>ox^x$Qd?mxeOxkop7Klj1XK02S2}tV2=f@`M{o9*doPr1kf44SKAsAviTmul0y&YuZ)c)5T;A^m zL|dV;^MrLcl>lwlGc^s33^C{LVu2ff%KEiuY{ORk)aplf?*qY{Yw;OMhcyg1T`NR` ze#ZRHTrle!86ZPB z_9H3`R{#-(G3l}C`S}|*1w(X+?*UU-j)?tvWE+v z|4-*9ja>o)8YswUFzhrxqu|v4?UBpn**{f|lYjY&!x%h*k^gUlFWJJcn8M|IS>V@6 zU>;}u)=L4*0KG@BtKGq}@f()7kf9$}-Dr=x`8X#hhlmR%iSh6}uK=+zRHy|ic`b#D zY#CDAYinve>nVkhNFHcX5B1;*YmCQVMkXza}IM z*LypACk34Cw#$Lg_%3z`f(nElt^5aB{6K?UW(z`o_%6 zEb<*ApT3K2sDyZVc{u|cpCdS1Z~fIqKztIvk>)xZK#BW(a-hXam}?Gad?7aFq6nVE&CwSa2&}uze$Qj#)*mnd z+!|?ly`RFv5uiZ{!UP*8N>2P3w zF$JQI69+)VtRjXoNAuN~N)3U>26=>WFI(|a9={W4_=`GC`HY!+n|qC0+uPd%k7yXO zYX7a`N)C5oMFpdZ+Buo&!v|U~UhLW&R$73jGq_sG+RvXqLCaedwCDy@IN8NrvRLc^ zRM~6@wM?8>&x*ukTbLfVE z!wncyfKO6Q;ADgF5zC>qMvpb!&sCu#42LE8fP*5Af&0L^nZ-MpWiLCbP( ztQ2J8QMgFA_Zra;>wf`yJ;8pQS zmWo#QArJ4g{$xx{%;?>$c9maq?5}TgN+zh+Nw0ohF}FYS2-D7*@)**9sLll8Hz9wp za{8C{z%E_uH0(If8)(jG{wJ9pOW|2aB(1ah(g>tvk0N#CTQvGbh!V`Psyu5Rm3~hWWF^ZQg|&mam0Yvt6YBVgX4>ewjfpCj zXR0@yv$y7*IT?vygD)N$dgSYH=f58rb{-+Eq2KX^t=`iuU&ZFrya@8I9Fo(iTDS^C9FC> zda>5*(E)I^da)@ix4Nh{PY=f|aa9FvF)XeU)d*6neh3-a`JBJs5r9;R=^TgCawT~l z|6b3B-nRTHnJ+A!@DC&$$eHH`k|Od6#l&5#_q&Ce`51*mjU*vU zI57sQijA+?ARC-^cN+J%k2YfKUD`mgp`||fA7b-~-D}+<;)r;tBs+wOhgbi`Ky!|$ z=J)Qy4V}wZm>>R}Q3VHqGMUI##XJq%oMuQ9*}vsHcs_Xa4ka4&oe$oXqg$J|COl>`@eqNSC;vi{21AP#YQv3-_6KQD_#H~-% z<`HC@`i;zlGOX!ZpAjh^=rKMEtd?OUZ{L!|?i9%ghFKfwd8WlcxYTuR`(|#FKBLd} z{UOEhcx4%e{LrRzt2)X>PWcV}S_GB#oE)c`z28q7bWbC4Ib?45`DJ-ZgH;U)KCgvZqk|u9QqXzQekxCaP=HKn=LVB zc&qKAiN_&94ZrZG;TYQ zOMH1a;qhGc1(#A#cVb|~b;2c+rz65wR{!cc*vI0~0?$e&PxXTzb|vVU6k!<;9{XXh z(XZn)fil7R>_g+bJ&*hB^3<8gu8b7Z`^^O6c*RRX%kz<~*=les$u9?Q$-P{AFsSBP z^|um^y~q%NEc$ngh!)dZP<*M=hP@~lAh%WxVjas0MSuM!QR=<{dQ)S@VsAUHZkpJp zjYY523PnnOf5QCrdqxb;$OaSX^iPJ;ES6TmxXkbof`(?)27 zA`;7&YR$ZL9@znTPC9OvOSpio;1Owyy15T}bFzCKcUi0q4K~1>)}Pa{;_-WPGO1Aj zwt9HHf|k;i3(#9dY9)0TzXhG#Yh??Rw&EnDaEv}qCG5|4UqtI80gOiQcr@@ zG(i&L8Vq!;VEv!s-$^((M8#nNqiLj~(QVJv1HJH^!n6=YdScj?Z8k^5rx=e5hFVfa za``C<{a6fayYSZ-kYV-vpQzaY*!VTH^+xQBXr#wnY#wplyVpP>;_I-`(zyM@TTOo0 zfN8Hu$IO7e&-2aJbk}j~W3i9M2Dhr42%EM1R+DvnbrVfsWwCSXAte(faJ4%5;>?o4 z=}h85;qRzwEX8;fG2tp!vyH=7*k;oz?9&^8L484k84D?=z7|i5r!3;FpVkvlRu|<7h39j1*mV Q@E5WdN}A8$3a>)`2U~?-?f?J) literal 0 HcmV?d00001 diff --git a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/delete.html b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/delete.html new file mode 100644 index 0000000000..18229e4737 --- /dev/null +++ b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/delete.html @@ -0,0 +1,53 @@ + + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/delete.html.snap-1.png b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/delete.html.snap-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b947953c522a4fc13dbd342e41243915e0486e9f GIT binary patch literal 9477 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 literal 0 HcmV?d00001 diff --git a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/replace.html b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/replace.html new file mode 100644 index 0000000000..e71ab23198 --- /dev/null +++ b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/replace.html @@ -0,0 +1,78 @@ + + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/replace.html.snap-1.png b/__tests__/html2/middleware/avatar/legacyAvatarMiddleware/replace.html.snap-1.png new file mode 100644 index 0000000000000000000000000000000000000000..03b6aabe9ba3e40a813509a8e8ad7e9acf766b4c GIT binary patch literal 11580 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 literal 0 HcmV?d00001 diff --git a/__tests__/html2/middleware/avatar/polymiddleware/addNew.html b/__tests__/html2/middleware/avatar/polymiddleware/addNew.html new file mode 100644 index 0000000000..431e4be5bb --- /dev/null +++ b/__tests__/html2/middleware/avatar/polymiddleware/addNew.html @@ -0,0 +1,80 @@ + + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/middleware/avatar/polymiddleware/addNew.html.snap-1.png b/__tests__/html2/middleware/avatar/polymiddleware/addNew.html.snap-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b371ae90094750435bb5c7dfac31572a40edeec7 GIT binary patch literal 11546 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 literal 0 HcmV?d00001 diff --git a/__tests__/html2/middleware/avatar/polymiddleware/decorate.html b/__tests__/html2/middleware/avatar/polymiddleware/decorate.html new file mode 100644 index 0000000000..0049b0fbc4 --- /dev/null +++ b/__tests__/html2/middleware/avatar/polymiddleware/decorate.html @@ -0,0 +1,77 @@ + + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/middleware/avatar/polymiddleware/decorate.html.snap-1.png b/__tests__/html2/middleware/avatar/polymiddleware/decorate.html.snap-1.png new file mode 100644 index 0000000000000000000000000000000000000000..c3ac963ce1f017bc175aec1ac9fac50f220a61f5 GIT binary patch literal 13018 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 TSBy*1o=6`^#yhR2u1Ejq*%McWNvN zCY*zfKO97B2$fi!7;zIF^IT3RwRrxVHb5KFhmscZfmeom)4H zck!b@Y|A0+RjFk0?Io~A2V=Nnu6-gja)}5(7S)HFmY7GW+S3$^|K1=yN;|wwM^{Lq zpcy7(u%cNb*SwsjPyWWR50g-0Ty|fU6H2kMdJ&k`m^%79d$U@^OLZR$vpGOD=b7t3 zJDy43VMVh3Em(S%rIObg%K8DDpb8otjU&DVouoCqHncvWgl$(vRG#6pg=9 zr#UQw^q0Z*gV0OW5S_giv6%t+VQV^ibVuDE5tJF^nNb{avXrPj4F`NvVYa#?dP{ptL2_W5?h%FOPsyXXP|#V8 zn|&(G8Sqx3p6mHD$pW9|-8u&eXS0Mg23-<4h8!~_II=P`iXPWy=Q%ro#2!4TJV)Of zsHC*;Y0=|f8mQ1{y+O&5J2?+9G$cub;-(c0`+$YSMs4Hn*sHG?y(l$gLRGwua}NGN zseZXmup)%RQ`yv*Xl;9~ZnLGP>v3GxJ?~k{6_#%RFO}kXZFtA#npI%y9>r!~^RjM*mSLR@Q#P3_=h-*;$k2Kh{Mjh7#H&n7J&d4$VjX%~% zYRnsh3Z#RB~&<7iXqV2uzsNP3x;Vm1^ zBmdS5X8s|_2v@$pQA*GO$qy|r?r~}7=Xp1T&o(gf*nOyVE`4+m>GJ!-=pknqd9!!o zT8XTA9&7Y)n*s6uLw3D79rM+q^Ffi4VkUU=Qwqcqa-mppAl!TvnDS8}UHr1&{THV> z8ch*YDcmN73m+-PJaL|LfdGba*{qwww6s`yM2`jvEg_i!v>7dx#fqFR6wAdP`CgG% zoL(5_!CUE>xt_roa}yhC%iNo=A5pX_#1wH*CBv1=jz*%4qF!YN%otYW=^xcCZDg6B zqo0zBGKai1Dk(7f_8GacG=`626%B)+O2bM*3q7mpJ=ONBZOi({zVL~A_SmDnMq?(f z2et(1v5XAuZdMII^VK+sC@>||A}_za(8a`nKMdGk{6~O@G}2Q`09h;K(F-}V;Pse2 z`7Q=<-Ov!9-@a`or)PC>0+xGhJfG5sLTX*E{6VS$*#!eL4W$&(7+$H4s{pBulHxST z$%@T3l=l3d4POND89) zZn{6^KCs4LWZaUAGJ7I&vUX>b-EJs1+!&IN`iOzBP~T5ac<(hodvO9DJP*d&{ZH$Bmgqu9u+lv;7|Wl{^x9F>kWU zLypsW;gSFRZ@)>KeO))DYvtQ5_#noBElg=j)0^UN&!Fgsw0usDYY)z(F*e!*;iVi) z-sX~>sF)kqkA+`}PGU3<@O2{eC7fkZz#cr5sMd=0_0}%V767V&e-d*^(o*PoBoJaV zfrn<9o|sW3LeNK~m}e{gVCw|5LM{o@$-A!LuoQ!PAJ(a9tMz=fidoqX%kVzK$exIhz{0D?$7q)5FN7Ti+~9 zyjsi6x8mNvze;hbv@-|a+ + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/middleware/avatar/polymiddleware/delete.html.snap-1.png b/__tests__/html2/middleware/avatar/polymiddleware/delete.html.snap-1.png new file mode 100644 index 0000000000000000000000000000000000000000..b947953c522a4fc13dbd342e41243915e0486e9f GIT binary patch literal 9477 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 literal 0 HcmV?d00001 diff --git a/__tests__/html2/middleware/avatar/polymiddleware/replace.html b/__tests__/html2/middleware/avatar/polymiddleware/replace.html new file mode 100644 index 0000000000..c937ffdb99 --- /dev/null +++ b/__tests__/html2/middleware/avatar/polymiddleware/replace.html @@ -0,0 +1,77 @@ + + + + + + + + + + + +
+ + + diff --git a/__tests__/html2/middleware/avatar/polymiddleware/replace.html.snap-1.png b/__tests__/html2/middleware/avatar/polymiddleware/replace.html.snap-1.png new file mode 100644 index 0000000000000000000000000000000000000000..2f8f30ac89ed083cf53633377e4d8219441ff427 GIT binary patch literal 11555 zcmeHtXH*mIzHg8hQ529Sf)vpgK@gDMqzfoWl@KWb3n;xAdKE+z1f&Z{uStL?RZ0S= zfP(ZUHMCGdFQF%#$8*m9aL>JauXR4&wf2_~GFg+EdFKDC^VU#bo1Tu74g!JD-@ALq z7y_ZPgFsG&oH+$PQTFlNfIu!l?%h!{@%gwiaTZ|$&1~D$J#*>(r|>ILHy_iwKW0)> zH8P^IjkqYUBRsk&n2tb;xTx;gzf9#x)Vs*BCv!zmu>Gptd+3O5Twnx5{IB!sSNKx1 zd?lq*RpSqzOWNYH5)!tFgBC(bb&cNZCxt@IH*VZufZPn_hFoIeg8Vwe0D1NoEkrVu zI)tN{s_ESnM;8u25M<=r5*(h@Zf^zBRSuk%pI316_V)HB2`>ox zy{Esuy75!r%|=n8Z)@hM&)77Zj;WhgLufDy*dRVECu&tEC*d$RKzac05=69?a(Ihw zj_=3JMzM>mLH=T}C>7qdbgK6rCES({KnQXhMtDL_MexZni}6pM^perBC0?~Uv@4t= zP+D)usaJe*r$sJR&i~*{p*7{PLWU_qsJ`N-K9sxYh{hO-7KJUdGN@kJ5SEL{Qc^}t z8>X~nn(f?21XNNRP93muUCY(7{DNpEe14VxN?f>24nku!)cje@1;0bz56AU{gzM89 zZrE~j(Ekw&{zxwLz1;6Z*aU-xIlogI+^Xrc^y|ieZHcf5{ppC z7$7`cpTt^J+QqpLBH4r^S#L12u_Ybg5(ErzL=?J#AOuAX}bNI5SPuXAEJMlvgHuD=Q)pgK2J)8)davx8P76omKp@eyOTpm zN}RhJrLKlKFFSOu3(%ig#~rQBv_#2LcMiJqFhg%11YIY=x zcwVI$xfXldV{7hS>G2qzQW*AU&0^h-_cs|nb!1H086$sgRDY{!b84KyzWrIfmosPJ z_EFKh%z3PuUDWdTr^jKOnHSe;7c%GO&M(9c5G%2x8{pVK`H>vh)Yj^j%YOgfaqaHU zd3~OvFaKUbVq)S#rhl2w=8Q#+ONRW5`R{L8z`IiVI?v_8a}NN$sJ;iw*&S!z>hgpfNv@`iAI?v{TkbN z6~nSa5D9J|&mJi^h)tY^s-w?4iGQ;B9A8))KTgL$I)gCkT%26$FA_e-N;UcXe(~Q% zXw%Ye<)dGY@DzVCX;=(iw=-nnnsn35Shh$ny|511&H+~8Me6A1rxAVmmbD({R$tt5 zMk{7if99rN(~dEbwP{bhu{Y_D(e0GrF4fOaFgJzWRbqE8DMp!Bq3W19qQzKM>B1e$ zYPCApgiQxxI#R?MSF6Xg-d)Ohbl71{$tB6+11fYH=;Az=hmhK{As>I{w&g@VyS{hm zz*Yam+gbiIVJbBYS05B`sGX$FsPv*(BZGmgyjmn4)!|K?3*J6iDqpXM#pT3>t|!<# z3=|tR1_f53-^ElrkI6{Oi%H+1CxI0ltCd{7A)s^~{Ev;z&d!!`9^GFXERS#T?mx%c zE1Dz(HFnr{v6`&k9QT=xwsLqQ8&~OyR}REC_*Tq(t4STk-}4u}GFHCmZXyI-`V}rI zDl8UA?%NUVFDlFO+e%58c$OGCP=T<(E@m$M~JJuf`U1Wf`BuZ{#vvX z7Z;~XsdOAbVNq)HCVqGQFL=`RWtzNwznjmmlei&rRQQUlf=e;v+$xS|=H<=W6%`9R z_H9y4E@xl}ro?E>Ci00yyt9*?L5gL1$R}Gg$JK6ZxV5ySF}O6Z0=leJr>wHu$lZ}_ zwV{fRC5HI2CE^FptuI`1frp6EyxM_%L%U>AxWX#juTr!sZ!UzZ<4N&M@Z}&1df`ZR z$y)4YR3pR2>synstkysepT3WF<6k}(8o4x*cfC$@qjmC<-c=7b-GQDSG{mNB$MOtL z6&U4db*T$L$x!~YcutZZ!X|~9Gk$+QtlH^-R533dk-IKC?)I%cNjNZF#G?8>9@9wi zqn&$rvDy7WLApd8`k->2rSYUOzPbIIum*)E_ldT;aVUx>CH6rB$-ePwjh4AVTGVR> zZq$>xrkB)v>v&Ap4=mZ5FI|DH)Rh$KLN;)ryXnRV7IHi4CNZMd?!>w^e!$$ClFV3SSAV% z(K@K@h3;X-vUD8-s4w?a&CSp}i!c{aJfZuZG-ddX)5C`!=5IFkxttk#Ei}(gCgYSJxt#(SZHwh^(MPdp3!|2R*lAVAa2P^mdBBk5xIsIww%F zafyji&%3xVuX9J9kVDdCoKY~$Bp?rI>!W5`ULXG(Boo=y@!IW`kqWp2W&vzr*svHn z;mK+Ke){0RE9vkt$@~1TTvYqQ&dGJ3(dh)z7*RTlxYg_0*GBay-qY=Tt?dt}COM7$ zmwblZL-bybZmqsPJRon*hSfCAcxzvWA4oInaVwJu*JJ13+&p8nug@#o( z+U%xnVY&8KiN4z`_Z8azkp{V zw~hsBg2zd*)q`Vox3k|~<{qaybK&*W86jq=BgP7JQrW!6U{wJodWGY^IDW`dn(+Dg zMy5cT(zM6Vr+PeNiarSThI;VDBl}rS zl)2ra8kNbNO(&_NCu%K+vPS~kSK9Sn#W#k~yr=WhP22P1Y6Xy*}BkauU#)CJpX5ox697O-s#fZ025?09K_x zcW#@(gE)Zf&C@Q*&krCC!lKy4cFC6yt%D8-VT{UuSPO8F=gjxB+yV2yQURJ-nMZRg z?k@J`O*X#E6o0B^Y-~J;f^r+4Iqq`g0)qrZ0Cv>N!=wDFhC3L>?ms88Jd8msogCdE zWoKU@sYSAjDL-lo+27wEw(>89qD=u!m|PkxgC9-$uVVqn0(#8N%_RGJXMIS!X8dCa}Z%>lnk=t$|d)0L(a>ZLpINl8`S zPnB?YcUL(%uJD+y9&_o}v5j0C)YFW`InGYhdk>ddz?Q2(NL7U8GL$>+^5=@Z zz4bE@&K|Q>%VtjW*2MFcsK4UF-MT8vcx64W{Zf%D0{Dc#5ulW;pWCZ z@6LSInT8a|rHQsfxY4q5QjN?S5@vdGG$zZ1&P^V_Ib_9oAEG`^i!6%jeL2r_dp|b# zc(yA;QTp!Y(cfK{I|SVu(sgKEqa~hLqkvdI{hk)_muAX-f9A(jPmgNG8Tf9E%LIx* z97H=7=;WiU>ju=?%HZ{tZcBaqdiBLmPz0SMA>?|tvSx3tCYv}VAUWcRy0#TApjKD! zD)ccHQ4GUmcA=T{K0j$Op)&H+;5QbL%kDYzBPUWQr9H~Nzwi(L=fyf=$Rw8gK>Fq! z7R0N|UY2&K1dM8XXSfW`e)D1W*99OX90!W0J3|(azPQgSc(0YfB~;=ZiYvhR-+KJ@ zXMzA5{p2puz1R-bmTjv9A(;61eCZ;DsE09bQ9WwS`bho13iGsc68>|tmb+=sUBS@M z(nf@b)6fvQiY&f=j5>IBB8%rG2f2<%-&@dAOggje;BOn*jbjx-k(UL32QlEDC#_{+E@U^DRina(*F5=d2$*9b?F|oVPbwjDoK>mk+ z+OObmreZ}L&Ft#oZ%q)+(LX$1LR{cdRg-lXDV|HTNf8@Zx>W0AlbpUu`$$zS<-ZM7 z5o@xSzWtDnQ=k8BL+KvGg-_!Xd%PNa74@Y_{CQ!!v38(#y2S=h8l5&%mLq<%gHhTaYFy>NU`eLE0 zKsUwfhr>q+yXNex*P<5Dz4==82CG4hZAZ9zPJM@BlC(9Z7ID(ISJw!Wu0(B*70#wh z;6tWVWw@0C_9;i}7{eTOLVHrZ_d2EDK*bhE27{-7Se$akgjYEZ7mi)IapRsw4gfja zX6t41p8uB#dhrhvq@khViX{yJpV2es4Nw>0rbUB~`}TY%(8zdZlyiTk#gHXdym?={gGLUla~}_@hBbdYE;!iSU#zn!@Nyl9vgI8 zI8G+qJIi3x2Cn;PwX^^9*KqpFGR1uMYNf*R^55D|z+hMX-v;SquN5^lH3huUb-lFn z91?fV0ljzAs$qYq)B>Qky!*^|@Xj&aWugw4PfCJ;vXRq7;NFDJE6u2j_JIJ5fO{y| z(y_70pV7$a2T<(VNcQ>}&kr;da_D}V)K9bXmu^kfuj3x97}O01UBv zlVbqq!)O_Cz*hbtE6MKLv#WrWC7!mPSCaJeDbPvssQupkKb_#~y`23Q#?PQ`wnV1I zt@K`Oe~|&~qTs29?)aXhYCw%yfxD@kyKFVVz#Mtnn(7LhmFK(Yr_1OKF90VBgL$SP zxfQ(xKOjbix4wp-OA&>mK>UVId-D;e3}U8B-HzsW~Y| zam{dCE(&n&4W?0`RS64O!B%_)xcFosssq2#%f91QG&Rr%w%W>1_h1pKN7C?N=7IQtAUFF5^O$RQ?6f>LKK8kwDACy8Qgt=^PnX1E_unoF9xX}7u!>fDmBQ)KDLriIJ>jY7{Y=ShivD7QE&Mnv zG!#*_H-WY-2wN{?7tDo&tRsJ5V`3Z^SYKgC>oESsJ%WK7+`0WgkW5zEKXAnsCJ!8K zXrjum@$o(GFVq{gk`>E}Y?p^hNgkvd4a!{)I&m~v<@)lX%de)m`Dat1Y7Q_h+&4=gZy!s~wYVf(cnGyENsv6?4fxj`o`e;J3pwZWw zm2~DV9@1M12@@R6t75f;4EjDBt5Vh+qxB~OxfCa~+S(Swy?qh5)>#jW0^_7EOSuHX zv;V`&o%otUxjH>er4s-QQ8m9@Im5^ZD`_|+qU~XR83*sG4Pcoj_j1+3N)~APx&MX_ zE&lOXmbthvS2LO@3bNX1M8bd9DOzu!fb+J`r(7M%vsK&oj`Q!V#@QY@+q)fn z*G8j#h*3qBF-%-PSO8*VNxS(vMK;=0Ck#U)@eQYo9nYO`YEJ`%lJ~{5@6ak^0qAAZ-bx^R zhzyHAMC%jppv>%c=Z+7y({`YngBI46c1BR8qn)KZ8r&W&O>e zB68gm2b&;cMzB7HOKL2iK{5)jLl{N%L18Rh(vCyF?ED9>4~#-T4V0j^a)BKOQJS6% zux?~z+*bhJr4spRmGkZm_M17cG8~ej2z@ZGD7ePbZ=Nra^n7wjg4E?+i_( zN@L7vmdkSsV^CtmAYT0a9tuKL2C@rL-h=)&KDm%kmmeY;WkP9rBeo|?cfOg^cX)uV zQ*iNWyXJZ4)CFESV((iaE4X+^=NX18kcOW5@IDyfU@TlCDuTgPaXd@pEciI_?2YaD zK-aq4GT3c|%IsUf@EZ8#BW3)dZ_0DT1}&$`)I#fDv=?qs+=o?0O`>nvP z9!37cl%*Y>N$Fohy~Yg^M7Vo6n7C9`{mKs##{PY|Hrk#+v$$i<4;M z#SN>ZQ~V2!G5^|{U6G^X4$V{j*<#%WRL*nfbP~CDeYUHuh5vho{yr&TsN;3wAPj`# z7BI&Nv=OF5<+PIjnHY6}>({SKNlE?v_povh^GNdh_ZN71d3ku&PSH0TE&eSa(AL^| zou6O+c<3Ld>=b6;E+~{|CAF#kd6kyk0}vP{YWc;m_^%ZzVf=te!L$s%77ScBc zxO8ozKI^{cY_u#8EPsg2+^#2EZFJxQ&CBY5#hi%K^qh#*s$o%(&^ zrLb!);i6{PSAHp}9YAT5Snr?kQ3&LG)1T)8=&lD2Z{Y%ufDu_Zb)lImzsM^HkO2s; z^$RY9hwlXwC5`L7oSiW=G;OJp&nrK<&$cdO8m+kLL}VR@?oWFFA_J+7;PuLRV2+$e zD@W?RF}n0Ihot3t$Q?Jx5;AymF)tS79Lpj3Y-{c(K%;J(r}#z`Ox5mYUN|t5$v4gV zYF;K+X(^rK77!KHm&Mwqa9-V!3puq4zz4YA-DF_Wqu5VKEPH*}0{EEEIVL71&ZOeQ z?8hx7W)&Ybk;VnUc`b+lyK7eqEF!qXN*?1uuXK5sRzAG*R`Wh+}BPAR4;OIbFV_0LcQBg-FjMl>R&yfkt#38z^xz^OgVnq7Lx>0esG|F za$jo0AHOLHW57uk-A`ra;tBxwHw97Mnvf96X{0<>Y4;&8w4Rdk z^1eV74qFFfsSQ8;_TvJQsnnvTEQQm!F^D|d7ApXT976L;tnqke>s@J^J6Hy5hvT{z z8<=`D9>~nd4<9~&aj3W4nvMXV{*4jVfYwbB1^e)VNJs;z3hpciRW~pzASxXtpjLjN zila6=W%1)+EpgJI{OFInrfs=ayIfXpUTHTiqOGN63KCypyHJgSXdtG1Tz_^)$T#5+1)kYZ(gvIvChhF7Zmi)RSL4j9_1 z5{)I$jr<1Q2e&^HrSN9m5=9TR%o<1DuJ0MJu0gsq7dG-1I2&Zp_8qYMnO8Z;KOKCsq*$O zNuBq;+w;B_e=vBNO;w?))zM+{vaAaNNv)+9y)2hDXZ#KT-d2Y9WbbUZ9oDNC2O37Z zzWleDwD1^KnF0rj0)f3d0JU zJY9hm_H+;tY%qD`ft;XA>VyiIx@syV((?GIVosAkag+{=f?~~Y>!*Ng{i;j`>}A1P zSf*0xa78}ifpa}*tNc#y91F@dXfG~2hzPxxLW|vK5RQ&5yFiyn%Nt+_U!stY&`;6U z#mqJ!Bk=otkb32%tVwT+*~n-6{_D{?mJUU#WO2M;(=av#)GX4Ml$hw?aFs!p%^^p# z8*VKQW#)wrpCix1i=|8R*6BH=di+SM*nZ!DYp1HV@AIx;u7%%UD+N7R&6HA*(xdgl zuJjK&Pid7d;jg6tp&&d8B&1+Yet+IzMb3`Vx7~JCj#BoPmyr)RQTrZh^2^4KW5f$L zY#WkBTZUD(PLLW@bJ3=Vg5E3q37t|dgX=qXLP_caZxV0T`7No(_li;-SS2NkSU8-? zHV+B91(yVpd$wLTkRD&YM;s==R`0O=>2iydey~MpIPfWi8sEdRX~t(N(p39x)om4( z8v1y$;N5Pp*zeVS48qvE7G@B4i@oyJQy8NXUps75(=u3M{YYSWciMXxfvV7su135U zE`yyX4+oP|GQ~Yr6PXl9pbW+4AuICqEk|e3oU0=HCj&W6<@(k>c_yB7k|WozglW7j z4%!DnwMoGP+B>PldR^x$6Xl$>X)k3S@JpO||wxK5UA#-MaK>f0XfpjjBRVZrF-iUW0a+yRlI!{_18y?n; zjEdU6s=8E|qcb{tMDhA@Qp7;>zRb6-oR~BvJXur_w4ABrk9axo5IW383Wf>~K9oy{ zd2Nu5Q#5@_NfL&kbcz^!N4H+eCQZqEt$eVWakxy!S@oRM7LcN|x=7*_En0hb{Q<&_ zbP$O37C}!=k7*gy9N{0wJ<)9B!NXC3KEdoM!inq(X?4`N>DP4dNJxfB-X-!UPK%|s z;r#*teL_}5ZPVDL$YtzI?Rt?ZwjZ7P)=Kb@De>B*u$Br^Xk!vy2#z{hOHhZ_nx-f3 zo#BLzZ_fIm=5lWC<1brZrLfFCEk?1K=de9BP9wR#g_X;sJ9dW}VP1N?W!8;WEqv0q z=}Chdlffr}Ns^u-P{Cq`IcZ7?vL^VXt66^dlE56b^}d%-!uw zTsoDFRG?Rda?rQ3odfT_WEWts9IOo4CEsH#j zIeQmgJjma_5+cE$7v|Wfw)(fTK02D7=k~)=mz~94g(~wj*~mDsxG47+5T-m;FZj@Y zHI2usRy*T9f1>%yuT)8>(%{qOo}4h<(sACUm!vT?1vc_>F5he3q2Z)%gQvLw!Sk`%@1^8avPYb(PTWlfy+MVE`_-ZPtj^N%J= z9gNm{yTa^>o5bz={$`W-)1F!vx8CHe-LOCVvg>k@kFR@+dXWBa^ZM4Gll!K_SEFB5 z^_0OCTOv$RlIvUHt@a>QFfD>BZ6Nwn activityEnhancers.map(enhancer => () => enhancer), [activityEnhancers]); + const avatarEnhancers = useMemoWithPrevious>( + (prevAvatarEnhancers = []) => { + const avatarEnhancers = extractAvatarEnhancer(polymiddleware); + + // Checks for array equality, return previous version if nothing has changed. + return prevAvatarEnhancers.length === avatarEnhancers.length && + avatarEnhancers.every((middleware, index) => Object.is(middleware, prevAvatarEnhancers.at(index))) + ? prevAvatarEnhancers + : avatarEnhancers; + }, + [polymiddleware] + ); + + const avatarPolymiddleware = useMemo(() => avatarEnhancers.map(enhancer => () => enhancer), [avatarEnhancers]); + const errorBoxEnhancers = useMemoWithPrevious>( (prevErrorBoxEnhancers = []) => { const errorBoxEnhancers = extractErrorBoxEnhancer(polymiddleware); @@ -75,7 +91,9 @@ function PolymiddlewareComposer(props: PolymiddlewareComposerProps) { return ( - {children} + + {children} + ); } diff --git a/packages/api-middleware/src/avatarPolymiddleware.tsx b/packages/api-middleware/src/avatarPolymiddleware.tsx new file mode 100644 index 0000000000..d389a98366 --- /dev/null +++ b/packages/api-middleware/src/avatarPolymiddleware.tsx @@ -0,0 +1,92 @@ +import { validateProps } from '@msinternal/botframework-webchat-react-valibot'; +import { type WebChatActivity } from 'botframework-webchat-core'; +import React, { memo, useMemo } from 'react'; +import { any, boolean, custom, object, pipe, readonly, safeParse, type InferInput } from 'valibot'; + +import templatePolymiddleware, { + type InferHandler, + type InferHandlerResult, + type InferMiddleware, + type InferProps, + type InferProviderProps, + type InferRenderer, + type InferRequest +} from './private/templatePolymiddleware'; + +// This is for bridging legacy AvatarMiddleware, will be removed when AvatarMiddleware is removed. +// Customization developers should request access to styleOptions themselves someway, says, `polymiddleware={[createCustomAvatarPolymiddleware(styleOptions)]}`. +const __INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol = Symbol(); + +const { + createMiddleware: createAvatarPolymiddleware, + extractEnhancer: extractAvatarEnhancer, + Provider: AvatarPolymiddlewareProvider, + Proxy, + reactComponent: avatarComponent, + useBuildRenderCallback: useBuildRenderAvatarCallback +} = templatePolymiddleware< + { + readonly [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: any; + readonly activity: WebChatActivity; + readonly fromUser: boolean; + }, + { readonly children?: never } +>('avatar'); + +type AvatarPolymiddleware = InferMiddleware; +type AvatarPolymiddlewareHandler = InferHandler; +type AvatarPolymiddlewareHandlerResult = InferHandlerResult; +type AvatarPolymiddlewareProps = InferProps; +type AvatarPolymiddlewareRenderer = InferRenderer; +type AvatarPolymiddlewareRequest = InferRequest; +type AvatarPolymiddlewareProviderProps = InferProviderProps; + +const avatarPolymiddlewareProxyPropsSchema = pipe( + object({ + [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: any(), + activity: custom>(value => safeParse(object({}), value).success), + fromUser: boolean() + }), + readonly() +); + +type AvatarPolymiddlewareProxyProps = Readonly>; + +// A friendlier version than the organic . +const AvatarPolymiddlewareProxy = memo(function AvatarPolymiddlewareProxy(props: AvatarPolymiddlewareProxyProps) { + const { + [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: styleOptions, + activity, + fromUser + } = validateProps(avatarPolymiddlewareProxyPropsSchema, props); + + const request = useMemo( + () => + Object.freeze({ + [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: styleOptions, + activity, + fromUser + }), + [activity, fromUser, styleOptions] + ); + + return ; +}); + +export { + __INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol, + avatarComponent, + AvatarPolymiddlewareProvider, + AvatarPolymiddlewareProxy, + createAvatarPolymiddleware, + extractAvatarEnhancer, + useBuildRenderAvatarCallback, + type AvatarPolymiddleware, + type AvatarPolymiddlewareHandler, + type AvatarPolymiddlewareHandlerResult, + type AvatarPolymiddlewareProps, + type AvatarPolymiddlewareProviderProps, + type AvatarPolymiddlewareProxyProps, + type AvatarPolymiddlewareRenderer, + type AvatarPolymiddlewareRequest +}; diff --git a/packages/api-middleware/src/index.ts b/packages/api-middleware/src/index.ts index 9e0a8aeb8e..86603d221f 100644 --- a/packages/api-middleware/src/index.ts +++ b/packages/api-middleware/src/index.ts @@ -12,6 +12,21 @@ export { type ActivityPolymiddlewareRequest } from './activityPolymiddleware'; +export { + __INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol, + avatarComponent, + AvatarPolymiddlewareProxy, + createAvatarPolymiddleware, + useBuildRenderAvatarCallback, + type AvatarPolymiddleware, + type AvatarPolymiddlewareHandler, + type AvatarPolymiddlewareHandlerResult, + type AvatarPolymiddlewareProps, + type AvatarPolymiddlewareProxyProps, + type AvatarPolymiddlewareRenderer, + type AvatarPolymiddlewareRequest +} from './avatarPolymiddleware'; + export { createErrorBoxPolymiddleware, errorBoxComponent, diff --git a/packages/api-middleware/src/legacy.ts b/packages/api-middleware/src/legacy.ts index c9405239c4..db6fbf25b5 100644 --- a/packages/api-middleware/src/legacy.ts +++ b/packages/api-middleware/src/legacy.ts @@ -6,3 +6,5 @@ export { } from './legacy/activityMiddleware'; export { type LegacyAttachmentMiddleware, type LegacyRenderAttachment } from './legacy/attachmentMiddleware'; + +export { type LegacyAvatarMiddleware, type LegacyAvatarRenderer } from './legacy/avatarMiddleware'; diff --git a/packages/api-middleware/src/legacy/avatarMiddleware.ts b/packages/api-middleware/src/legacy/avatarMiddleware.ts new file mode 100644 index 0000000000..82dabcf71d --- /dev/null +++ b/packages/api-middleware/src/legacy/avatarMiddleware.ts @@ -0,0 +1,19 @@ +// TODO: This is moved from /api, need to revisit/rewrite everything in this file. +import { type WebChatActivity } from 'botframework-webchat-core'; +import { type ReactNode } from 'react'; + +type LegacyAvatarComponentFactoryArguments = { + readonly activity: WebChatActivity; + readonly fromUser: boolean; + readonly styleOptions: Readonly>; +}; + +type LegacyAvatarRenderer = false | (() => Exclude); + +type LegacyAvatarEnhancer = ( + next: (args: LegacyAvatarComponentFactoryArguments) => LegacyAvatarRenderer +) => (args: LegacyAvatarComponentFactoryArguments) => LegacyAvatarRenderer; + +type LegacyAvatarMiddleware = () => LegacyAvatarEnhancer; + +export type { LegacyAvatarMiddleware, LegacyAvatarRenderer }; diff --git a/packages/api/src/boot/internal.ts b/packages/api/src/boot/internal.ts index 1deec63da8..add882acb8 100644 --- a/packages/api/src/boot/internal.ts +++ b/packages/api/src/boot/internal.ts @@ -1,3 +1,4 @@ +export { __INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol } from '@msinternal/botframework-webchat-api-middleware'; export { default as LowPriorityDecoratorComposer } from '../decorator/internal/LowPriorityDecoratorComposer'; export { default as usePostVoiceActivity } from '../hooks/internal/usePostVoiceActivity'; export { default as useSetDictateState } from '../hooks/internal/useSetDictateState'; diff --git a/packages/api/src/boot/middleware.ts b/packages/api/src/boot/middleware.ts index 8d42eb1069..a38e239327 100644 --- a/packages/api/src/boot/middleware.ts +++ b/packages/api/src/boot/middleware.ts @@ -16,6 +16,20 @@ export { type ActivityPolymiddlewareRequest } from '@msinternal/botframework-webchat-api-middleware'; +export { + avatarComponent, + AvatarPolymiddlewareProxy, + createAvatarPolymiddleware, + useBuildRenderAvatarCallback, + type AvatarPolymiddleware, + type AvatarPolymiddlewareHandler, + type AvatarPolymiddlewareHandlerResult, + type AvatarPolymiddlewareProps, + type AvatarPolymiddlewareProxyProps, + type AvatarPolymiddlewareRenderer, + type AvatarPolymiddlewareRequest +} from '@msinternal/botframework-webchat-api-middleware'; + export { createErrorBoxPolymiddleware, errorBoxComponent, @@ -31,3 +45,5 @@ export { } from '@msinternal/botframework-webchat-api-middleware'; export { default as createActivityPolymiddlewareFromLegacy } from '../legacy/createActivityPolymiddlewareFromLegacy'; + +export { default as createAvatarPolymiddlewareFromLegacy } from '../legacy/createAvatarPolymiddlewareFromLegacy'; diff --git a/packages/api/src/hooks/Composer.tsx b/packages/api/src/hooks/Composer.tsx index 3b1794d86b..877931d423 100644 --- a/packages/api/src/hooks/Composer.tsx +++ b/packages/api/src/hooks/Composer.tsx @@ -50,6 +50,7 @@ import errorBoxTelemetryPolymiddleware from '../errorBox/errorBoxTelemetryPolymi import PrecompiledGlobalize from '../external/PrecompiledGlobalize'; import usePonyfill from '../hooks/usePonyfill'; import createActivityPolymiddlewareFromLegacy from '../legacy/createActivityPolymiddlewareFromLegacy'; +import createAvatarPolymiddlewareFromLegacy from '../legacy/createAvatarPolymiddlewareFromLegacy'; import getAllLocalizedStrings from '../localization/getAllLocalizedStrings'; import { SendBoxMiddlewareProvider, type SendBoxMiddleware } from '../middleware/SendBoxMiddleware'; import { @@ -213,12 +214,15 @@ function mergeStringsOverrides(localizedStrings, language, overrideLocalizedStri type ComposerCoreProps = Readonly<{ /** - * @deprecated The `activityMiddleware` prop is being deprecated, please use `polymiddleware` instead. This prop will be removed on or after 2027-08-21. + * @deprecated Use `polymiddleware` instead. The `activityMiddleware` prop is being deprecated, please use `polymiddleware` instead. This prop will be removed on or after 2027-08-21. */ activityMiddleware?: OneOrMany; activityStatusMiddleware?: OneOrMany; attachmentForScreenReaderMiddleware?: OneOrMany; attachmentMiddleware?: OneOrMany; + /** + * @deprecated Use `polymiddleware` instead. The `avatarMiddleware` prop is being deprecated, please use `polymiddleware` instead. This prop will be removed on or after 2028-03-16. + */ avatarMiddleware?: OneOrMany; cardActionMiddleware?: OneOrMany; children?: ReactNode | ((context: ContextOf>) => ReactNode); @@ -467,14 +471,11 @@ const ComposerCore = ({ [attachmentMiddleware] ); - const patchedAvatarRenderer = useMemo( + const polymiddlewareForLegacyAvatarMiddleware = useMemo( () => - applyMiddlewareForRenderer( - 'avatar', - { strict: false }, - ...singleToArray(avatarMiddleware), - () => () => () => false - )({}), + avatarMiddleware + ? Object.freeze([createAvatarPolymiddlewareFromLegacy(...singleToArray(avatarMiddleware))]) + : EMPTY_ARRAY, [avatarMiddleware] ); @@ -530,11 +531,32 @@ const ComposerCore = ({ // Error box telemetry polymiddleware is special and has a much higher priority. // This guarantees telemetry is always emitted for exception and no other polymiddleware can override this behavior. errorBoxTelemetryPolymiddleware, - ...(polymiddlewareFromProps || []), + + // # Why render legacy middleware before polymiddleware? + // + // - Legacy middleware should have high priority than defaults + // - Default middleware will be upgraded to polymiddleware, however, they should have lower priority + // - Default middleware are implemented in the `component` package, and passed via `polymiddleware` props + // - They are UI, cannot be implemented in `api` package + // + // We have a few way out, either one of the followings: + // + // - Add a new `lowPriorityPolymiddleware` props for default polymiddleware, so we can put them after legacy + // - We don't want any special treatments or any prioritization system + // - We put the upgrade logics inside both `api` and `component` package + // - `component` will upgrade legacy to polymiddleware and prioritize properly + // - Spaghetti code and it is difficult to test the logic in `api` package + // - We always render legacy middleware before polymiddleware + // - Default middleware are polymiddleware, has lower priority than legacy + // + // The simplest and logical move is #3: render legacy middleware before polymiddleware. + ...polymiddlewareForLegacyActivityMiddleware, + ...polymiddlewareForLegacyAvatarMiddleware, + ...(polymiddlewareFromProps || []), activityFallbackPolymiddleware ]), - [polymiddlewareForLegacyActivityMiddleware, polymiddlewareFromProps] + [polymiddlewareForLegacyActivityMiddleware, polymiddlewareForLegacyAvatarMiddleware, polymiddlewareFromProps] ); /** @@ -555,7 +577,6 @@ const ComposerCore = ({ activityStatusRenderer: patchedActivityStatusRenderer, attachmentForScreenReaderRenderer: patchedAttachmentForScreenReaderRenderer, attachmentRenderer: patchedAttachmentRenderer, - avatarRenderer: patchedAvatarRenderer, dir: patchedDir, directLine, downscaleImageToDataURL, @@ -589,7 +610,6 @@ const ComposerCore = ({ patchedActivityStatusRenderer, patchedAttachmentForScreenReaderRenderer, patchedAttachmentRenderer, - patchedAvatarRenderer, patchedDir, patchedGrammars, patchedLocalizedStrings, diff --git a/packages/api/src/hooks/internal/WebChatAPIContext.ts b/packages/api/src/hooks/internal/WebChatAPIContext.ts index a0bc434b51..f567c3dc7f 100644 --- a/packages/api/src/hooks/internal/WebChatAPIContext.ts +++ b/packages/api/src/hooks/internal/WebChatAPIContext.ts @@ -11,7 +11,6 @@ import { createContext } from 'react'; import { RenderActivityStatus } from '../../types/ActivityStatusMiddleware'; import { AttachmentForScreenReaderComponentFactory } from '../../types/AttachmentForScreenReaderMiddleware'; -import { AvatarComponentFactory } from '../../types/AvatarMiddleware'; import { PerformCardAction } from '../../types/CardActionMiddleware'; import { GroupActivities } from '../../types/GroupActivitiesMiddleware'; import LocalizedStrings from '../../types/LocalizedStrings'; @@ -25,7 +24,6 @@ export type WebChatAPIContextType = { activityStatusRenderer: RenderActivityStatus; attachmentForScreenReaderRenderer?: AttachmentForScreenReaderComponentFactory; attachmentRenderer?: LegacyRenderAttachment; - avatarRenderer: AvatarComponentFactory; clearSuggestedActions?: () => void; dir?: string; directLine?: DirectLineJSBotConnection; diff --git a/packages/api/src/hooks/useCreateAvatarRenderer.ts b/packages/api/src/hooks/useCreateAvatarRenderer.ts index 086e985e3a..61a47f69f5 100644 --- a/packages/api/src/hooks/useCreateAvatarRenderer.ts +++ b/packages/api/src/hooks/useCreateAvatarRenderer.ts @@ -1,40 +1,37 @@ -import { useMemo } from 'react'; -import useStyleOptions from './useStyleOptions'; -import useWebChatAPIContext from './internal/useWebChatAPIContext'; - -import type { AvatarComponentFactory } from '../types/AvatarMiddleware'; -import type { ReactNode } from 'react'; +import { + __INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol, + useBuildRenderAvatarCallback +} from '@msinternal/botframework-webchat-api-middleware'; import type { WebChatActivity } from 'botframework-webchat-core'; +import { useMemo, type ReactNode } from 'react'; +import useStyleOptions from './useStyleOptions'; +/** + * @deprecated Use `` or `useBuildRenderAvatarCallback` instead. This hook will be removed on or after 2028-03-16. + */ export default function useCreateAvatarRenderer(): ({ activity }: { activity: WebChatActivity; }) => false | (() => Exclude) { const [styleOptions] = useStyleOptions(); - const { avatarRenderer }: { avatarRenderer: AvatarComponentFactory } = useWebChatAPIContext(); + const buildRenderAvatar = useBuildRenderAvatarCallback(); return useMemo( () => ({ activity }) => { const { from: { role } = {} }: { from?: { role?: string } } = activity; - const result = avatarRenderer({ - activity, - fromUser: role === 'user', - styleOptions - }); - - if (result !== false && typeof result !== 'function') { - console.warn( - 'botframework-webchat: avatarMiddleware should return a function to render the avatar, or return false if avatar should be hidden. Please refer to HOOKS.md for details.' - ); - - return () => result; - } + const renderer = buildRenderAvatar( + Object.freeze({ + activity, + fromUser: role === 'user', + [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: styleOptions + }) + ); - return result; + return renderer ? (): ReactNode => renderer({}) : false; }, - [avatarRenderer, styleOptions] + [buildRenderAvatar, styleOptions] ); } diff --git a/packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx b/packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx new file mode 100644 index 0000000000..43343f80ff --- /dev/null +++ b/packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx @@ -0,0 +1,89 @@ +import { + __INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol, + avatarComponent, + createAvatarPolymiddleware, + type AvatarPolymiddleware +} from '@msinternal/botframework-webchat-api-middleware'; +import { type LegacyAvatarMiddleware } from '@msinternal/botframework-webchat-api-middleware/legacy'; +import { composeEnhancer } from 'handler-chain'; +import React, { Fragment, memo, type ReactNode } from 'react'; +import { custom, function_, never, object, optional, pipe, readonly, safeParse, type InferInput } from 'valibot'; + +type LegacyAvatarRenderFunction = () => Exclude; + +const legacyAvatarBridgeComponentPropsSchema = pipe( + object({ + children: optional(never()), + renderFn: custom(value => safeParse(function_(), value).success) + }), + readonly() +); + +type LegacyAvatarBridgeComponentProps = Readonly< + InferInput & { children?: never } +>; + +/** + * Bridge component for the legacy avatar middleware. + * Renders the result of the legacy render function. + */ +function LegacyAvatarBridge(props: LegacyAvatarBridgeComponentProps) { + const { renderFn } = props; + + return {renderFn()}; +} + +const MemoizedLegacyAvatarBridge = memo(LegacyAvatarBridge); + +/** + * Polyfill legacy avatar middleware into a polymiddleware. + * + * @deprecated Use `polymiddleware` instead. Legacy avatar middleware is being deprecated and will be removed on or after 2027-08-16. + * @param middleware An array of legacy avatar middleware. + * @returns A polymiddleware composed by legacy avatar middleware. + */ +function createAvatarPolymiddlewareFromLegacy(...middlewares: readonly LegacyAvatarMiddleware[]): AvatarPolymiddleware { + const legacyEnhancer = composeEnhancer(...middlewares.map(middleware => middleware())); + + return createAvatarPolymiddleware(next => { + const legacyHandler = legacyEnhancer(({ activity, fromUser, styleOptions }) => { + // Pass styleOptions through the polymiddleware chain via the internal runtime extension + // so downstream handlers (e.g. core middleware) can still read it. + const handler = next( + Object.freeze({ + activity, + fromUser, + [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: styleOptions + }) + ); + + // TODO: Warn if the result is wrong. Also add tests. + // if (result !== false && typeof result !== 'function') { + // console.warn( + // 'botframework-webchat: avatarMiddleware should return a function to render the avatar, or return false if avatar should be hidden. Please refer to HOOKS.md for details.' + // ); + + // return () => result; + // } + + return !!handler && ((): Exclude => handler.render({})); + }); + + return request => { + const { + activity, + fromUser, + [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: styleOptions + } = request; + + const legacyResult = legacyHandler(Object.freeze({ activity, fromUser, styleOptions })); + + return legacyResult + ? avatarComponent(MemoizedLegacyAvatarBridge, Object.freeze({ renderFn: legacyResult })) + : undefined; + }; + }); +} + +export default createAvatarPolymiddlewareFromLegacy; +export { legacyAvatarBridgeComponentPropsSchema, type LegacyAvatarBridgeComponentProps }; diff --git a/packages/bundle/src/boot/actual/middleware.ts b/packages/bundle/src/boot/actual/middleware.ts index 9641816786..39662aec03 100644 --- a/packages/bundle/src/boot/actual/middleware.ts +++ b/packages/bundle/src/boot/actual/middleware.ts @@ -13,6 +13,8 @@ export { type Polymiddleware } from 'botframework-webchat-api/middleware'; +export { createActivityPolymiddlewareFromLegacy } from 'botframework-webchat-api/middleware'; + export { createErrorBoxPolymiddleware, errorBoxComponent, @@ -27,4 +29,18 @@ export { type ErrorBoxPolymiddlewareRequest } from 'botframework-webchat-api/middleware'; -export { createActivityPolymiddlewareFromLegacy } from 'botframework-webchat-api/middleware'; +export { + avatarComponent, + AvatarPolymiddlewareProxy, + createAvatarPolymiddleware, + useBuildRenderAvatarCallback, + type AvatarPolymiddleware, + type AvatarPolymiddlewareHandler, + type AvatarPolymiddlewareHandlerResult, + type AvatarPolymiddlewareProps, + type AvatarPolymiddlewareProxyProps, + type AvatarPolymiddlewareRenderer, + type AvatarPolymiddlewareRequest +} from 'botframework-webchat-api/middleware'; + +export { createAvatarPolymiddlewareFromLegacy } from 'botframework-webchat-api/middleware'; diff --git a/packages/component/src/Activity/Avatar.tsx b/packages/component/src/Activity/Avatar.tsx index 5bd9995f44..ba405241bb 100644 --- a/packages/component/src/Activity/Avatar.tsx +++ b/packages/component/src/Activity/Avatar.tsx @@ -2,7 +2,7 @@ import { validateProps } from '@msinternal/botframework-webchat-react-valibot'; import React, { memo } from 'react'; import { boolean, object, optional, pipe, readonly, string, type InferInput } from 'valibot'; -import { DefaultAvatar } from '../Middleware/Avatar/createCoreMiddleware'; +import DefaultAvatar from '../Middleware/Avatar/DefaultAvatar'; const avatarPropsSchema = pipe( object({ diff --git a/packages/component/src/Composer.tsx b/packages/component/src/Composer.tsx index 5b651bf1ec..04c421ef3c 100644 --- a/packages/component/src/Composer.tsx +++ b/packages/component/src/Composer.tsx @@ -400,7 +400,7 @@ const Composer = ({ ); const patchedAvatarMiddleware = useMemo( - () => [...singleToArray(avatarMiddleware), ...theme.avatarMiddleware, ...createDefaultAvatarMiddleware()], + () => [...singleToArray(avatarMiddleware), ...theme.avatarMiddleware], [avatarMiddleware, theme.avatarMiddleware] ); @@ -414,7 +414,14 @@ const Composer = ({ ); const patchedPolymiddleware = useMemo( - () => Object.freeze([...(polymiddleware || []), ...theme.polymiddleware]), + () => + Object.freeze([ + ...(polymiddleware || []), + ...theme.polymiddleware, + // Polymiddleware has lower priority than legacy middleware. + // Later, we should move default middleware to a "default theme." + ...createDefaultAvatarMiddleware() + ]), [polymiddleware, theme.polymiddleware] ); diff --git a/packages/component/src/Middleware/Avatar/DefaultAvatar.tsx b/packages/component/src/Middleware/Avatar/DefaultAvatar.tsx new file mode 100644 index 0000000000..57119de079 --- /dev/null +++ b/packages/component/src/Middleware/Avatar/DefaultAvatar.tsx @@ -0,0 +1,62 @@ +import { validateProps } from '@msinternal/botframework-webchat-react-valibot'; +import classNames from 'classnames'; +import React, { memo } from 'react'; +import { boolean, never, object, optional, pipe, readonly, string, type InferInput } from 'valibot'; + +import ImageAvatar from '../../Avatar/ImageAvatar'; +import InitialsAvatar from '../../Avatar/InitialsAvatar'; +import { useStyleToEmotionObject } from '../../hooks/internal/styleToEmotionObject'; +import useStyleSet from '../../hooks/useStyleSet'; + +const ROOT_STYLE = { + overflow: ['hidden', 'clip'], + position: 'relative', + + '> *': { + left: 0, + position: 'absolute', + top: 0 + } +}; + +const defaultAvatarPropsSchema = pipe( + object({ + 'aria-hidden': optional(boolean(), true), + children: optional(never()), + className: optional(string()), + fromUser: boolean() + }), + readonly() +); + +// eslint-disable-next-line react/require-default-props +type DefaultAvatarProps = InferInput; + +function DefaultAvatar(props: DefaultAvatarProps) { + const { 'aria-hidden': ariaHidden, className, fromUser } = validateProps(defaultAvatarPropsSchema, props, 'strict'); + + const [{ avatar: avatarStyleSet }] = useStyleSet(); + const rootClassName = useStyleToEmotionObject()(ROOT_STYLE) + ''; + + return ( +
+ + +
+ ); +} + +DefaultAvatar.displayName = 'DefaultAvatar'; + +export default memo(DefaultAvatar); + +export { defaultAvatarPropsSchema, type DefaultAvatarProps }; diff --git a/packages/component/src/Middleware/Avatar/createCoreMiddleware.tsx b/packages/component/src/Middleware/Avatar/createCoreMiddleware.tsx index 1b153d9933..dcd580fade 100644 --- a/packages/component/src/Middleware/Avatar/createCoreMiddleware.tsx +++ b/packages/component/src/Middleware/Avatar/createCoreMiddleware.tsx @@ -1,75 +1,27 @@ -import { AvatarMiddleware } from 'botframework-webchat-api'; -import { validateProps } from '@msinternal/botframework-webchat-react-valibot'; -import classNames from 'classnames'; -import React, { memo } from 'react'; -import { boolean, object, optional, pipe, readonly, string, type InferInput } from 'valibot'; - -import ImageAvatar from '../../Avatar/ImageAvatar'; -import InitialsAvatar from '../../Avatar/InitialsAvatar'; -import { useStyleToEmotionObject } from '../../hooks/internal/styleToEmotionObject'; -import useStyleSet from '../../hooks/useStyleSet'; - -const ROOT_STYLE = { - overflow: ['hidden', 'clip'], - position: 'relative', - - '> *': { - left: 0, - position: 'absolute', - top: 0 - } -}; - -const defaultAvatarPropsSchema = pipe( - object({ - 'aria-hidden': optional(boolean()), - className: optional(string()), - fromUser: boolean() - }), - readonly() -); - -type DefaultAvatarProps = InferInput; - -function DefaultAvatar(props: DefaultAvatarProps) { - const { 'aria-hidden': ariaHidden = true, className, fromUser } = validateProps(defaultAvatarPropsSchema, props); - - const [{ avatar: avatarStyleSet }] = useStyleSet(); - const rootClassName = useStyleToEmotionObject()(ROOT_STYLE) + ''; - - return ( -
- - -
- ); +import { __INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol } from 'botframework-webchat-api/internal'; +import { + avatarComponent, + createAvatarPolymiddleware, + type AvatarPolymiddleware +} from 'botframework-webchat-api/middleware'; +import DefaultAvatar from './DefaultAvatar'; + +export default function createDefaultAvatarMiddleware(): readonly AvatarPolymiddleware[] { + return Object.freeze([ + createAvatarPolymiddleware( + _next => + ({ + fromUser, + [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: { + botAvatarImage, + botAvatarInitials, + userAvatarImage, + userAvatarInitials + } + }) => + (fromUser ? userAvatarImage || userAvatarInitials : botAvatarImage || botAvatarInitials) + ? avatarComponent(DefaultAvatar, Object.freeze({ fromUser })) + : undefined + ) + ]); } - -export default function createCoreAvatarMiddleware(): AvatarMiddleware[] { - return [ - () => - () => - ({ fromUser, styleOptions }) => { - const { botAvatarImage, botAvatarInitials, userAvatarImage, userAvatarInitials } = styleOptions; - - if (fromUser ? userAvatarImage || userAvatarInitials : botAvatarImage || botAvatarInitials) { - return () => ; - } - - return false; - } - ]; -} - -const MemoizedDefaultAvatar = memo(DefaultAvatar); - -export { MemoizedDefaultAvatar as DefaultAvatar, defaultAvatarPropsSchema, type DefaultAvatarProps }; diff --git a/packages/component/src/Transcript/hooks/useRenderActivityProps.ts b/packages/component/src/Transcript/hooks/useRenderActivityProps.ts index 2d4041f03a..62070d0ee3 100644 --- a/packages/component/src/Transcript/hooks/useRenderActivityProps.ts +++ b/packages/component/src/Transcript/hooks/useRenderActivityProps.ts @@ -1,4 +1,6 @@ import { hooks } from 'botframework-webchat-api'; +import { __INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol } from 'botframework-webchat-api/internal'; +import { useBuildRenderAvatarCallback } from 'botframework-webchat-api/middleware'; import { type WebChatActivity } from 'botframework-webchat-core'; import { useMemo, type ReactNode } from 'react'; @@ -8,7 +10,7 @@ import useFirstActivityInStatusGroup from '../../Middleware/ActivityGrouping/ui/ import useLastActivityInStatusGroup from '../../Middleware/ActivityGrouping/ui/StatusGrouping/useLastActivity'; import isZeroOrPositive from '../../Utils/isZeroOrPositive'; -const { useCreateActivityStatusRenderer, useCreateAvatarRenderer, useStyleOptions } = hooks; +const { useCreateActivityStatusRenderer, useStyleOptions } = hooks; type RenderActivityProps = { hideTimestamp: boolean; @@ -18,13 +20,15 @@ type RenderActivityProps = { }; const useRenderActivityProps = (activity: WebChatActivity): RenderActivityProps => { - const [{ bubbleFromUserNubOffset, bubbleNubOffset, groupTimestamp, showAvatarInGroup }] = useStyleOptions(); + const [styleOptions] = useStyleOptions(); const [firstActivityInSenderGroup] = useFirstActivityInSenderGroup(); const [firstActivityInStatusGroup] = useFirstActivityInStatusGroup(); const [lastActivityInSenderGroup] = useLastActivityInSenderGroup(); const [lastActivityInStatusGroup] = useLastActivityInStatusGroup(); const createActivityStatusRenderer = useCreateActivityStatusRenderer(); - const renderAvatar = useCreateAvatarRenderer(); + const buildRenderAvatar = useBuildRenderAvatarCallback(); + + const { bubbleFromUserNubOffset, bubbleNubOffset, groupTimestamp, showAvatarInGroup } = styleOptions; const hideAllTimestamps = groupTimestamp === false; const isFirstInSenderGroup = @@ -36,10 +40,20 @@ const useRenderActivityProps = (activity: WebChatActivity): RenderActivityProps const isLastInStatusGroup = lastActivityInStatusGroup === activity || typeof lastActivityInStatusGroup === 'undefined'; - const renderAvatarForSenderGroup = useMemo( - () => !!renderAvatar && renderAvatar({ activity }), - [activity, renderAvatar] - ); + const renderAvatarForSenderGroup = useMemo Exclude)>(() => { + const fromUser = activity.from?.role === 'user'; + // Pass styleOptions through the runtime object (not typed in public request) for internal use + // by the core middleware and legacy bridge handlers. + const renderer = buildRenderAvatar( + Object.freeze({ + [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: styleOptions, + activity, + fromUser + }) + ); + + return renderer ? (): ReactNode => renderer({}) : false; + }, [activity, buildRenderAvatar, styleOptions]); const isTopSideBotNub = isZeroOrPositive(bubbleNubOffset); const isTopSideUserNub = isZeroOrPositive(bubbleFromUserNubOffset); From 446b23bb6f576be5551b7028f9f2839b9d4c54ab Mon Sep 17 00:00:00 2001 From: William Wong Date: Thu, 19 Mar 2026 09:07:57 +0000 Subject: [PATCH 05/11] Keep original request object --- packages/api-middleware/src/index.ts | 1 + .../src/legacy/avatarMiddleware.ts | 12 ++++- .../createAvatarPolymiddlewareFromLegacy.tsx | 53 +++++++++++-------- packages/api/src/types/AvatarMiddleware.ts | 5 ++ 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/packages/api-middleware/src/index.ts b/packages/api-middleware/src/index.ts index 86603d221f..80a766e350 100644 --- a/packages/api-middleware/src/index.ts +++ b/packages/api-middleware/src/index.ts @@ -42,5 +42,6 @@ export { } from './errorBoxPolymiddleware'; // TODO: [P0] Add tests for nesting `polymiddleware`. +export { __INTERNAL_DO_NOT_USE__legacyAvatarMiddlewareOriginalRequestSymbol } from './legacy/avatarMiddleware'; export { default as PolymiddlewareComposer } from './PolymiddlewareComposer'; export { type Polymiddleware } from './types/Polymiddleware'; diff --git a/packages/api-middleware/src/legacy/avatarMiddleware.ts b/packages/api-middleware/src/legacy/avatarMiddleware.ts index 82dabcf71d..6899783972 100644 --- a/packages/api-middleware/src/legacy/avatarMiddleware.ts +++ b/packages/api-middleware/src/legacy/avatarMiddleware.ts @@ -1,8 +1,14 @@ // TODO: This is moved from /api, need to revisit/rewrite everything in this file. import { type WebChatActivity } from 'botframework-webchat-core'; import { type ReactNode } from 'react'; +import type { AvatarPolymiddlewareRequest } from '../avatarPolymiddleware'; + +// Polymiddleware requires immutable request object. +// When bridging between legacy and polymiddlware, this symbol helps keeping the original object. +const __INTERNAL_DO_NOT_USE__legacyAvatarMiddlewareOriginalRequestSymbol = Symbol(); type LegacyAvatarComponentFactoryArguments = { + readonly [__INTERNAL_DO_NOT_USE__legacyAvatarMiddlewareOriginalRequestSymbol]: AvatarPolymiddlewareRequest; readonly activity: WebChatActivity; readonly fromUser: boolean; readonly styleOptions: Readonly>; @@ -16,4 +22,8 @@ type LegacyAvatarEnhancer = ( type LegacyAvatarMiddleware = () => LegacyAvatarEnhancer; -export type { LegacyAvatarMiddleware, LegacyAvatarRenderer }; +export { + __INTERNAL_DO_NOT_USE__legacyAvatarMiddlewareOriginalRequestSymbol, + type LegacyAvatarMiddleware, + type LegacyAvatarRenderer +}; diff --git a/packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx b/packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx index 43343f80ff..f1d3f3bea6 100644 --- a/packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx +++ b/packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx @@ -1,5 +1,6 @@ import { __INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol, + __INTERNAL_DO_NOT_USE__legacyAvatarMiddlewareOriginalRequestSymbol, avatarComponent, createAvatarPolymiddleware, type AvatarPolymiddleware @@ -46,37 +47,45 @@ function createAvatarPolymiddlewareFromLegacy(...middlewares: readonly LegacyAva const legacyEnhancer = composeEnhancer(...middlewares.map(middleware => middleware())); return createAvatarPolymiddleware(next => { - const legacyHandler = legacyEnhancer(({ activity, fromUser, styleOptions }) => { - // Pass styleOptions through the polymiddleware chain via the internal runtime extension - // so downstream handlers (e.g. core middleware) can still read it. - const handler = next( - Object.freeze({ - activity, - fromUser, - [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: styleOptions - }) - ); + const legacyHandler = legacyEnhancer( + ({ [__INTERNAL_DO_NOT_USE__legacyAvatarMiddlewareOriginalRequestSymbol]: originalRequest }) => { + if (!originalRequest) { + // TODO: Add a test + throw new Error('botframework-webchat: `avatarMiddleware` must not modify the request object'); + } + + // Pass styleOptions through the polymiddleware chain via the internal runtime extension + // so downstream handlers (e.g. core middleware) can still read it. + const handler = next(originalRequest); - // TODO: Warn if the result is wrong. Also add tests. - // if (result !== false && typeof result !== 'function') { - // console.warn( - // 'botframework-webchat: avatarMiddleware should return a function to render the avatar, or return false if avatar should be hidden. Please refer to HOOKS.md for details.' - // ); + // TODO: Warn if the result is wrong. Also add tests. + // if (result !== false && typeof result !== 'function') { + // console.warn( + // 'botframework-webchat: avatarMiddleware should return a function to render the avatar, or return false if avatar should be hidden. Please refer to HOOKS.md for details.' + // ); - // return () => result; - // } + // return () => result; + // } - return !!handler && ((): Exclude => handler.render({})); - }); + return !!handler && ((): Exclude => handler.render({})); + } + ); return request => { const { + [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: styleOptions, activity, - fromUser, - [__INTERNAL_DO_NOT_USE__avatarPolymiddlewareRequestStyleOptionsSymbol]: styleOptions + fromUser } = request; - const legacyResult = legacyHandler(Object.freeze({ activity, fromUser, styleOptions })); + const legacyResult = legacyHandler( + Object.freeze({ + activity, + fromUser, + styleOptions, + [__INTERNAL_DO_NOT_USE__legacyAvatarMiddlewareOriginalRequestSymbol]: request + }) + ); return legacyResult ? avatarComponent(MemoizedLegacyAvatarBridge, Object.freeze({ renderFn: legacyResult })) diff --git a/packages/api/src/types/AvatarMiddleware.ts b/packages/api/src/types/AvatarMiddleware.ts index 351b11f9fa..50b3c3dd40 100644 --- a/packages/api/src/types/AvatarMiddleware.ts +++ b/packages/api/src/types/AvatarMiddleware.ts @@ -1,10 +1,15 @@ import { type WebChatActivity } from 'botframework-webchat-core'; +import type { + __INTERNAL_DO_NOT_USE__legacyAvatarMiddlewareOriginalRequestSymbol, + AvatarPolymiddlewareRequest +} from '@msinternal/botframework-webchat-api-middleware'; import { StrictStyleOptions } from '../StyleOptions'; import ComponentMiddleware, { ComponentFactory } from './ComponentMiddleware'; type AvatarComponentFactoryArguments = [ { + [__INTERNAL_DO_NOT_USE__legacyAvatarMiddlewareOriginalRequestSymbol]: AvatarPolymiddlewareRequest; activity: WebChatActivity; fromUser: boolean; styleOptions: StrictStyleOptions; From b808d04ed89d7dfa9fd8954861fc9a202e1609e3 Mon Sep 17 00:00:00 2001 From: William Wong Date: Thu, 19 Mar 2026 09:08:45 +0000 Subject: [PATCH 06/11] Fix ESLint --- packages/core-debug-api/src/RestrictedDebugAPI.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-debug-api/src/RestrictedDebugAPI.ts b/packages/core-debug-api/src/RestrictedDebugAPI.ts index 79472fe7b1..8ef030a7ad 100644 --- a/packages/core-debug-api/src/RestrictedDebugAPI.ts +++ b/packages/core-debug-api/src/RestrictedDebugAPI.ts @@ -3,7 +3,7 @@ import DebugAPI from './private/DebugAPI'; import type { BaseContext, BreakpointObject, RestrictedDebugAPIType } from './types'; // 🔒 This function must be left empty. -// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars +// eslint-disable-next-line @typescript-eslint/no-empty-function const BREAKPOINT_FUNCTION = (__DEBUG_CONTEXT__: T) => {}; type AsGetters = { From 72443885e955858197b2ec9ef468f079176f3fb8 Mon Sep 17 00:00:00 2001 From: William Wong Date: Thu, 19 Mar 2026 09:11:45 +0000 Subject: [PATCH 07/11] Fix ESLint --- packages/component/src/Middleware/Avatar/DefaultAvatar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/component/src/Middleware/Avatar/DefaultAvatar.tsx b/packages/component/src/Middleware/Avatar/DefaultAvatar.tsx index 57119de079..9a84697b0b 100644 --- a/packages/component/src/Middleware/Avatar/DefaultAvatar.tsx +++ b/packages/component/src/Middleware/Avatar/DefaultAvatar.tsx @@ -29,7 +29,6 @@ const defaultAvatarPropsSchema = pipe( readonly() ); -// eslint-disable-next-line react/require-default-props type DefaultAvatarProps = InferInput; function DefaultAvatar(props: DefaultAvatarProps) { From 1ee89561e7283c67ac1073ce35197354a9c75823 Mon Sep 17 00:00:00 2001 From: William Wong Date: Thu, 19 Mar 2026 09:20:12 +0000 Subject: [PATCH 08/11] Move to macOS 26 --- .github/workflows/pull-request-validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request-validation.yml b/.github/workflows/pull-request-validation.yml index 5e14e26873..370f51e8ee 100644 --- a/.github/workflows/pull-request-validation.yml +++ b/.github/workflows/pull-request-validation.yml @@ -101,7 +101,7 @@ jobs: strategy: matrix: os: - - macos-latest + - macos-26 - ubuntu-latest - windows-latest runs-on: ${{ matrix.os }} From d431dd36f849d9252caddbd3e7658f5b5c182037 Mon Sep 17 00:00:00 2001 From: William Wong Date: Thu, 19 Mar 2026 09:33:50 +0000 Subject: [PATCH 09/11] Fix legacy avatar bridge --- .../createAvatarPolymiddlewareFromLegacy.tsx | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx b/packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx index f1d3f3bea6..a547decaff 100644 --- a/packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx +++ b/packages/api/src/legacy/createAvatarPolymiddlewareFromLegacy.tsx @@ -58,15 +58,6 @@ function createAvatarPolymiddlewareFromLegacy(...middlewares: readonly LegacyAva // so downstream handlers (e.g. core middleware) can still read it. const handler = next(originalRequest); - // TODO: Warn if the result is wrong. Also add tests. - // if (result !== false && typeof result !== 'function') { - // console.warn( - // 'botframework-webchat: avatarMiddleware should return a function to render the avatar, or return false if avatar should be hidden. Please refer to HOOKS.md for details.' - // ); - - // return () => result; - // } - return !!handler && ((): Exclude => handler.render({})); } ); @@ -87,9 +78,23 @@ function createAvatarPolymiddlewareFromLegacy(...middlewares: readonly LegacyAva }) ); - return legacyResult - ? avatarComponent(MemoizedLegacyAvatarBridge, Object.freeze({ renderFn: legacyResult })) - : undefined; + if (!legacyResult) { + return; + } + + let props: LegacyAvatarBridgeComponentProps; + + if (typeof legacyResult !== 'function') { + console.warn( + 'botframework-webchat: avatarMiddleware should return a function to render the avatar, or return false if avatar should be hidden. Please refer to HOOKS.md for details.' + ); + + props = Object.freeze({ renderFn: () => legacyResult }); + } else { + props = Object.freeze({ renderFn: legacyResult }); + } + + return avatarComponent(MemoizedLegacyAvatarBridge, props); }; }); } From d5c6b46436edd0f3f8694601b9f5117368afaff4 Mon Sep 17 00:00:00 2001 From: William Wong Date: Fri, 20 Mar 2026 02:19:04 +0000 Subject: [PATCH 10/11] Deprioritize default activity middleware --- .../src/private/templatePolymiddleware.tsx | 18 +++++++++++++++++- packages/api/src/hooks/Composer.tsx | 2 +- .../createActivityPolymiddlewareFromLegacy.tsx | 11 ++++++++++- packages/component/src/Composer.tsx | 5 +++-- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/api-middleware/src/private/templatePolymiddleware.tsx b/packages/api-middleware/src/private/templatePolymiddleware.tsx index 9b9cd80541..db84af06b7 100644 --- a/packages/api-middleware/src/private/templatePolymiddleware.tsx +++ b/packages/api-middleware/src/private/templatePolymiddleware.tsx @@ -19,6 +19,8 @@ const isArrayOfFunction = (middleware: unknown): middleware is InferOutput = next => request => next(request); +const DEBUG_ENHANCER_SYMBOL = Symbol('OriginalEnhancer'); +const DEBUG_NAME_SYMBOL = Symbol('MiddlewareName'); const EMPTY_ARRAY = Object.freeze([]); // Following @types/react to use {} for props. @@ -54,7 +56,21 @@ function templatePolymiddleware(name: string) { // We enforce middleware to be created using factory function. Object.defineProperty(taggedEnhancer, middlewareFactoryTag, { enumerable: false }); - return init => (init === name ? taggedEnhancer : BYPASS_ENHANCER); + const middleware: TemplatedMiddleware = init => (init === name ? taggedEnhancer : BYPASS_ENHANCER); + + Object.defineProperty(middleware, DEBUG_ENHANCER_SYMBOL, { + configurable: false, + value: enhancer, + writable: false + }); + + Object.defineProperty(middleware, DEBUG_NAME_SYMBOL, { + configurable: false, + value: name, + writable: false + }); + + return middleware; }; const warnInvalidExtraction = warnOnce(`Middleware passed for extraction of "${name}" must be an array of function`); diff --git a/packages/api/src/hooks/Composer.tsx b/packages/api/src/hooks/Composer.tsx index 877931d423..815ebc42f8 100644 --- a/packages/api/src/hooks/Composer.tsx +++ b/packages/api/src/hooks/Composer.tsx @@ -85,10 +85,10 @@ import isObject from '../utils/isObject'; import mapMap from '../utils/mapMap'; import normalizeLanguage from '../utils/normalizeLanguage'; import Tracker from './internal/Tracker'; -import useVoiceHandlers from './internal/useVoiceHandlers'; import WebChatAPIContext, { type WebChatAPIContextType } from './internal/WebChatAPIContext'; import WebChatReduxContext, { useDispatch } from './internal/WebChatReduxContext'; import defaultSelectVoice from './internal/defaultSelectVoice'; +import useVoiceHandlers from './internal/useVoiceHandlers'; import activityFallbackPolymiddleware from './middleware/activityFallbackPolymiddleware'; import applyMiddleware, { forLegacyRenderer as applyMiddlewareForLegacyRenderer, diff --git a/packages/api/src/legacy/createActivityPolymiddlewareFromLegacy.tsx b/packages/api/src/legacy/createActivityPolymiddlewareFromLegacy.tsx index 8135ee1040..a1f8c1e076 100644 --- a/packages/api/src/legacy/createActivityPolymiddlewareFromLegacy.tsx +++ b/packages/api/src/legacy/createActivityPolymiddlewareFromLegacy.tsx @@ -27,6 +27,7 @@ import { import LegacyActivityBridge from './LegacyActivityBridge'; +const DEBUG_ORIGINAL_LEGACY_MIDDLEWARE_SYMBOL = Symbol('OriginalLegacyMiddleware'); const webChatActivitySchema = custom(value => safeParse(object({}), value).success); type LegacyRenderFunction = ( @@ -87,7 +88,7 @@ function createActivityPolymiddlewareFromLegacy( ): ActivityPolymiddleware { const legacyEnhancer = composeEnhancer(...middleware.map(middleware => middleware())); - return createActivityPolymiddleware(next => { + const polymiddleware = createActivityPolymiddleware(next => { const legacyHandler = legacyEnhancer(request => { const handler = next(request); @@ -102,6 +103,14 @@ function createActivityPolymiddlewareFromLegacy( : undefined; }; }); + + Object.defineProperty(polymiddleware, DEBUG_ORIGINAL_LEGACY_MIDDLEWARE_SYMBOL, { + configurable: false, + value: Object.freeze([...middleware]), + writable: false + }); + + return polymiddleware; } export default createActivityPolymiddlewareFromLegacy; diff --git a/packages/component/src/Composer.tsx b/packages/component/src/Composer.tsx index 04c421ef3c..485ed99c1f 100644 --- a/packages/component/src/Composer.tsx +++ b/packages/component/src/Composer.tsx @@ -11,7 +11,7 @@ import { type SendBoxToolbarMiddleware } from 'botframework-webchat-api'; import { DecoratorComposer, type DecoratorMiddleware } from 'botframework-webchat-api/decorator'; -import { type Polymiddleware } from 'botframework-webchat-api/middleware'; +import { createActivityPolymiddlewareFromLegacy, type Polymiddleware } from 'botframework-webchat-api/middleware'; import { singleToArray } from 'botframework-webchat-core'; import classNames from 'classnames'; import MarkdownIt from 'markdown-it'; @@ -368,7 +368,7 @@ const Composer = ({ const theme = useTheme(); const patchedActivityMiddleware = useMemo( - () => [...singleToArray(activityMiddleware), ...theme.activityMiddleware, ...createDefaultActivityMiddleware()], + () => [...singleToArray(activityMiddleware), ...theme.activityMiddleware], [activityMiddleware, theme.activityMiddleware] ); @@ -420,6 +420,7 @@ const Composer = ({ ...theme.polymiddleware, // Polymiddleware has lower priority than legacy middleware. // Later, we should move default middleware to a "default theme." + createActivityPolymiddlewareFromLegacy(...createDefaultActivityMiddleware()), ...createDefaultAvatarMiddleware() ]), [polymiddleware, theme.polymiddleware] From 8ee929b3c07139cca679c939aec80c647831bbee Mon Sep 17 00:00:00 2001 From: William Wong Date: Fri, 20 Mar 2026 06:46:00 +0000 Subject: [PATCH 11/11] Patch attachmentLayout instead of via activityMiddleware --- .../ActivityGroupingSurface.js | 40 +++++-------------- .../createDirectLineWithTranscript.js | 7 +++- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/packages/test/page-object/src/globals/testHelpers/activityGrouping/ActivityGroupingSurface.js b/packages/test/page-object/src/globals/testHelpers/activityGrouping/ActivityGroupingSurface.js index 97e0782380..b6093d354b 100644 --- a/packages/test/page-object/src/globals/testHelpers/activityGrouping/ActivityGroupingSurface.js +++ b/packages/test/page-object/src/globals/testHelpers/activityGrouping/ActivityGroupingSurface.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; -import ActivityGroupingContext from './ActivityGroupingContext'; import createDirectLineWithTranscript from '../createDirectLineWithTranscript'; +import ActivityGroupingContext from './ActivityGroupingContext'; // Use React from window (UMD) instead of import. const { React: { useEffect, useMemo, useState } = {} } = window; @@ -23,22 +23,6 @@ const URL_QUERY_MAPPING = { wd: 'hide' }; -function createCustomActivityMiddleware(attachmentLayout) { - return () => - next => - (arg0, ...args) => - next( - { - ...arg0, - activity: { - ...arg0.activity, - ...(attachmentLayout && arg0.activity.from.role === 'bot' ? { attachmentLayout } : {}) - } - }, - ...args - ); -} - function generateURL(state) { const params = {}; @@ -143,7 +127,15 @@ const ActivityGroupingSurface = ({ children }) => { let directLine; (async function () { - directLine = await createDirectLineWithTranscript(transcriptName); + directLine = await createDirectLineWithTranscript(transcriptName, { + patchActivity: activity => { + if ((attachmentLayout === 'carousel' || attachmentLayout === 'stacked') && activity.from?.role === 'bot') { + return Object.freeze({ ...activity, attachmentLayout }); + } + + return activity; + } + }); aborted || setDirectLine(directLine); })(); @@ -152,15 +144,7 @@ const ActivityGroupingSurface = ({ children }) => { aborted = true; directLine && directLine.end(); }; - }, [setDirectLine, transcriptName]); - - const activityMiddleware = useMemo( - () => - attachmentLayout === 'carousel' || attachmentLayout === 'stacked' - ? createCustomActivityMiddleware(attachmentLayout) - : undefined, - [attachmentLayout] - ); + }, [attachmentLayout, setDirectLine, transcriptName]); const styleOptions = useMemo( () => ({ @@ -223,7 +207,6 @@ const ActivityGroupingSurface = ({ children }) => { const context = useMemo( () => ({ ...contextState, - activityMiddleware, directLine, setAttachmentLayout, setBotAvatarInitials, @@ -242,7 +225,6 @@ const ActivityGroupingSurface = ({ children }) => { url }), [ - activityMiddleware, contextState, directLine, setAttachmentLayout, diff --git a/packages/test/page-object/src/globals/testHelpers/createDirectLineWithTranscript.js b/packages/test/page-object/src/globals/testHelpers/createDirectLineWithTranscript.js index c1e1fe0b72..a8218f28ce 100644 --- a/packages/test/page-object/src/globals/testHelpers/createDirectLineWithTranscript.js +++ b/packages/test/page-object/src/globals/testHelpers/createDirectLineWithTranscript.js @@ -36,10 +36,13 @@ function createUpdateRelativeTimestamp(now, { Date }) { export default function createDirectLineWithTranscript( activitiesOrFilename, - { overridePostActivity, ponyfill: { Date } = { Date: window.Date } } = {} + { overridePostActivity, patchActivity: patchActivityFromOptions, ponyfill: { Date } = { Date: window.Date } } = {} ) { const now = Date.now(); - const patchActivity = createUpdateRelativeTimestamp(now, { Date }); + const patchActivity = activity => + createUpdateRelativeTimestamp(now, { Date })( + patchActivityFromOptions ? patchActivityFromOptions(activity) : activity + ); const connectionStatusDeferredObservable = createDeferredObservable(() => { connectionStatusDeferredObservable.next(0); });