@tuturuuu/ui 0.25.2 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -0
- package/biome.json +1 -1
- package/package.json +13 -12
- package/src/components/ui/chat/chat-sidebar-items.tsx +25 -19
- package/src/components/ui/chat/chat-utils.test.ts +88 -2
- package/src/components/ui/chat/chat-workspace.tsx +1 -0
- package/src/components/ui/chat/composer-attachment-chip.tsx +128 -0
- package/src/components/ui/chat/external-message-content.test.ts +41 -0
- package/src/components/ui/chat/external-message-content.ts +28 -0
- package/src/components/ui/chat/message-bubble.tsx +4 -2
- package/src/components/ui/chat/message-composer.tsx +121 -33
- package/src/components/ui/chat/message-links.test.tsx +14 -0
- package/src/components/ui/chat/utils.ts +39 -3
- package/src/components/ui/custom/education/courses/course-row-actions.tsx +1 -1
- package/src/components/ui/custom/education/modules/course-module-row-actions.tsx +1 -1
- package/src/components/ui/custom/structure.test.tsx +55 -1
- package/src/components/ui/custom/structure.tsx +10 -1
- package/src/components/ui/custom/tables/custom-data-table.tsx +3 -2
- package/src/components/ui/custom/tables/data-table-column-header.tsx +4 -3
- package/src/components/ui/custom/tables/data-table-faceted-filter.tsx +4 -3
- package/src/components/ui/custom/tables/data-table-pagination.tsx +6 -6
- package/src/components/ui/custom/tables/data-table-toolbar.tsx +5 -5
- package/src/components/ui/custom/tables/data-table-view-options.tsx +4 -3
- package/src/components/ui/custom/tables/data-table.tsx +69 -30
- package/src/components/ui/finance/invoices/columns.test.tsx +3 -3
- package/src/components/ui/finance/invoices/columns.tsx +4 -2
- package/src/components/ui/finance/invoices/pending-columns.tsx +1 -1
- package/src/components/ui/finance/invoices/row-actions.tsx +1 -1
- package/src/components/ui/finance/transactions/categories/columns.test.tsx +5 -5
- package/src/components/ui/finance/transactions/categories/columns.tsx +4 -2
- package/src/components/ui/finance/transactions/categories/row-actions.tsx +1 -1
- package/src/components/ui/finance/transactions/columns.test.tsx +5 -5
- package/src/components/ui/finance/transactions/columns.tsx +4 -2
- package/src/components/ui/finance/transactions/row-actions.tsx +1 -1
- package/src/components/ui/finance/wallets/columns.tsx +4 -2
- package/src/components/ui/finance/wallets/row-actions.tsx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,117 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.26.0](https://github.com/tutur3u/platform/compare/ui-v0.25.3...ui-v0.26.0) (2026-08-09)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **apps:** add resilient error recovery shells ([f0f514d](https://github.com/tutur3u/platform/commit/f0f514d2b1712ea76c6845801fa2803369418a63))
|
|
9
|
+
* **chat:** add connected site sync bridge ([05c27f5](https://github.com/tutur3u/platform/commit/05c27f5ac4a545af366097ee2a919ec755970b9b))
|
|
10
|
+
* **chat:** add connected site sync bridge ([#5078](https://github.com/tutur3u/platform/issues/5078)) ([3de1a07](https://github.com/tutur3u/platform/commit/3de1a07dfa2fec96982aa0d116e003d0aa3fe47c))
|
|
11
|
+
* **chat:** add external parity reconciliation ([953b9d6](https://github.com/tutur3u/platform/commit/953b9d6315611ef86ec2c213bbbbc5e62d5f4ad4))
|
|
12
|
+
* **chat:** add external parity reconciliation ([#5086](https://github.com/tutur3u/platform/issues/5086)) ([5ef796f](https://github.com/tutur3u/platform/commit/5ef796f7812ec6a9f9a62193ba21633cd2503001))
|
|
13
|
+
* **chat:** complete connected-site operational parity ([#5093](https://github.com/tutur3u/platform/issues/5093)) ([397e11b](https://github.com/tutur3u/platform/commit/397e11bd87d583fe1c65f83a2b8019c287650e19))
|
|
14
|
+
* **chat:** complete connected-site parity ([fd4061d](https://github.com/tutur3u/platform/commit/fd4061d8b2f654e521c40ea9819a348ae81575c9))
|
|
15
|
+
* **finance:** reconcile inventory provider sales ([56650e0](https://github.com/tutur3u/platform/commit/56650e066c473337700998d8af607380e8ea6202))
|
|
16
|
+
* **inventory:** allocate bundle revenue and add cash checkout ([bd465f9](https://github.com/tutur3u/platform/commit/bd465f9bb35fad16d259378fa49611a8eb5ba95b))
|
|
17
|
+
* **inventory:** secure Square POS event checkout ([532b463](https://github.com/tutur3u/platform/commit/532b46372116ee0bebfd83ba2af762cc9f668c3c))
|
|
18
|
+
* **landing:** retile the app bento and rebuild the problem section ([c56f42a](https://github.com/tutur3u/platform/commit/c56f42adf754362a269ff08b380db1ee0cf8c6ca))
|
|
19
|
+
* **meet:** revamp collaborative scheduling ([9124a5d](https://github.com/tutur3u/platform/commit/9124a5d559e071c7e6c1c713cfbca4d9f5205611))
|
|
20
|
+
* **navigation:** open workspace selector on reveal ([c760d42](https://github.com/tutur3u/platform/commit/c760d42b5e0b8f7bd141e26e2ace956b7f39912e))
|
|
21
|
+
* **offline:** own service worker runtime and refresh dependencies ([ae44477](https://github.com/tutur3u/platform/commit/ae44477603c39f0513244514771653287338a89f))
|
|
22
|
+
* **onboarding:** connect product guidance across apps ([68cf626](https://github.com/tutur3u/platform/commit/68cf626c9650e5044b6c123f9423a6cebf1bba9a))
|
|
23
|
+
* **reports:** add periodic reporting automation ([ec7bd5e](https://github.com/tutur3u/platform/commit/ec7bd5e10abb137e217d1dcf143624530276392f))
|
|
24
|
+
* **ui:** align landing and locale experience ([aa6e47f](https://github.com/tutur3u/platform/commit/aa6e47f17356ce74111ccf130e8b17071cc7aadf))
|
|
25
|
+
* **web:** improve workspace navigation context ([73959e3](https://github.com/tutur3u/platform/commit/73959e3c6745f60513d23740f30024c5d24e7ee4))
|
|
26
|
+
* **web:** redesign marketing landing page ([13ede78](https://github.com/tutur3u/platform/commit/13ede784ac03af9f25bbb7df61095826c335e002))
|
|
27
|
+
* **web:** refine marketing experience ([c7f1cec](https://github.com/tutur3u/platform/commit/c7f1cec0dd667e6d5f59aaf0bab82069b79c7376))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Bug Fixes
|
|
31
|
+
|
|
32
|
+
* **auth:** isolate invitation caches by user ([c720f15](https://github.com/tutur3u/platform/commit/c720f1551b1276bedf393bd4fc8975e33f64aa83))
|
|
33
|
+
* **auth:** resume a pending sign-in after switching accounts ([5403718](https://github.com/tutur3u/platform/commit/54037189ee895863eac621239eb54b8ea94c6ed0))
|
|
34
|
+
* **chat:** address external bridge review findings ([30533fc](https://github.com/tutur3u/platform/commit/30533fc64093fed4b25279bc0e45d43bdb7a6cde))
|
|
35
|
+
* **chat:** close bridge edge cases ([db585c8](https://github.com/tutur3u/platform/commit/db585c8486064837dcbce615a30f66c38c0a3ec0))
|
|
36
|
+
* **chat:** decode external visitor labels ([1ca3e75](https://github.com/tutur3u/platform/commit/1ca3e75a939c06222c842da9fe5892a1bf9ce729))
|
|
37
|
+
* **chat:** decode external visitor labels ([#5112](https://github.com/tutur3u/platform/issues/5112)) ([cb7af54](https://github.com/tutur3u/platform/commit/cb7af54264344961234da681c877c5677bf950fa))
|
|
38
|
+
* **chat:** harden bridge review edge cases ([0ecfad7](https://github.com/tutur3u/platform/commit/0ecfad7c66454fdb3bcd971f1362d3789287f086))
|
|
39
|
+
* **chat:** harden connected site migration ([73cccd0](https://github.com/tutur3u/platform/commit/73cccd04e9bd85a443891f3e52b443ef755570cb))
|
|
40
|
+
* **chat:** harden connected site sync ([70e8785](https://github.com/tutur3u/platform/commit/70e8785967e145a77330272e671ca58dfc38fbee))
|
|
41
|
+
* **chat:** harden connected-site parity ([a0d3148](https://github.com/tutur3u/platform/commit/a0d31483a87ed0ebd59f2be5c3bdbb342bd67855))
|
|
42
|
+
* **chat:** harden external bridge boundaries ([2d25202](https://github.com/tutur3u/platform/commit/2d252023e5cfb1c7414ee2ed2d161e7f5de613f8))
|
|
43
|
+
* **chat:** harden external bridge synchronization ([5b7c709](https://github.com/tutur3u/platform/commit/5b7c7092260b05c8de86c2383291433f81c9eead))
|
|
44
|
+
* **chat:** harden external bridge synchronization ([63ccf91](https://github.com/tutur3u/platform/commit/63ccf9129ff8e7cf255bef8c74a3e096ab45cb7d))
|
|
45
|
+
* **chat:** harden external parity replay ([896923b](https://github.com/tutur3u/platform/commit/896923b3c498a09fe0974de1aa0f8d291f8b2c84))
|
|
46
|
+
* **chat:** harden external parity replay ([89c5498](https://github.com/tutur3u/platform/commit/89c5498aef4ad5ebf05ef93bfb05168417a5dfe2))
|
|
47
|
+
* **chat:** mount query state adapter ([#5084](https://github.com/tutur3u/platform/issues/5084)) ([60253f8](https://github.com/tutur3u/platform/commit/60253f82477fc54923556706d12e4dc525a7a170))
|
|
48
|
+
* **chat:** preserve native message behavior ([fc09530](https://github.com/tutur3u/platform/commit/fc095301f2d26ca2bc39de02ce17b1f341327de9))
|
|
49
|
+
* **chat:** restore external inbox display parity ([#5108](https://github.com/tutur3u/platform/issues/5108)) ([b34ea6d](https://github.com/tutur3u/platform/commit/b34ea6d7d8ffda344053ed1f580128446d0d0a81))
|
|
50
|
+
* **ci:** keep task helper collection source-only ([7355a05](https://github.com/tutur3u/platform/commit/7355a05385ccd8d1e2d1bcf6ac15ae5297e057ad))
|
|
51
|
+
* **ci:** resolve shared task helper during e2e collection ([0319a3b](https://github.com/tutur3u/platform/commit/0319a3bd0df12592b823b7cebdff38cea3e50836))
|
|
52
|
+
* **deps:** align Streamdown with Shiki 4 ([0d89f55](https://github.com/tutur3u/platform/commit/0d89f555b3228307e893a5d7723aaf8db8505d63))
|
|
53
|
+
* **inventory:** correct storefront bundle checkout ([cb9c247](https://github.com/tutur3u/platform/commit/cb9c24740ceb77f9426ba640383a68bd9ed95d2c))
|
|
54
|
+
* **platform:** improve satellite workspace routing ([0c49c48](https://github.com/tutur3u/platform/commit/0c49c4882d26704fb16ba94ee289ab0af7deb4de))
|
|
55
|
+
* **platform:** merge notification and group visibility fixes ([4fe9e97](https://github.com/tutur3u/platform/commit/4fe9e970bf61bffaee4353b1ebb83ce2f880a4c8))
|
|
56
|
+
* **platform:** persist settings dialog state ([04d2128](https://github.com/tutur3u/platform/commit/04d212807b6fabf33f43743c0b779ba9499334ba))
|
|
57
|
+
* **platform:** restore notifications and group visibility ([eb570a4](https://github.com/tutur3u/platform/commit/eb570a47e7a3d38fc855fbf3e887ecbde853ece0))
|
|
58
|
+
* **platform:** stabilize offline and calendar initialization ([8ef8fbc](https://github.com/tutur3u/platform/commit/8ef8fbc1274e108930adf38bb8e998384551058b))
|
|
59
|
+
* resolve code quality findings ([63f10b5](https://github.com/tutur3u/platform/commit/63f10b5ec22a4194f48f448ee2b1b088b5da8f08))
|
|
60
|
+
* **security:** remediate code scanning findings ([023db2e](https://github.com/tutur3u/platform/commit/023db2edf4b0557be108a9d772cbc7e2223af947))
|
|
61
|
+
* **settings:** harden workspace role management ([0570687](https://github.com/tutur3u/platform/commit/0570687d255698e438d1eb02262ec0fa7c56240f))
|
|
62
|
+
* **settings:** repair satellite workspace management ([63614cd](https://github.com/tutur3u/platform/commit/63614cdd1550cbf7084724dbed728e798b6f979c))
|
|
63
|
+
* **tasks:** quiet the description toolbar and name the compact action honestly ([51838a0](https://github.com/tutur3u/platform/commit/51838a08a1875a44cd4c7f539b35203f890e1a39))
|
|
64
|
+
* **tasks:** repair board share access and harden AI media attachments ([65b8092](https://github.com/tutur3u/platform/commit/65b809245cc01fb7a5f034f703083f329c20f1c1))
|
|
65
|
+
* **tasks:** repair onboarding and external metadata ([e0b62eb](https://github.com/tutur3u/platform/commit/e0b62eb7119155f6e4cad3dc4fb4d0f9820c98e8))
|
|
66
|
+
* **tasks:** restore task description on deep-link opens ([56d4efa](https://github.com/tutur3u/platform/commit/56d4efabd2f036e7dae39588d8aed7b129fa9a11))
|
|
67
|
+
* **tasks:** show one skeleton per task dialog open ([944288b](https://github.com/tutur3u/platform/commit/944288bdc869ee80b3cdf537ee10feb17fdada22))
|
|
68
|
+
* **tasks:** stop the task dialog remounting after it opens ([9a423da](https://github.com/tutur3u/platform/commit/9a423daf7e7c1e901a63ddcc1a8a94ddff9192c9))
|
|
69
|
+
* **ui:** make member management mobile responsive ([104c812](https://github.com/tutur3u/platform/commit/104c8120949ef34bfdc48da74fdc0e2d35eb4ce7))
|
|
70
|
+
* **ui:** preserve hosted workspace logos ([1f8e8b2](https://github.com/tutur3u/platform/commit/1f8e8b28c23f659952ad58f00a2a371acd8fcef9))
|
|
71
|
+
* **ui:** publish portable vendored dependencies ([99ad359](https://github.com/tutur3u/platform/commit/99ad35903a27b8f9df26f22fc5f1003aed363d6f))
|
|
72
|
+
* **ui:** refine member access settings ([b5b1ebf](https://github.com/tutur3u/platform/commit/b5b1ebf49a4a3b93532da74357899927f6e5d988))
|
|
73
|
+
* **ui:** unify workspace access and sidebar controls ([450183e](https://github.com/tutur3u/platform/commit/450183ef78af8e09a386bebb93be7018379c5152))
|
|
74
|
+
* **web:** harden prerender client boundaries ([ab1596a](https://github.com/tutur3u/platform/commit/ab1596a769e5a6ae43b0247c651ecbecea52d20d))
|
|
75
|
+
* **workspaces:** harden invitation interactions ([b7f8f6c](https://github.com/tutur3u/platform/commit/b7f8f6cf52ceec1b67d36b11513eae7806284f5d))
|
|
76
|
+
* **workspaces:** propagate a workspace rename without a page reload ([cca0b2d](https://github.com/tutur3u/platform/commit/cca0b2d7e14f6d72f1fdda1deaac18aaf07e9eee))
|
|
77
|
+
* **workspaces:** repair shared member administration ([8a1515b](https://github.com/tutur3u/platform/commit/8a1515ba970adbe690b9d92f77a447adb93c339f))
|
|
78
|
+
* **workspaces:** restore invitation access across apps ([3592699](https://github.com/tutur3u/platform/commit/35926990fc28e44996fd330c6403496d78d96872))
|
|
79
|
+
* **workspaces:** restore invitation access across apps ([#5099](https://github.com/tutur3u/platform/issues/5099)) ([c7032c3](https://github.com/tutur3u/platform/commit/c7032c310639c2783b60ac560e83a84d65a5c7f5))
|
|
80
|
+
* **workspaces:** route satellite creation through setup ([754bf1b](https://github.com/tutur3u/platform/commit/754bf1b81360e4755a171819f3a8e6a7c102f351))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
### Performance Improvements
|
|
84
|
+
|
|
85
|
+
* **inventory:** add event-driven storefront caching ([2f571f4](https://github.com/tutur3u/platform/commit/2f571f4fc534bf5dee0ca36c4310d87b44e6e613))
|
|
86
|
+
* **storefront:** share customer navigation shell ([21cb78d](https://github.com/tutur3u/platform/commit/21cb78db484d1e1fdb9f2469fcac4e3a948bd176))
|
|
87
|
+
* **storefront:** stabilize navigation and cart rendering ([471c6b7](https://github.com/tutur3u/platform/commit/471c6b75abc6a4d69692e868cc1b220785e03262))
|
|
88
|
+
|
|
89
|
+
## [0.25.3](https://github.com/tutur3u/platform/compare/ui-v0.25.2...ui-v0.25.3) (2026-08-09)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
### Bug Fixes
|
|
93
|
+
|
|
94
|
+
* **chat:** decode external visitor labels ([1ca3e75](https://github.com/tutur3u/platform/commit/1ca3e75a939c06222c842da9fe5892a1bf9ce729))
|
|
95
|
+
* **chat:** decode external visitor labels ([#5112](https://github.com/tutur3u/platform/issues/5112)) ([cb7af54](https://github.com/tutur3u/platform/commit/cb7af54264344961234da681c877c5677bf950fa))
|
|
96
|
+
* **platform:** merge notification and group visibility fixes ([4fe9e97](https://github.com/tutur3u/platform/commit/4fe9e970bf61bffaee4353b1ebb83ce2f880a4c8))
|
|
97
|
+
* **platform:** restore notifications and group visibility ([eb570a4](https://github.com/tutur3u/platform/commit/eb570a47e7a3d38fc855fbf3e887ecbde853ece0))
|
|
98
|
+
|
|
99
|
+
## [0.25.2](https://github.com/tutur3u/platform/compare/ui-v0.25.1...ui-v0.25.2) (2026-08-07)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
### Bug Fixes
|
|
103
|
+
|
|
104
|
+
* **chat:** restore external inbox display parity ([#5108](https://github.com/tutur3u/platform/issues/5108)) ([b34ea6d](https://github.com/tutur3u/platform/commit/b34ea6d7d8ffda344053ed1f580128446d0d0a81))
|
|
105
|
+
* **tasks:** repair board share access and harden AI media attachments ([65b8092](https://github.com/tutur3u/platform/commit/65b809245cc01fb7a5f034f703083f329c20f1c1))
|
|
106
|
+
|
|
107
|
+
## [0.25.1](https://github.com/tutur3u/platform/compare/ui-v0.25.0...ui-v0.25.1) (2026-08-06)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
### Bug Fixes
|
|
111
|
+
|
|
112
|
+
* **workspaces:** harden invitation interactions ([b7f8f6c](https://github.com/tutur3u/platform/commit/b7f8f6cf52ceec1b67d36b11513eae7806284f5d))
|
|
113
|
+
* **workspaces:** restore invitation access across apps ([#5099](https://github.com/tutur3u/platform/issues/5099)) ([c7032c3](https://github.com/tutur3u/platform/commit/c7032c310639c2783b60ac560e83a84d65a5c7f5))
|
|
114
|
+
|
|
3
115
|
## [0.25.0](https://github.com/tutur3u/platform/compare/ui-v0.24.0...ui-v0.25.0) (2026-08-06)
|
|
4
116
|
|
|
5
117
|
|
package/biome.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuturuuu/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@tanstack/react-hotkeys": "^0.10.0",
|
|
59
59
|
"@tanstack/react-pacer": "^0.22.1",
|
|
60
60
|
"@tanstack/react-query": "^5.101.2",
|
|
61
|
-
"@tanstack/react-table": "^
|
|
61
|
+
"@tanstack/react-table": "^9.0.0",
|
|
62
62
|
"@tanstack/react-virtual": "^3.14.9",
|
|
63
63
|
"@tiptap/core": "3.29.2",
|
|
64
64
|
"@tiptap/extension-collaboration": "3.29.2",
|
|
@@ -86,13 +86,13 @@
|
|
|
86
86
|
"@tiptap/pm": "3.29.2",
|
|
87
87
|
"@tiptap/react": "3.29.2",
|
|
88
88
|
"@tiptap/starter-kit": "3.29.2",
|
|
89
|
-
"@tuturuuu/ai": "0.8.
|
|
89
|
+
"@tuturuuu/ai": "0.8.1",
|
|
90
90
|
"@tuturuuu/apis": "0.11.0",
|
|
91
91
|
"@tuturuuu/hooks": "0.1.0",
|
|
92
92
|
"@tuturuuu/icons": "0.1.0",
|
|
93
|
-
"@tuturuuu/internal-api": "0.
|
|
93
|
+
"@tuturuuu/internal-api": "0.29.0",
|
|
94
94
|
"@tuturuuu/supabase": "0.5.0",
|
|
95
|
-
"@tuturuuu/utils": "0.
|
|
95
|
+
"@tuturuuu/utils": "0.24.0",
|
|
96
96
|
"@types/debug": "^4.1.13",
|
|
97
97
|
"browser-image-compression": "^2.0.2",
|
|
98
98
|
"class-variance-authority": "^0.7.1",
|
|
@@ -104,18 +104,19 @@
|
|
|
104
104
|
"debug": "^4.4.3",
|
|
105
105
|
"embla-carousel-react": "^8.6.0",
|
|
106
106
|
"eventemitter3": "^5.0.4",
|
|
107
|
-
"framer-motion": "^
|
|
107
|
+
"framer-motion": "^13.0.0",
|
|
108
108
|
"gsap": "^3.15.0",
|
|
109
|
+
"html-entities": "^2.6.0",
|
|
109
110
|
"html2canvas-pro": "^2.3.3",
|
|
110
111
|
"input-otp": "^1.4.2",
|
|
111
112
|
"katex": "^0.18.1",
|
|
112
113
|
"lodash": "4.18.1",
|
|
113
114
|
"moment": "^2.30.1",
|
|
114
|
-
"motion": "^
|
|
115
|
+
"motion": "^13.0.0",
|
|
115
116
|
"next": "^16.3.0",
|
|
116
|
-
"next-intl": "^4.13.
|
|
117
|
+
"next-intl": "^4.13.5",
|
|
117
118
|
"next-themes": "^0.4.6",
|
|
118
|
-
"nuqs": "^2.9.
|
|
119
|
+
"nuqs": "^2.9.5",
|
|
119
120
|
"prosemirror-state": "^1.4.4",
|
|
120
121
|
"prosemirror-view": "1.42.2",
|
|
121
122
|
"qrcode.react": "^4.2.0",
|
|
@@ -142,14 +143,14 @@
|
|
|
142
143
|
"use-debounce": "^10.1.1",
|
|
143
144
|
"vaul": "^1.1.2",
|
|
144
145
|
"y-protocols": "^1.0.7",
|
|
145
|
-
"yjs": "^13.6.
|
|
146
|
+
"yjs": "^13.6.32",
|
|
146
147
|
"zod": "^4.4.3"
|
|
147
148
|
},
|
|
148
149
|
"devDependencies": {
|
|
149
150
|
"@tailwindcss/postcss": "^4.3.3",
|
|
150
151
|
"@tailwindcss/typography": "^0.5.20",
|
|
151
152
|
"@tanstack/react-query": "^5.101.4",
|
|
152
|
-
"@tanstack/react-table": "^
|
|
153
|
+
"@tanstack/react-table": "^9.0.0",
|
|
153
154
|
"@testing-library/jest-dom": "^7.0.0",
|
|
154
155
|
"@testing-library/react": "^16.3.2",
|
|
155
156
|
"@tuturuuu/types": "0.27.0",
|
|
@@ -174,7 +175,7 @@
|
|
|
174
175
|
},
|
|
175
176
|
"peerDependencies": {
|
|
176
177
|
"@tanstack/react-query": "^5.101.2",
|
|
177
|
-
"@tanstack/react-table": "^
|
|
178
|
+
"@tanstack/react-table": "^9.0.0"
|
|
178
179
|
},
|
|
179
180
|
"exports": {
|
|
180
181
|
"./globals.css": "./src/globals.css",
|
|
@@ -6,6 +6,7 @@ import { cn } from '@tuturuuu/utils/format';
|
|
|
6
6
|
import { useLocale, useTranslations } from 'next-intl';
|
|
7
7
|
import { Button } from '../button';
|
|
8
8
|
import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip';
|
|
9
|
+
import { getChatMessageDisplayContent } from './external-message-content';
|
|
9
10
|
import {
|
|
10
11
|
getChatConversationQueueDetails,
|
|
11
12
|
getChatMessageSenderLabel,
|
|
@@ -37,6 +38,7 @@ export function ConversationRow({
|
|
|
37
38
|
channel: t('untitled_channel'),
|
|
38
39
|
chat: t('untitled_chat'),
|
|
39
40
|
direct: t('direct_message'),
|
|
41
|
+
external: t('external_sender'),
|
|
40
42
|
group: t('group_chat'),
|
|
41
43
|
});
|
|
42
44
|
const pinned = isChatConversationPinned(conversation, currentUserId);
|
|
@@ -160,25 +162,29 @@ export function SearchResultList({
|
|
|
160
162
|
|
|
161
163
|
return (
|
|
162
164
|
<div className="h-full space-y-1 overflow-y-auto p-2">
|
|
163
|
-
{messages.map((message) =>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
165
|
+
{messages.map((message) => {
|
|
166
|
+
const displayContent = getChatMessageDisplayContent(message);
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<button
|
|
170
|
+
className="w-full rounded-md px-2 py-2 text-left transition-colors hover:bg-accent"
|
|
171
|
+
key={message.id}
|
|
172
|
+
onClick={() => onSelectConversation(message.conversationId)}
|
|
173
|
+
type="button"
|
|
174
|
+
>
|
|
175
|
+
<span className="block truncate font-medium text-sm">
|
|
176
|
+
{getChatMessageSenderLabel(message, {
|
|
177
|
+
assistant: t('assistant_name'),
|
|
178
|
+
external: t('external_sender'),
|
|
179
|
+
unknown: t('unknown_sender'),
|
|
180
|
+
})}
|
|
181
|
+
</span>
|
|
182
|
+
<span className="mt-1 line-clamp-2 text-muted-foreground text-xs">
|
|
183
|
+
{displayContent}
|
|
184
|
+
</span>
|
|
185
|
+
</button>
|
|
186
|
+
);
|
|
187
|
+
})}
|
|
182
188
|
</div>
|
|
183
189
|
);
|
|
184
190
|
}
|
|
@@ -134,6 +134,62 @@ describe('chat utils', () => {
|
|
|
134
134
|
expect(title).toBe('Ada Lovelace');
|
|
135
135
|
});
|
|
136
136
|
|
|
137
|
+
it('gives unnamed external conversations a stable pseudonymous title', () => {
|
|
138
|
+
const title = getConversationTitle(
|
|
139
|
+
conversation({
|
|
140
|
+
id: 'a7d12840-f72d-48f2-8057-a1ea15aecd29',
|
|
141
|
+
metadata: { externalChat: true },
|
|
142
|
+
title: 'External visitor',
|
|
143
|
+
type: 'channel',
|
|
144
|
+
}),
|
|
145
|
+
'user-1',
|
|
146
|
+
{ external: 'Website visitor' }
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(title).toBe('Website visitor #AECD29');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('prefers an external profile name over a persisted generic title', () => {
|
|
153
|
+
const title = getConversationTitle(
|
|
154
|
+
conversation({
|
|
155
|
+
metadata: { displayName: 'Visitor 42', externalChat: true },
|
|
156
|
+
title: 'External visitor',
|
|
157
|
+
type: 'channel',
|
|
158
|
+
}),
|
|
159
|
+
'user-1',
|
|
160
|
+
{ external: 'Website visitor' }
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(title).toBe('Visitor 42');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('decodes external profile and persisted conversation titles', () => {
|
|
167
|
+
expect(
|
|
168
|
+
getConversationTitle(
|
|
169
|
+
conversation({
|
|
170
|
+
metadata: {
|
|
171
|
+
displayName: 'Nguyễn Thị Vi&ecirc;n',
|
|
172
|
+
externalChat: true,
|
|
173
|
+
},
|
|
174
|
+
title: 'External visitor',
|
|
175
|
+
type: 'channel',
|
|
176
|
+
}),
|
|
177
|
+
'user-1'
|
|
178
|
+
)
|
|
179
|
+
).toBe('Nguyễn Thị Viên');
|
|
180
|
+
|
|
181
|
+
expect(
|
|
182
|
+
getConversationTitle(
|
|
183
|
+
conversation({
|
|
184
|
+
metadata: { externalChat: true },
|
|
185
|
+
title: 'Nguyễn Thị Viên',
|
|
186
|
+
type: 'channel',
|
|
187
|
+
}),
|
|
188
|
+
'user-1'
|
|
189
|
+
)
|
|
190
|
+
).toBe('Nguyễn Thị Viên');
|
|
191
|
+
});
|
|
192
|
+
|
|
137
193
|
it('builds useful message previews for files and deleted messages', () => {
|
|
138
194
|
expect(
|
|
139
195
|
getLastMessagePreview(
|
|
@@ -171,6 +227,21 @@ describe('chat utils', () => {
|
|
|
171
227
|
).toBe('message_deleted');
|
|
172
228
|
});
|
|
173
229
|
|
|
230
|
+
it('decodes external message previews without changing native previews', () => {
|
|
231
|
+
const encoded = 'Tôi đã gửi tin';
|
|
232
|
+
|
|
233
|
+
expect(
|
|
234
|
+
getLastMessagePreview({
|
|
235
|
+
...baseMessage,
|
|
236
|
+
content: encoded,
|
|
237
|
+
metadata: { externalChat: true },
|
|
238
|
+
})
|
|
239
|
+
).toBe('Tôi đã gửi tin');
|
|
240
|
+
expect(getLastMessagePreview({ ...baseMessage, content: encoded })).toBe(
|
|
241
|
+
encoded
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
|
|
174
245
|
it('formats file sizes for attachment rows', () => {
|
|
175
246
|
expect(formatFileSize(0)).toBe('0 B');
|
|
176
247
|
expect(formatFileSize(2048)).toBe('2.0 KB');
|
|
@@ -344,12 +415,12 @@ describe('chat utils', () => {
|
|
|
344
415
|
...baseMessage,
|
|
345
416
|
metadata: {
|
|
346
417
|
externalChat: true,
|
|
347
|
-
externalSender: { displayName: 'Visitor 42' },
|
|
418
|
+
externalSender: { displayName: 'Visitor &amp; 42' },
|
|
348
419
|
},
|
|
349
420
|
},
|
|
350
421
|
fallback
|
|
351
422
|
)
|
|
352
|
-
).toBe('Visitor 42');
|
|
423
|
+
).toBe('Visitor & 42');
|
|
353
424
|
expect(
|
|
354
425
|
getChatMessageSenderLabel(
|
|
355
426
|
{ ...baseMessage, metadata: { externalChat: true } },
|
|
@@ -382,6 +453,21 @@ describe('chat utils', () => {
|
|
|
382
453
|
});
|
|
383
454
|
});
|
|
384
455
|
|
|
456
|
+
it('decodes connected-site queue previews using conversation scope', () => {
|
|
457
|
+
const details = getChatConversationQueueDetails(
|
|
458
|
+
conversation({
|
|
459
|
+
latestMessage: {
|
|
460
|
+
...baseMessage,
|
|
461
|
+
content: 'Tôi đã gửi tin',
|
|
462
|
+
metadata: { status: 'seen' },
|
|
463
|
+
},
|
|
464
|
+
metadata: { externalChat: true },
|
|
465
|
+
})
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
expect(details.preview).toBe('Tôi đã gửi tin');
|
|
469
|
+
});
|
|
470
|
+
|
|
385
471
|
it('normalizes connected-site delivery and deletion states', () => {
|
|
386
472
|
expect(
|
|
387
473
|
getChatConversationDeliveryState({
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { FileText, Film, Music, X } from '@tuturuuu/icons';
|
|
4
|
+
import type { ChatAttachmentDraft } from '@tuturuuu/internal-api';
|
|
5
|
+
import { cn } from '@tuturuuu/utils/format';
|
|
6
|
+
import { Button } from '../button';
|
|
7
|
+
import { formatFileSize } from './utils';
|
|
8
|
+
|
|
9
|
+
export type ComposerAttachmentKind = 'audio' | 'file' | 'image' | 'video';
|
|
10
|
+
|
|
11
|
+
export function getComposerAttachmentKind(
|
|
12
|
+
filename: string,
|
|
13
|
+
contentType?: string | null
|
|
14
|
+
): ComposerAttachmentKind {
|
|
15
|
+
const type = contentType?.toLowerCase() ?? '';
|
|
16
|
+
const extension = filename.split('.').pop()?.toLowerCase() ?? '';
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
type.startsWith('image/') ||
|
|
20
|
+
['avif', 'gif', 'heic', 'jpeg', 'jpg', 'png', 'webp'].includes(extension)
|
|
21
|
+
) {
|
|
22
|
+
return 'image';
|
|
23
|
+
}
|
|
24
|
+
if (
|
|
25
|
+
type.startsWith('video/') ||
|
|
26
|
+
['m4v', 'mov', 'mp4', 'webm'].includes(extension)
|
|
27
|
+
) {
|
|
28
|
+
return 'video';
|
|
29
|
+
}
|
|
30
|
+
if (
|
|
31
|
+
type.startsWith('audio/') ||
|
|
32
|
+
['aac', 'flac', 'm4a', 'mp3', 'ogg', 'opus', 'wav'].includes(extension)
|
|
33
|
+
) {
|
|
34
|
+
return 'audio';
|
|
35
|
+
}
|
|
36
|
+
return 'file';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const kindIcon = {
|
|
40
|
+
audio: Music,
|
|
41
|
+
file: FileText,
|
|
42
|
+
image: FileText,
|
|
43
|
+
video: Film,
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A queued attachment, shown before the message is sent. Media gets a real
|
|
48
|
+
* thumbnail from the local file rather than a filename chip — a picture is the
|
|
49
|
+
* only reliable way to confirm you attached the right screenshot.
|
|
50
|
+
*/
|
|
51
|
+
export function ComposerAttachmentChip({
|
|
52
|
+
attachment,
|
|
53
|
+
onRemove,
|
|
54
|
+
previewUrl,
|
|
55
|
+
removeLabel,
|
|
56
|
+
}: {
|
|
57
|
+
attachment: ChatAttachmentDraft;
|
|
58
|
+
onRemove: () => void;
|
|
59
|
+
previewUrl?: string;
|
|
60
|
+
removeLabel: string;
|
|
61
|
+
}) {
|
|
62
|
+
const kind = getComposerAttachmentKind(
|
|
63
|
+
attachment.filename,
|
|
64
|
+
attachment.contentType
|
|
65
|
+
);
|
|
66
|
+
const showsThumbnail = Boolean(previewUrl) && kind !== 'file';
|
|
67
|
+
const Icon = kindIcon[kind];
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
className={cn(
|
|
72
|
+
'group relative flex min-w-0 items-center gap-2 overflow-hidden rounded-md border bg-muted/40 text-sm',
|
|
73
|
+
showsThumbnail ? 'p-1 pr-1' : 'px-2 py-1'
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
76
|
+
{showsThumbnail ? (
|
|
77
|
+
<span className="size-10 shrink-0 overflow-hidden rounded bg-background">
|
|
78
|
+
{kind === 'video' ? (
|
|
79
|
+
<video
|
|
80
|
+
className="size-full object-cover"
|
|
81
|
+
muted
|
|
82
|
+
preload="metadata"
|
|
83
|
+
src={previewUrl}
|
|
84
|
+
/>
|
|
85
|
+
) : kind === 'audio' ? (
|
|
86
|
+
<span className="flex size-full items-center justify-center">
|
|
87
|
+
<Music className="size-4 text-muted-foreground" />
|
|
88
|
+
</span>
|
|
89
|
+
) : (
|
|
90
|
+
/* biome-ignore lint/performance/noImgElement: local blob: URL for a not-yet-sent file; next/image cannot optimize object URLs */
|
|
91
|
+
<img
|
|
92
|
+
alt={attachment.filename}
|
|
93
|
+
className="size-full object-cover"
|
|
94
|
+
src={previewUrl}
|
|
95
|
+
/>
|
|
96
|
+
)}
|
|
97
|
+
</span>
|
|
98
|
+
) : (
|
|
99
|
+
<Icon className="size-4 shrink-0 text-muted-foreground" />
|
|
100
|
+
)}
|
|
101
|
+
|
|
102
|
+
<span className="min-w-0 flex-1">
|
|
103
|
+
<span
|
|
104
|
+
className="block max-w-40 truncate font-medium"
|
|
105
|
+
title={attachment.filename}
|
|
106
|
+
>
|
|
107
|
+
{attachment.filename}
|
|
108
|
+
</span>
|
|
109
|
+
{attachment.sizeBytes ? (
|
|
110
|
+
<span className="block text-muted-foreground text-xs">
|
|
111
|
+
{formatFileSize(attachment.sizeBytes)}
|
|
112
|
+
</span>
|
|
113
|
+
) : null}
|
|
114
|
+
</span>
|
|
115
|
+
|
|
116
|
+
<Button
|
|
117
|
+
aria-label={removeLabel}
|
|
118
|
+
className="size-6 shrink-0"
|
|
119
|
+
onClick={onRemove}
|
|
120
|
+
size="icon"
|
|
121
|
+
type="button"
|
|
122
|
+
variant="ghost"
|
|
123
|
+
>
|
|
124
|
+
<X className="size-3.5" />
|
|
125
|
+
</Button>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { getChatMessageDisplayContent } from './external-message-content';
|
|
3
|
+
|
|
4
|
+
describe('getChatMessageDisplayContent', () => {
|
|
5
|
+
it('decodes named and numeric entities for external chat messages', () => {
|
|
6
|
+
expect(
|
|
7
|
+
getChatMessageDisplayContent({
|
|
8
|
+
content:
|
|
9
|
+
'Tôi đã gửi tin & chờ phản hồi.',
|
|
10
|
+
metadata: { externalChat: true },
|
|
11
|
+
})
|
|
12
|
+
).toBe('Tôi đã gửi tin & chờ phản hồi.');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('decodes bounded nested entities from double-encoded legacy rows', () => {
|
|
16
|
+
expect(
|
|
17
|
+
getChatMessageDisplayContent({
|
|
18
|
+
content: 'Khám trực tiếp &amp; mang theo kết quả',
|
|
19
|
+
metadata: { externalChat: true },
|
|
20
|
+
})
|
|
21
|
+
).toBe('Khám trực tiếp & mang theo kết quả');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('leaves native chat content unchanged', () => {
|
|
25
|
+
expect(
|
|
26
|
+
getChatMessageDisplayContent({
|
|
27
|
+
content: 'Keep & exactly as authored',
|
|
28
|
+
metadata: {},
|
|
29
|
+
})
|
|
30
|
+
).toBe('Keep & exactly as authored');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('returns decoded markup as text content rather than an HTML contract', () => {
|
|
34
|
+
expect(
|
|
35
|
+
getChatMessageDisplayContent({
|
|
36
|
+
content: '<img src=x onerror=alert(1)>',
|
|
37
|
+
metadata: { externalChat: true },
|
|
38
|
+
})
|
|
39
|
+
).toBe('<img src=x onerror=alert(1)>');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ChatMessage } from '@tuturuuu/internal-api';
|
|
2
|
+
import { decode } from 'html-entities';
|
|
3
|
+
|
|
4
|
+
const MAX_ENTITY_DECODE_PASSES = 3;
|
|
5
|
+
|
|
6
|
+
export function decodeExternalChatContent(
|
|
7
|
+
content: string,
|
|
8
|
+
externalChat: boolean
|
|
9
|
+
) {
|
|
10
|
+
if (!externalChat) return content;
|
|
11
|
+
|
|
12
|
+
let displayContent = content;
|
|
13
|
+
for (let pass = 0; pass < MAX_ENTITY_DECODE_PASSES; pass += 1) {
|
|
14
|
+
const decoded = decode(displayContent, { level: 'html5', scope: 'body' });
|
|
15
|
+
if (decoded === displayContent) break;
|
|
16
|
+
displayContent = decoded;
|
|
17
|
+
}
|
|
18
|
+
return displayContent;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getChatMessageDisplayContent(
|
|
22
|
+
message: Pick<ChatMessage, 'content' | 'metadata'>
|
|
23
|
+
) {
|
|
24
|
+
return decodeExternalChatContent(
|
|
25
|
+
message.content,
|
|
26
|
+
message.metadata.externalChat === true
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -26,6 +26,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '../popover';
|
|
|
26
26
|
import { toast } from '../sonner';
|
|
27
27
|
import { AiMessageParts } from './ai-message-parts';
|
|
28
28
|
import { getAiMessagePartsFromMetadata } from './ai-message-render-utils';
|
|
29
|
+
import { getChatMessageDisplayContent } from './external-message-content';
|
|
29
30
|
import { MessageAttachmentButton } from './message-attachment-button';
|
|
30
31
|
import { MessageLinkPreviews, MessageText } from './message-links';
|
|
31
32
|
import {
|
|
@@ -259,6 +260,7 @@ function MessageContent({
|
|
|
259
260
|
const t = useTranslations('chat');
|
|
260
261
|
const aiParts = getAiMessagePartsFromMetadata(message.metadata);
|
|
261
262
|
const externalAttachment = readExternalAttachment(message.metadata);
|
|
263
|
+
const displayContent = getChatMessageDisplayContent(message);
|
|
262
264
|
|
|
263
265
|
return (
|
|
264
266
|
<div
|
|
@@ -284,10 +286,10 @@ function MessageContent({
|
|
|
284
286
|
textFallback={message.content}
|
|
285
287
|
/>
|
|
286
288
|
) : (
|
|
287
|
-
<MessageText content={
|
|
289
|
+
<MessageText content={displayContent} />
|
|
288
290
|
)}
|
|
289
291
|
<MessageLinkPreviews
|
|
290
|
-
content={
|
|
292
|
+
content={displayContent}
|
|
291
293
|
conversationId={message.conversationId}
|
|
292
294
|
isOwnMessage={isOwnMessage}
|
|
293
295
|
wsId={wsId}
|