Repository navigation
Commit 76c560e
fix(plugin-chatbot): render an answer's text and charts in stream order (objectui#13492) (#13500)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D1ujqNbMkVbXgPHgdDMe18r)_
Fixes #13492
Clause-②: no
Metadata-protocol: none
## What changed
An assistant answer now renders its text and its charts in the order the
stream produced them. A chart the model drew before writing its analysis
shows first, so an open-ended analysis in the console's assistant panel
puts its chart on the first screen (the acceptance
objectstack-ai/cloud#3072 sets). Before, `mapMessages` joined every text
part into one `content` and lifted every `data-chart` part into one
trailing `charts[]`, and `ChatbotEnhanced` rendered the whole text
first, so the chart always landed below every paragraph.
- **`mapMessages.ts`:** `extractCharts` now records, on each chart,
where it arrived among the text parts: `textOffset` = the length of the
text that `extractText` joined before it. Tool, step and reasoning parts
add nothing. Only when `content` IS that join; a legacy string `content`
is not built from the parts, so its charts carry no offset.
- **`ChatbotEnhanced.tsx`:** a new optional `ChatChart.textOffset`.
`layoutAnswer` splits `content` at the offsets into text runs and chart
runs; each text run renders through its own `MessageResponse` (so
long-answer folding and record links work in every run), and each chart
run through the same chart markup as before, now in `AnswerCharts`. The
streaming caret rides the last text run, where it sat before. Charts
render in the same tree position whether or not prose has arrived, so a
chart drawn before the first word keeps its mounted node when the text
streams in.
- **Backward-compatible:** `content` and `charts` keep their shape. A
chart with no `textOffset` (a host-built message, the legacy
string-content shape) or one past the end of the text trails all of it,
which is exactly the old layout; a message with no charts renders the
same DOM as before. `textOffset` is a placement key and never reaches
the chart schema handed to `SchemaRenderer`.
## The card's acceptance, item by item
| Acceptance | Pinned by |
|---|---|
| parts `data-chart`, `text`: the chart renders before the text |
`data-chart then text: the chart renders before the analysis` |
| parts `text`, `data-chart`, `text`: the three in that order | `text,
data-chart, text: the three render in that order` |
| a re-hydrated stored message keeps the order |
`useObjectChat.chartOrderRehydrate-13492.test.tsx` (API mode, stored
message with `parts` through the hook and into the bubble) and
`re-mapping a restored message (same parts) yields the same placement` |
| long-answer folding (objectui#13032) does not regress |
`objectui#13032 does not regress: the long analysis after a chart still
folds once the stream ends`, plus the unchanged
`chatAnswerLayout-13032.test.tsx` |
| record links (objectui#13037) do not regress | `objectui#13037 does
not regress: a record link in the text after a chart opens through the
host opener`, plus the unchanged `chatRecordLink-13037.test.tsx` |
| staging re-test with 「帮我分析一下客户」 | **NOT MEASURED**: staging reaches
this through cloud's objectui pin; re-run after the pin moves past this
merge |
New files: `mapMessages.chartOrder-13492.test.ts` (6 tests),
`ChatbotEnhanced.chartOrder-13492.test.tsx` (7, one a CONTROL for the
old layout), `useObjectChat.chartOrderRehydrate-13492.test.tsx` (2).
`mapMessages.test.ts` gains the `textOffset` in the one test that pins a
chart's exact shape.
## Verification (local, head `cb0af96`)
All runs from the repo root under the shared verify lock.
- `pnpm exec vitest run --maxWorkers=2 packages/plugin-chatbot/`: **Test
Files 82 passed (82), Tests 787 passed (787)**.
- The 54 test files in `packages/app-shell/src` and `apps/console/src`
that import `@object-ui/plugin-chatbot` (resolved to its `src` by the
root config's alias): **Test Files 54 passed (54), Tests 582 passed
(582)**.
- `pnpm turbo run type-check --filter=@object-ui/plugin-chatbot` (builds
the dependency closure, then `tsc --noEmit && tsc -p
tsconfig.test.json`): exit 0 after one fix to the new re-hydrate test
(it now hands the hook's messages through `toRuntimeMessages`, as hosts
do).
- `eslint --no-inline-config` on the six touched source and test files:
0 errors; the 11 warnings are all on lines this PR does not touch.
- `node scripts/check-changeset-presence.mjs` and `node
scripts/check-changeset-no-major.mjs`: pass. `pnpm
check:new-line-citations`: 0 new citations. `check:vi-mock-specifiers`,
`check:vi-mock-inherit`, `check:vi-mock-override-shape`,
`check:test-path-roots`, `check:control-bytes`, `check:ui-root-lookups`,
`check:unreferenced-sources`: all exit 0.
- **Reverse verification**, both through `ablation-replace.mjs` with the
fix committed and restore proven (blob equals HEAD, `git diff HEAD`
empty):
- renderer: `layoutAnswer` ignores `textOffset` (every chart trails the
text) ⇒ 6 of the 9 render and re-hydrate tests turn red; the record-link
test, the old-layout CONTROL and the mapper tests stay green, as they
should.
- mapper: the `textOffset` spread deleted ⇒ 13 tests red across the four
files, including the existing exact-shape test; the legacy no-offset
test stays green.
- NOT MEASURED locally, left to CI: the sharded full `pnpm test`,
repo-wide `pnpm lint`, builds and the remaining `check:*` gates.
## Acceptance notes
- **No reload path in this repository restores a chart today, so on
reload the chart is missing, not misplaced.** The server keeps a
conversation as Vercel `ModelMessage`s
(`IAIConversationService.addMessage` in `@objectstack/spec`'s
`contracts/ai-service.ts`), which have no data parts. On the way back,
app-shell's `hydratedMessagesToChatMessages` builds `content` and tool
invocations only, and `sanitizeChatMessagesForCache` writes text and
tool parts only. Read from code, not measured on staging; outside this
card's file surface (`packages/plugin-chatbot/src/**`). A stored message
that does carry its `data-chart` parts keeps its order (pinned above).
Whether cloud's `visualize_data` result carries enough to re-derive the
chart on reload is a cloud-side reading this session cannot reach.
- In an answer that interleaves text and charts, each text run folds on
its own (a run taller than two viewport heights folds; a short intro
above the chart does not). An answer with no chart, or with its charts
after all the text, is one run and folds exactly as before.
- Docs: neither `packages/plugin-chatbot/README.md` nor
`content/docs/plugins/plugin-chatbot.mdx` describes where charts render,
so nothing there went stale; `textOffset` is documented on the
`ChatChart` type.
- [x] Adds no product-specific widget, dialog, hook or string (see
AGENTS.md › Product-neutral renderer)
Card thread session: `session_01VWbYRWtBm3f8J2T8HAt97o`, claimed by the
`domain:ui` seat 4 (claim comment 6393183623).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01VWbYRWtBm3f8J2T8HAt97o
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VWbYRWtBm3f8J2T8HAt97o)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent ebd20ec commit 76c560e
7 files changed
Lines changed: 548 additions & 55 deletions
File tree
- .changeset
- packages/plugin-chatbot/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
157 | | - | |
158 | | - | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
159 | 160 | | |
160 | 161 | | |
161 | 162 | | |
| |||
176 | 177 | | |
177 | 178 | | |
178 | 179 | | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
179 | 190 | | |
180 | 191 | | |
181 | 192 | | |
| |||
1711 | 1722 | | |
1712 | 1723 | | |
1713 | 1724 | | |
| 1725 | + | |
| 1726 | + | |
| 1727 | + | |
| 1728 | + | |
| 1729 | + | |
| 1730 | + | |
| 1731 | + | |
| 1732 | + | |
| 1733 | + | |
| 1734 | + | |
| 1735 | + | |
| 1736 | + | |
| 1737 | + | |
| 1738 | + | |
| 1739 | + | |
| 1740 | + | |
| 1741 | + | |
| 1742 | + | |
| 1743 | + | |
| 1744 | + | |
| 1745 | + | |
| 1746 | + | |
| 1747 | + | |
| 1748 | + | |
| 1749 | + | |
| 1750 | + | |
| 1751 | + | |
| 1752 | + | |
| 1753 | + | |
| 1754 | + | |
| 1755 | + | |
| 1756 | + | |
| 1757 | + | |
| 1758 | + | |
| 1759 | + | |
| 1760 | + | |
| 1761 | + | |
| 1762 | + | |
| 1763 | + | |
| 1764 | + | |
| 1765 | + | |
| 1766 | + | |
| 1767 | + | |
| 1768 | + | |
| 1769 | + | |
| 1770 | + | |
| 1771 | + | |
| 1772 | + | |
| 1773 | + | |
| 1774 | + | |
| 1775 | + | |
| 1776 | + | |
| 1777 | + | |
| 1778 | + | |
| 1779 | + | |
| 1780 | + | |
| 1781 | + | |
| 1782 | + | |
| 1783 | + | |
| 1784 | + | |
| 1785 | + | |
| 1786 | + | |
| 1787 | + | |
| 1788 | + | |
| 1789 | + | |
| 1790 | + | |
| 1791 | + | |
| 1792 | + | |
| 1793 | + | |
| 1794 | + | |
| 1795 | + | |
| 1796 | + | |
| 1797 | + | |
| 1798 | + | |
| 1799 | + | |
| 1800 | + | |
| 1801 | + | |
| 1802 | + | |
| 1803 | + | |
| 1804 | + | |
| 1805 | + | |
| 1806 | + | |
| 1807 | + | |
1714 | 1808 | | |
1715 | 1809 | | |
1716 | 1810 | | |
| |||
4078 | 4172 | | |
4079 | 4173 | | |
4080 | 4174 | | |
| 4175 | + | |
| 4176 | + | |
| 4177 | + | |
| 4178 | + | |
| 4179 | + | |
| 4180 | + | |
| 4181 | + | |
| 4182 | + | |
4081 | 4183 | | |
4082 | 4184 | | |
4083 | 4185 | | |
| |||
4169 | 4271 | | |
4170 | 4272 | | |
4171 | 4273 | | |
4172 | | - | |
4173 | | - | |
4174 | | - | |
4175 | | - | |
4176 | | - | |
4177 | | - | |
4178 | | - | |
4179 | | - | |
4180 | | - | |
4181 | | - | |
4182 | | - | |
4183 | | - | |
| 4274 | + | |
| 4275 | + | |
| 4276 | + | |
| 4277 | + | |
4184 | 4278 | | |
4185 | 4279 | | |
4186 | 4280 | | |
| |||
4194 | 4288 | | |
4195 | 4289 | | |
4196 | 4290 | | |
4197 | | - | |
4198 | | - | |
4199 | | - | |
4200 | | - | |
4201 | | - | |
4202 | | - | |
4203 | | - | |
4204 | | - | |
4205 | | - | |
4206 | | - | |
4207 | | - | |
4208 | | - | |
4209 | | - | |
4210 | | - | |
4211 | | - | |
4212 | | - | |
4213 | | - | |
4214 | | - | |
4215 | | - | |
4216 | | - | |
4217 | | - | |
4218 | | - | |
4219 | | - | |
4220 | | - | |
4221 | | - | |
4222 | | - | |
4223 | | - | |
4224 | | - | |
4225 | | - | |
4226 | | - | |
4227 | | - | |
4228 | | - | |
4229 | | - | |
4230 | | - | |
4231 | | - | |
4232 | | - | |
4233 | | - | |
4234 | | - | |
4235 | | - | |
| 4291 | + | |
| 4292 | + | |
| 4293 | + | |
| 4294 | + | |
| 4295 | + | |
| 4296 | + | |
| 4297 | + | |
| 4298 | + | |
| 4299 | + | |
| 4300 | + | |
| 4301 | + | |
| 4302 | + | |
| 4303 | + | |
| 4304 | + | |
| 4305 | + | |
| 4306 | + | |
| 4307 | + | |
| 4308 | + | |
| 4309 | + | |
| 4310 | + | |
| 4311 | + | |
| 4312 | + | |
| 4313 | + | |
| 4314 | + | |
| 4315 | + | |
| 4316 | + | |
| 4317 | + | |
| 4318 | + | |
| 4319 | + | |
| 4320 | + | |
4236 | 4321 | | |
4237 | 4322 | | |
4238 | 4323 | | |
| |||
0 commit comments