yyqdbngt opened a new pull request, #4687:
URL: https://github.com/apache/rocketmq-dashboard/pull/4687

   <!-- Base branch: `rocketmq-studio`, the RocketMQ Studio trunk. -->
   
   ### Brief Description
   
   `AiConversationService.deriveTitle` and `AiConversationService.capTitle` 
capped the conversation title
   by UTF-16 `char`:
   
   ```java
   return folded.substring(0, TITLE_MAX_CHARS);                                 
   // 40
   return title.length() <= TITLE_COLUMN_MAX_CHARS ? title : title.substring(0, 
TITLE_COLUMN_MAX_CHARS);
   ```
   
   A supplementary character — an emoji, a CJK extension character — is two 
chars, so a cut can land
   between its high and its low surrogate and keep half of one. An unpaired 
surrogate is not a code
   point and has no UTF-8 encoding, so the value that reaches the database and 
the client holds a
   replacement character instead of the character it was cut out of. The title 
is both
   `rmq_ai_conversation.title` and the label of the conversation list, i.e. the 
first thing the user
   sees about a conversation, and the *first* message that produces it is 
exactly the free-text field a
   user is most likely to put an emoji or a Chinese/CJK character in.
   
   Concretely, `deriveTitle("a" * 39 + "🚀" + "tail")` used to return 39 `a`s 
plus the lone high
   surrogate `U+D83D`, and `capTitle(511 × "b" + "🚀" + "tail")` did the same 
against the 512-char column.
   
   Both caps now count code points and cut with `offsetByCodePoints`, which is 
the rule
   `MessagePropertyDisplay` already applies to message property values (its 
constant is
   `MAX_PROPERTY_VALUE_CODE_POINTS` for that reason). The two constants keep 
their names but their caps
   are code-point budgets, so a 40-emoji message now keeps 40 emoji instead of 
20 — the ceiling the
   javadoc always described.
   
   ### How Did You Test This Change?
   
   Extended 
`AiConversationServiceTest#deriveTitleShouldFoldWhitespaceAndCapTheLengthTest` 
(the test that
   already pins the 40/512 caps) with the boundary cases: 39 ASCII chars + an 
astral character + trailing
   text, the same for the 512-char rename cap, and a title made only of astral 
characters.
   
   Before the fix (red) — the cut kept the high surrogate of the emoji and 
dropped its low surrogate, so
   the value no longer equals the string it was cut out of:
   
   ```
   $ cd server && mvn -B -ntp test 
-Dtest='AiConversationServiceTest#deriveTitleShouldFoldWhitespaceAndCapTheLengthTest'
   [ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 
2.756 s <<< FAILURE! -- in 
org.apache.rocketmq.studio.ops.ai.conversation.AiConversationServiceTest
   org.opentest4j.AssertionFailedError:
   
   expected: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa🚀"
    but was: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
   ```
   
   The `but was:` line is the 39 `a`s followed by the unpaired high surrogate 
`U+D83D`; JUnit stops
   printing there because AssertJ renders that lone surrogate as a replacement 
character. The full text
   from `target/surefire-reports/...AiConversationServiceTest.txt` is the 
assertion above.
   
   After the fix (green) — the focused class plus the neighbouring conversation 
classes:
   
   ```
   $ cd server && mvn -B -ntp test 
-Dtest='AiConversationServiceTest,AiConversationControllerTest,AiConversationPersistenceIntegrationTest,AiConversationVoAssemblerTest,AiRunExecutorTest,AiRunServiceTest'
   [INFO] You have 0 Checkstyle violations.
   [INFO] Tests run: 18, Failures: 0, Errors: 0, Skipped: 0 -- in 
org.apache.rocketmq.studio.ops.ai.conversation.AiConversationServiceTest
   [INFO] Tests run: 108, Failures: 0, Errors: 0, Skipped: 0
   [INFO] BUILD SUCCESS
   ```
   
   The pre-existing cap assertions (`deriveTitle("x".repeat(120)).hasSize(40)`,
   `capTitle("y".repeat(600)).hasSize(512)`) are unchanged and still pass, so 
an ASCII title behaves
   exactly as before.
   
   Note on the full suite: on a clean `rocketmq-studio` checkout `mvn -B -ntp 
test` already reports
   `Tests run: 3051, Failures: 6, Errors: 25, Skipped: 4`. The 11 red classes 
are the MySQL 8 backed
   Spring integration tests (`AuthServiceBootstrapIntegrationTest`,
   `AuthServiceConcurrencyIntegrationTest`, 
`AuthServiceSessionOverviewIntegrationTest`,
   `HealthProbeIntegrationTest`, `QueryHistoryServiceIntegrationTest`,
   `NativeAlertEvaluationTransactionTest`, 
`NotificationOutboxMapperIntegrationTest`,
   `RmqAlertStateMapperIntegrationTest`, `StudioApplicationTest`) plus the 
external-CLI ones
   (`CliAgentProviderTest`, `ClaudeCodeAgentProviderTest`). None of them are 
touched by this change.
   
   ### Checklist
   
   - [x] One coherent change; unrelated modifications are not bundled in
   - [x] Commit subject follows Conventional Commits (`fix(ai): …`)
   - [x] Tests added or updated for non-trivial changes, test methods named 
`...Test`
   - [x] New UI text has both Chinese and English entries under `web/src/i18n/` 
(no UI text in this change)
   - [x] Architecture constraints stay green (`mvn test` runs the ArchUnit 
checks)
   - [x] New source files carry the ASF license header (no new files, no 
non-ASCII source: the astral character is written as a `\uD83D\uDE80` escape, 
as the checkstyle config requires)
   - [x] Documentation touched where behaviour changed (the two caps are 
documented as code-point budgets)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to