Render \ce chemistry + \pu units in docs and chat, and harden AI math instructions - #722
Conversation
Import katex/contrib/mhchem in the doc editor and chat renderer, and pin katex to one version via a pnpm override so the extension patches the single instance both surfaces render with. Without the dedupe, rehype-katex used its own katex copy and \ce/\pu fell back to error-colored source text in chat.
Teach the model that chemistry (\ce) and units (\pu) render, and drop two redundancies: the "KaTeX also supports matrices/aligned/..." hint (models already produce those unprompted) and the doc-HTML ruleset that was repeated in the create initialContent field (the create tool description already carries it, matching edit).
Lockfile resolution for the KaTeX single-instance override. Also carries the vitest-evals devDependency the `vp config` prepare hook added during install, kept so package.json and the lockfile stay consistent.
Live evals (pnpm eval) grade tool choice, argument validity, and answer quality against real models through the gateway, run in the workers pool alongside the app's own worker tests. Add a free, zero-token snapshot test of the model-facing surface (system prompt + tool input JSON schemas) that runs in normal CI. Move the operation failure-code consts into a pure leaf module so the tool schema layer no longer depends on the operation impls, which keeps it Node-safe for the snapshot test.
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
|
Capy auto-review is paused for this organization because the usage-cycle auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews. |
|
React Doctor found 1 new issue in 1 file · 1 warning · score 92 / 100 (Great) · 0 fixed · vs Reviewed by React Doctor for commit |
|
Warning Review limit reached
Next review available in: 31 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis PR centralizes workspace operation failure codes, adds chemistry and unit notation support, and introduces snapshot and live AI evaluations for workspace tools. ChangesWorkspace AI evaluation and tooling
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant EvaluationSuite
participant WorkspaceAgent
participant WorkspaceTools
participant AIGateway
participant Scorers
EvaluationSuite->>WorkspaceAgent: Run dataset case
WorkspaceAgent->>WorkspaceTools: Load workspace tool schemas
WorkspaceAgent->>AIGateway: Request model generation
AIGateway-->>WorkspaceAgent: Return text and tool calls
WorkspaceAgent-->>EvaluationSuite: Return normalized output
EvaluationSuite->>Scorers: Check inputs and tool usage
Scorers->>AIGateway: Grade rubric answer when configured
AIGateway-->>Scorers: Return structured score
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 052d97c256
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| description: definition.description, | ||
| // Heterogeneous across tools; the eval only needs the schema rendered | ||
| // to the model, so widen past the per-tool union. | ||
| inputSchema: asSchema(definition.inputSchema as z.ZodTypeAny), |
There was a problem hiding this comment.
Use provider-compatible schemas in the eval
When a case selects claude-sonnet or claude-haiku through the supported modelId field, this sends the raw Zod schemas, several of which emit maxItems. Production deliberately removes that keyword in createProviderCompatibleInputSchema because Anthropic-compatible providers reject it, so these evals can fail at the gateway instead of grading model behavior. Reuse the production schema adapter rather than wrapping definition.inputSchema directly.
Useful? React with 👍 / 👎.
| // Neutral stub so a follow-up step (e.g. read → edit) can still proceed. | ||
| execute: async () => ({ ok: true, note: "eval stub — no real mutation" }), |
There was a problem hiding this comment.
Return realistic results for multi-step tool evals
For the added read-then-edit case, workspace_read_items returns this generic object instead of document HTML and a valid data-ref, so the model cannot construct the requested content-preserving edit from the first step. Since the scorer accepts any nonempty ref as schema-valid, a hallucinated target can still make the case pass, while a model that correctly refuses to edit without content can fail. Use per-tool, schema-valid fixtures—especially a read result containing editable HTML and refs—for multi-step cases.
Useful? React with 👍 / 👎.
| tool({ | ||
| description: definition.description, | ||
| // Heterogeneous across tools; the eval only needs the schema rendered | ||
| // to the model, so widen past the per-tool union. | ||
| inputSchema: asSchema(definition.inputSchema as z.ZodTypeAny), |
There was a problem hiding this comment.
Include production tool input examples in evals
The production workspace adapter passes definition.inputExamples into every tool, and the shared language-model middleware injects those examples into the model prompt, but these reconstructed tools omit them. Consequently the live eval exercises a materially different tool surface, cannot detect regressions to the examples, and may report selection or argument-validity failures that do not reproduce in the app. Pass through definition.inputExamples or reuse the production tool factory.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/features/workspaces/operations/workspace-operation-failure-codes.ts (1)
1-7: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winKeep this Node-eval contract module dependency-free.
workspace-operation-failure-codes.tsis documented as a pure leaf, but importing it evaluatesdocument-ai-edits.ts, which imports@tiptap/pm/modelandzod. Move the operation/vocabulary-level failure-code arrays into a separate dependency-free file and keep ProseMirror/Zod-related files out of this Node eval path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/workspaces/operations/workspace-operation-failure-codes.ts` around lines 1 - 7, Move the workspace operation and vocabulary failure-code arrays currently sourced through documentAiEditFailureCodes and workspaceRelationFailureCodes into a separate dependency-free module. Update workspace-operation-failure-codes.ts and all consumers to import those arrays from the new leaf module, ensuring document-ai-edits.ts and other ProseMirror/Zod-dependent files are not evaluated by the Node-facing contract path.src/features/workspaces/operations/workspace-tool-surface.test.ts (1)
5-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDerive the tool list from
workspaceToolDefinitionsto avoid drift.This file re-enumerates workspace tool names and schemas by hand.
eval/support/harness.tsalready treatsworkspaceToolDefinitionsas the canonical list of tools (name + description +inputSchema). If a tool is added, renamed, or removed there without a matching manual update here, this snapshot test silently stops covering it — undermining the regression-net purpose the file's own comment describes.Build
TOOL_INPUT_SCHEMASfromworkspaceToolDefinitionsinstead of importing each schema individually.♻️ Proposed refactor
-import { getAIThreadSoulPrompt } from "`#/features/workspaces/ai/ai-thread-soul-prompt`"; -import { - workspaceCreateItemsInputSchema, - workspaceDeleteItemsInputSchema, - workspaceEditItemInputSchema, - workspaceLinkItemsInputSchema, - workspaceListItemsInputSchema, - workspaceMoveItemsInputSchema, - workspaceReadItemsInputSchema, - workspaceRenameItemInputSchema, - workspaceSearchInputSchema, -} from "`#/features/workspaces/operations/workspace-tool-schemas`"; +import { getAIThreadSoulPrompt } from "`#/features/workspaces/ai/ai-thread-soul-prompt`"; +import { workspaceToolDefinitions } from "`#/features/workspaces/operations/workspace-tool-definitions`"; ... -const TOOL_INPUT_SCHEMAS = { - workspace_list_items: workspaceListItemsInputSchema, - workspace_read_items: workspaceReadItemsInputSchema, - workspace_search: workspaceSearchInputSchema, - workspace_create_items: workspaceCreateItemsInputSchema, - workspace_edit_item: workspaceEditItemInputSchema, - workspace_delete_items: workspaceDeleteItemsInputSchema, - workspace_move_items: workspaceMoveItemsInputSchema, - workspace_rename_item: workspaceRenameItemInputSchema, - workspace_link_items: workspaceLinkItemsInputSchema, -} as const; +const TOOL_INPUT_SCHEMAS = Object.fromEntries( + workspaceToolDefinitions.map((definition) => [definition.name, definition.inputSchema]), +);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/features/workspaces/operations/workspace-tool-surface.test.ts` around lines 5 - 36, Update workspace-tool-surface.test.ts to derive TOOL_INPUT_SCHEMAS from the canonical workspaceToolDefinitions collection, using each definition’s name and inputSchema rather than manually importing and enumerating individual schemas. Remove the redundant per-schema imports while preserving the existing tool-schema snapshot coverage and key format.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/features/workspaces/operations/workspace-operation-failure-codes.ts`:
- Around line 1-7: Move the workspace operation and vocabulary failure-code
arrays currently sourced through documentAiEditFailureCodes and
workspaceRelationFailureCodes into a separate dependency-free module. Update
workspace-operation-failure-codes.ts and all consumers to import those arrays
from the new leaf module, ensuring document-ai-edits.ts and other
ProseMirror/Zod-dependent files are not evaluated by the Node-facing contract
path.
In `@src/features/workspaces/operations/workspace-tool-surface.test.ts`:
- Around line 5-36: Update workspace-tool-surface.test.ts to derive
TOOL_INPUT_SCHEMAS from the canonical workspaceToolDefinitions collection, using
each definition’s name and inputSchema rather than manually importing and
enumerating individual schemas. Remove the redundant per-schema imports while
preserving the existing tool-schema snapshot coverage and key format.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 19dfe2ce-af52-400b-8bb8-3c1339e1cc7f
⛔ Files ignored due to path filters (2)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlsrc/features/workspaces/operations/__snapshots__/workspace-tool-surface.test.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (21)
eval/README.mdeval/datasets/workspace-tools.cases.tseval/support/harness.tseval/support/scorers.tseval/workspace-tools.eval.tspackage.jsonpnpm-workspace.yamlsrc/features/workspaces/ai/ai-thread-soul-prompt.tssrc/features/workspaces/components/ai-chat/AiChatMessageResponse.tsxsrc/features/workspaces/documents/tiptap-extensions.tssrc/features/workspaces/operations/create-items.tssrc/features/workspaces/operations/delete-items.tssrc/features/workspaces/operations/edit-item.tssrc/features/workspaces/operations/link-items.tssrc/features/workspaces/operations/move-items.tssrc/features/workspaces/operations/rename-item.tssrc/features/workspaces/operations/workspace-operation-failure-codes.tssrc/features/workspaces/operations/workspace-tool-schemas.tssrc/features/workspaces/operations/workspace-tool-surface.test.tstsconfig.jsonvitest.evals.config.ts
Greptile SummaryThis change enables mhchem rendering in workspace documents and chat, expands workspace-agent math guidance, centralizes operation failure codes, and adds agent evaluation coverage. The new read-then-edit evaluation can report success even when the agent has not received document HTML or a valid item reference: the read stub returns only a neutral success object, while fabricated refs and full-document replacements satisfy the current checks. This leaves targeted document-edit regressions undetected. Confidence Score: 4/5Not safe to merge until the targeted-edit evaluation verifies read-derived document references rather than only tool names and input shape. Focused execution reproduced a passing read-then-edit sequence with no document data, a fabricated item reference, and a full-document replacement. This makes the new evaluation unable to detect a meaningful class of workspace-agent editing regressions. Files Needing Attention: eval/support/harness.ts needs a realistic workspace_read_items fixture, and eval/workspace-tools.eval.ts plus eval/support/scorers.ts need assertions that connect the returned refs to the requested targeted edit.
What T-Rex did
|
| // to the model, so widen past the per-tool union. | ||
| inputSchema: asSchema(definition.inputSchema as z.ZodTypeAny), | ||
| // Neutral stub so a follow-up step (e.g. read → edit) can still proceed. | ||
| execute: async () => ({ ok: true, note: "eval stub — no real mutation" }), |
There was a problem hiding this comment.
Read stub removes targeted-edit provenance
workspace_read_items receives the same { ok: true } response as every mutating tool, so the read-then-edit evaluation never supplies HTML or the data-ref values required to make a targeted edit. The grading only checks that workspace_read_items and workspace_edit_item were called with schema-valid inputs; a fabricated nonempty ref, or a replace_all edit that does not use a ref, receives a passing score. As a result, this evaluation can pass while the model cannot perform the production read-to-targeted-edit workflow it is intended to cover.
Return a deterministic read fixture containing document HTML and real data-ref values, then require the targeted scenario to submit a targeted operation whose ref is present in that fixture. Treat replace_all as a separate scenario rather than a passing substitute for a targeted edit.
Artifacts
Focused validation test source for the read-stub grading gap
- The executed Vitest source invokes the current read stub and checks fabricated-reference, replace-all, and scorer behavior; it reproduces the missing-provenance coverage gap.
Initial focused validation run
- The initial worker-pool Vitest execution completed one focused test successfully against the current harness; it establishes that the changed-stack test is executable.
Focused validation output showing neutral read and permissive scoring
- The verbose worker-pool Vitest run printed the neutral read result and showed fabricated references, replace-all, expected tools, and input validity all passing; it confirms the eval gap.
Live workspace eval skipped without gateway credentials
- The actual live eval command detected no `AI_GATEWAY_API_KEY` and skipped all six cases; it shows why a real gateway model turn could not be run in this environment.
There was a problem hiding this comment.
5 issues found across 23 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="eval/datasets/workspace-tools.cases.ts">
<violation number="1" location="eval/datasets/workspace-tools.cases.ts:48">
P2: The “read then edit” eval does not detect the ordering failure it is intended to catch: any sequence containing both tools passes. Adding an ordered tool-sequence assertion (or a case-specific scorer) would verify that `workspace_read_items` occurs before `workspace_edit_item`.</violation>
<violation number="2" location="eval/datasets/workspace-tools.cases.ts:61">
P2: A read-only turn can still call `workspace_link_items` and pass this case, even though the injected scope explicitly forbids linking items. Including the link tool in `forbiddenTools` would make the test enforce the stated boundary.</violation>
<violation number="3" location="eval/datasets/workspace-tools.cases.ts:75">
P2: The general-question case does not enforce its no-tool requirement because five workspace tools are missing from `forbiddenTools`. Listing every workspace tool (or adding an explicit no-tool assertion) would prevent tool calls from being silently accepted.</violation>
</file>
<file name="eval/support/scorers.ts">
<violation number="1" location="eval/support/scorers.ts:1">
P2: The deterministic scorers are no longer portable outside the Cloudflare Workers test pool because this module has a top-level `cloudflare:test` dependency. Keeping pure scorers in a dependency-free module and isolating or injecting the worker-specific judge dependencies would preserve the portability documented in `eval/README.md`.</violation>
<violation number="2" location="eval/support/scorers.ts:88">
P2: A model-generated answer can inject instructions into the LLM judge and make a failing evaluation pass. Serializing the fields as quoted data and explicitly telling the judge to ignore instructions in `userPrompt` and `assistantAnswer` would make the grading path resistant to this failure mode.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| prompt: | ||
| "In /Notes/Standup.md, add a second bullet that says 'Review metrics'. Read the document first, then make the edit.", | ||
| }, | ||
| expectedTools: ["workspace_read_items", "workspace_edit_item"], |
There was a problem hiding this comment.
P2: The “read then edit” eval does not detect the ordering failure it is intended to catch: any sequence containing both tools passes. Adding an ordered tool-sequence assertion (or a case-specific scorer) would verify that workspace_read_items occurs before workspace_edit_item.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At eval/datasets/workspace-tools.cases.ts, line 48:
<comment>The “read then edit” eval does not detect the ordering failure it is intended to catch: any sequence containing both tools passes. Adding an ordered tool-sequence assertion (or a case-specific scorer) would verify that `workspace_read_items` occurs before `workspace_edit_item`.</comment>
<file context>
@@ -0,0 +1,80 @@
+ prompt:
+ "In /Notes/Standup.md, add a second bullet that says 'Review metrics'. Read the document first, then make the edit.",
+ },
+ expectedTools: ["workspace_read_items", "workspace_edit_item"],
+ },
+ {
</file context>
| "workspace_create_items", | ||
| "workspace_edit_item", | ||
| "workspace_delete_items", | ||
| "workspace_search", |
There was a problem hiding this comment.
P2: The general-question case does not enforce its no-tool requirement because five workspace tools are missing from forbiddenTools. Listing every workspace tool (or adding an explicit no-tool assertion) would prevent tool calls from being silently accepted.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At eval/datasets/workspace-tools.cases.ts, line 75:
<comment>The general-question case does not enforce its no-tool requirement because five workspace tools are missing from `forbiddenTools`. Listing every workspace tool (or adding an explicit no-tool assertion) would prevent tool calls from being silently accepted.</comment>
<file context>
@@ -0,0 +1,80 @@
+ "workspace_create_items",
+ "workspace_edit_item",
+ "workspace_delete_items",
+ "workspace_search",
+ ],
+ qualityRubric:
</file context>
| "workspace_create_items", | ||
| "workspace_edit_item", | ||
| "workspace_move_items", | ||
| "workspace_rename_item", |
There was a problem hiding this comment.
P2: A read-only turn can still call workspace_link_items and pass this case, even though the injected scope explicitly forbids linking items. Including the link tool in forbiddenTools would make the test enforce the stated boundary.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At eval/datasets/workspace-tools.cases.ts, line 61:
<comment>A read-only turn can still call `workspace_link_items` and pass this case, even though the injected scope explicitly forbids linking items. Including the link tool in `forbiddenTools` would make the test enforce the stated boundary.</comment>
<file context>
@@ -0,0 +1,80 @@
+ "workspace_create_items",
+ "workspace_edit_item",
+ "workspace_move_items",
+ "workspace_rename_item",
+ ],
+ qualityRubric:
</file context>
| @@ -0,0 +1,97 @@ | |||
| import { env } from "cloudflare:test"; | |||
There was a problem hiding this comment.
P2: The deterministic scorers are no longer portable outside the Cloudflare Workers test pool because this module has a top-level cloudflare:test dependency. Keeping pure scorers in a dependency-free module and isolating or injecting the worker-specific judge dependencies would preserve the portability documented in eval/README.md.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At eval/support/scorers.ts, line 1:
<comment>The deterministic scorers are no longer portable outside the Cloudflare Workers test pool because this module has a top-level `cloudflare:test` dependency. Keeping pure scorers in a dependency-free module and isolating or injecting the worker-specific judge dependencies would preserve the portability documented in `eval/README.md`.</comment>
<file context>
@@ -0,0 +1,97 @@
+import { env } from "cloudflare:test";
+import { Output, generateText } from "ai";
+import { z } from "zod";
</file context>
| output: Output.object({ schema: QUALITY_VERDICT_SCHEMA }), | ||
| system: | ||
| "You are a strict grader. Score how well the ASSISTANT ANSWER satisfies the RUBRIC for the given USER PROMPT. Return pass=false unless the rubric is clearly met. score is 0..1.", | ||
| prompt: `USER PROMPT:\n${args.prompt}\n\nASSISTANT ANSWER:\n${args.answer}\n\nRUBRIC:\n${args.rubric}`, |
There was a problem hiding this comment.
P2: A model-generated answer can inject instructions into the LLM judge and make a failing evaluation pass. Serializing the fields as quoted data and explicitly telling the judge to ignore instructions in userPrompt and assistantAnswer would make the grading path resistant to this failure mode.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At eval/support/scorers.ts, line 88:
<comment>A model-generated answer can inject instructions into the LLM judge and make a failing evaluation pass. Serializing the fields as quoted data and explicitly telling the judge to ignore instructions in `userPrompt` and `assistantAnswer` would make the grading path resistant to this failure mode.</comment>
<file context>
@@ -0,0 +1,97 @@
+ output: Output.object({ schema: QUALITY_VERDICT_SCHEMA }),
+ system:
+ "You are a strict grader. Score how well the ASSISTANT ANSWER satisfies the RUBRIC for the given USER PROMPT. Return pass=false unless the rubric is clearly met. score is 0..1.",
+ prompt: `USER PROMPT:\n${args.prompt}\n\nASSISTANT ANSWER:\n${args.answer}\n\nRUBRIC:\n${args.rubric}`,
+ });
+
</file context>
The eval hand-rolled its toolset instead of the production path, so it
diverged in three ways the reviewers caught:
- Sent raw Zod schemas (with `maxItems`, which Anthropic-compatible
providers reject) instead of the provider-compatible schema. Now reuses
the exported `createProviderCompatibleInputSchema`.
- Omitted `inputExamples`, which the gateway middleware injects into the
prompt in production. Now passed through per tool.
- Gave `workspace_read_items` the same neutral `{ok:true}` stub as every
tool, so the read→edit case could pass with a fabricated ref or a
whole-doc `replace_all`. Now returns a fixture with real document HTML +
`data-ref`s, and `scoreTargetedEditProvenance` requires a targeted edit
whose ref traces back to that fixture.
Also collapses a two-pass map().filter() in scoreNoForbiddenTools.
| validRefs: string[], | ||
| ): ScoreResult { | ||
| const refs = new Set(validRefs); | ||
| const edits = output.toolCalls |
There was a problem hiding this comment.
React Doctor · react-doctor/js-combine-iterations (warning)
This loops over your list twice because .filter().flatMap() makes two passes, so do it in one pass with .reduce() or a for...of loop
Fix → Combine .map().filter() style chains into one pass with .reduce() or a for...of loop, so you only loop over the list once
What
Math authoring across both surfaces (documents and AI chat) now renders chemistry (
\ce{…}) and units (\pu{…}), and the model is guided to write correct math for each surface. Everything was verified end-to-end through the real pipelines + KaTeX.Why
Three problems, all confirmed with evals across Claude tiers + the real doc/chat render pipelines:
mhchemextension wasn't imported, so\ce{}/\pu{}came out as error-colored source text.\ce/\puwere broken in chat even after importing mhchem — because@streamdown/mathbundles its ownkatex@0.16.x, a different instance than the app's0.17.0that the mhchem import patches. Proven by running Streamdown's actualrehype-katex(the source was painted in the errorColor, same as a genuinely-undefined command).\(…\)), currency\$escaping bleeding into HTML docs (visible backslash), and<sub>/<sup>tags that reject the whole write.How
import "katex/contrib/mhchem"in the doc editor (tiptap-extensions.ts) and chat renderer (AiChatMessageResponse.tsx).pnpm-workspace.yamloverride (katex: 0.17.0) so the one mhchem import patches the instance both surfaces render with.0.17.0satisfies both consumers (tiptap-math peer^0.16.4 || ^0.17.0, rehype-katex^0.16).workspaceDocumentHtmlInstruction+ the chat soul prompt): usedata-latex(never$…$/\(…\)) in docs, plain$30currency in HTML, sub/superscripts as math, and\ce/\pufor chemistry/units. Also removed two redundancies (a "KaTeX supports matrices/…" hint models already follow, and a doc-ruleset that was duplicated in the create tool'sinitialContentfield).Verification
\ce(states/->[H2O]/equilibrium),\puunits.pnpm lint(types + lint) green.Commits
feat(workspaces): render \ce chemistry and \pu units via KaTeX mhchem— imports + katex dedupefix(workspaces): tighten AI doc/chat math authoring instructions— prompt hardening + de-dupchore(deps): dedupe katex to 0.17.0 in the lockfile— lockfile resolution (also carries thevitest-evalsdevDep added by thevp configprepare hook)feat(ai): add workspace agent eval harness— related eval scaffoldingNotes for reviewers
truststaysfalse(LLM/user-derived content), so\href/\html*remain disabled.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by CodeRabbit