Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
EXECUTOR_MAX_WORKERS = 30
CHAT_STREAM_EXECUTOR_MAX_WORKERS = max(8, int(os.getenv('SIMPLECHAT_CHAT_STREAM_WORKERS', '32') or '32'))
SESSION_TYPE = 'filesystem'
VERSION = "0.261.424"
VERSION = "0.261.425"
IS_DEVELOPMENT = is_development_env_enabled()

# Opt-out for deployments where App Service Easy Auth is active but the platform
Expand Down
12 changes: 11 additions & 1 deletion application/v2_ui/src/components/chat/AssistantMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,16 @@
// out here would render the raw ⟦cite:N⟧ token as visible text.
p: ({ children }) => <p>{renderTokens(children)}</p>,
li: ({ children }) => <li>{renderTokens(children)}</li>,
table: ({ node: _node, ...props }) => (
<div
className="chat-markdown-table min-w-0 max-w-full overflow-x-auto rounded-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-accent"
role="region"

Check warning on line 241 in application/v2_ui/src/components/chat/AssistantMarkdown.tsx

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
aria-label="Table"
tabIndex={0}
>
<table {...props} />
</div>
),
td: ({ children }) => <td>{renderTokens(children)}</td>,
th: ({ children }) => <th>{renderTokens(children)}</th>,
h1: ({ children }) => <h1>{renderTokens(children)}</h1>,
Expand Down Expand Up @@ -374,7 +384,7 @@
return (
<div
className={clsx(
'text-[15px] leading-relaxed break-words',
'min-w-0 text-[15px] leading-relaxed break-words',
'[&_p]:my-2 [&_p:first-child]:mt-0 [&_p:last-child]:mb-0',
'[&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5',
'[&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5',
Expand Down
3 changes: 2 additions & 1 deletion application/v2_ui/src/components/chat/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,7 @@ function MessageBubbleInner({
className={clsx(
bubbleWidthClass(chatWidth),
'rounded-2xl px-4 py-3',
!isUser && 'chat-assistant-bubble min-w-0',
// These are repeated per message, so they use the non-blurred surface:
// a backdrop-filter per bubble makes long threads scroll badly.
alignRight && !agentPosted
Expand Down Expand Up @@ -1434,7 +1435,7 @@ function StreamingBubble() {

return (
<div data-scroll-live-reply className="flex justify-start">
<div className={clsx(streamingContent ? 'glass-flat rounded-2xl px-4 py-3' : 'min-w-0 py-2', bubbleWidthClass(chatWidth))}>
<div className={clsx('chat-assistant-bubble min-w-0', streamingContent ? 'glass-flat rounded-2xl px-4 py-3' : 'py-2', bubbleWidthClass(chatWidth))}>
{/* Shown while connecting even when partial content is already on screen:
without it, a response that stopped mid-sentence just looks frozen. */}
{connecting && (
Expand Down
11 changes: 11 additions & 0 deletions application/v2_ui/src/styles/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,17 @@ html[data-font-size='xl'] {
}

@layer utilities {
.chat-assistant-bubble:has(.chat-markdown-table) {
width: 100%;
max-width: 100%;
}

.chat-markdown-table :is(th, td) {
min-width: clamp(3.5rem, 8vw, 8rem);
overflow-wrap: anywhere;
white-space: normal;
}

/*
* The Admin Settings card and field rules below are shared with the V2 workflow editor
* (SectionCard.tsx and WorkflowField.tsx), so a workflow card reads as the same object as an
Expand Down
117 changes: 117 additions & 0 deletions docs/explanation/fixes/V2_CHAT_TABLE_OVERFLOW_FIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# V2 Chat Table Overflow Fix

Fixed in version: **0.261.425**

## Issue and root cause

Wide tables in V2 assistant replies could extend past their message bubble. Long
identifiers, compact JSON, and URLs contributed an intrinsic table width larger
than the bubble's reading-width limit. Setting the table to `width: 100%` did not
override that minimum content width, and there was no table-local scroll
container.

The normal bubble limits remain useful for prose: wide mode caps bubbles at
`min(64rem, 90%)`, and comfortable mode at `min(46rem, 85%)`. The defect was
applying those limits to evidence tables without handling their layout and
overflow separately.

## Changes

`application/v2_ui/src/components/chat/MessageList.tsx` gives saved assistant
bubbles and the streaming bubble a shared layout class and permits them to
shrink within their flex container. User bubbles retain their existing classes.

`application/v2_ui/src/components/chat/AssistantMarkdown.tsx` wraps each Markdown
table in a bounded horizontal scroll region. The region is keyboard focusable,
has the accessible name **Table**, and shows a focus outline. The semantic
`table`, header cells, data cells, citation substitutions, and safe React

Check warning on line 27 in docs/explanation/fixes/V2_CHAT_TABLE_OVERFLOW_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
rendering remain intact.

`application/v2_ui/src/styles/theme.css` expands only assistant bubbles that
contain a rendered Markdown table. They can fill the existing conversation

Check warning on line 31 in docs/explanation/fixes/V2_CHAT_TABLE_OVERFLOW_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
container rather than stopping at the normal bubble reading limit. Comfortable
mode still limits the conversation container; wide mode still allows it to fill

Check warning on line 33 in docs/explanation/fixes/V2_CHAT_TABLE_OVERFLOW_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
the pane. No preference is changed or persisted by this fix.

Table cells wrap text, including long unbroken records, with
`overflow-wrap: anywhere`. A responsive minimum cell width of
`clamp(3.5rem, 8vw, 8rem)` prevents a short column from collapsing into a narrow
strip beside a long record. Column allocation otherwise remains automatic; the
fix does not impose equal-width columns or reduce the font size.

`application/single_app/config.py` increments `VERSION` from `0.261.424` to
`0.261.425`. The new functional and UI test headers record that implementation

Check warning on line 43 in docs/explanation/fixes/V2_CHAT_TABLE_OVERFLOW_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Moderate - Changed line contains obfuscation, dynamic loading, or hidden payload marker. Recommendation%3A Confirm the changed code is not hiding behavior, decoding payloads, or bypassing normal review.
version.

## User-visible behavior and boundaries

Tables use available horizontal space, then wrap their content. If the columns
or non-wrapping content still cannot fit, the reader can scroll the table
horizontally without scrolling the conversation sideways. For example, the

Check warning on line 50 in docs/explanation/fixes/V2_CHAT_TABLE_OVERFLOW_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
four-column regression fixture fits at 393 pixels but needs local scrolling at
320 pixels when it includes a non-wrapping citation chip. The same text-only

Check warning on line 52 in docs/explanation/fixes/V2_CHAT_TABLE_OVERFLOW_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
table wraps without horizontal scrolling at 320 pixels.

The shared rules apply as a streamed reply becomes a table, after finalization,
and when the saved message is loaded again. Plain prose messages keep their
existing reading widths. Classic chat, backend APIs, settings, browser assets,
and deployers are unchanged.

Very narrow tables can become tall because content is retained rather than
truncated. Tables with many columns intentionally use the scroll fallback.
Validation used desktop Chromium, including emulated narrow viewports; it does
not constitute real-device or cross-browser qualification.

## Regression coverage

`functional_tests/test_v2_chat_table_layout.py` checks the saved/streaming layout
wiring, unchanged width preferences, rendered-table scoping, wrapping, keyboard
focus, and safe semantic Markdown rendering.

`ui_tests/test_v2_chat_table_layout.py` runs production components, stores, and
production CSS through the existing V2 browser harness. It covers:

- Evidence tables at 320, 393, 768, 1280, and 1920 pixels, in light/dark themes
and both reading-width preferences.
- Long identifiers, JSON in inline code, long links, and document citations.

Check warning on line 76 in docs/explanation/fixes/V2_CHAT_TABLE_OVERFLOW_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
- Table and bubble geometry, cell overflow, page/pane containment, minimum cell
width, unchanged table font size, and narrower ordinary messages.
- Resizing the same conversation, partial streaming, finalization, and remounting

Check warning on line 79 in docs/explanation/fixes/V2_CHAT_TABLE_OVERFLOW_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
the saved reply.
- A 30-column table whose final column is reached with keyboard scrolling.

The shared browser fixtures support local Chromium or an existing Azure
Playwright workspace using `DefaultAzureCredential`. No cloud resources or

Check warning on line 84 in docs/explanation/fixes/V2_CHAT_TABLE_OVERFLOW_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
credentials are created by these tests. Screenshots are written to the ignored
`ui_tests/artifacts/v2-chat-tables/` directory.

## Validation results

The new desktop evidence-table regression failed against the original
implementation: at a 1920-pixel viewport, the table's right edge reached
approximately 4533 pixels while its bubble ended at 1040 pixels. After the fix,
the bubble expands within the conversation and the content stays within its
table scroll region.

| Command | Result |
| --- | --- |
| `npm --prefix .\application\v2_ui run build -- --outDir ..\..\ui_tests\artifacts\orchestration-plan-editor` | Passed TypeScript compilation and production build; Vite reported its existing large-chunk warning. |
| `python .\functional_tests\test_v2_chat_table_layout.py` | 3 passed. |
| `python .\functional_tests\test_v2_mobile_layout.py` | 5 passed. |
| `python -m pytest .\ui_tests\test_v2_chat_table_layout.py .\ui_tests\test_v2_mobile_chat_layout.py -q --tb=short` | 18 passed: 9 table regressions and 9 existing mobile-layout regressions. |
| `python .\functional_tests\test_docs_site_quality.py` | 6 of 6 checks passed. |
| `git -c core.whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol diff --check` | Passed. |

The adjacent `test_v2_orchestration_streaming_bubble.py` suite was also attempted.
It reported three assertion failures and eight teardown errors: its API stub
does not handle the current pending-question, credential-request, and user-input
reads, and three tests expect a **Review the running plan** button that is not
found. A representative failing test was rerun with all three production UI
files restored to their original source and reproduced both the missing-button
failure and API-stub errors. These unrelated tests were left unchanged.

The required UI detector reported the existing activity-indicator
`animate-bounce` rule and a false positive matching the literal `<img>` in an
accessibility comment. The actual adjacent image has a resolved `src` and an
error handler. Neither finding concerns the table changes; no detector
suppression or unrelated design change was added.
63 changes: 63 additions & 0 deletions functional_tests/test_v2_chat_table_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# test_v2_chat_table_layout.py
"""
Functional contracts for responsive V2 assistant tables.
Version: 0.261.425
Implemented in: 0.261.425

Pin shared rendering and scoped saved/streaming layout wiring. Real browser
geometry, content preservation and keyboard scrolling are covered in ui_tests.
"""

from pathlib import Path
import re

import pytest


ROOT = Path(__file__).resolve().parents[1]
SOURCE = ROOT / "application" / "v2_ui" / "src"


def test_saved_and_streaming_bubbles_share_table_layout_without_changing_user_widths():
source = (SOURCE / "components" / "chat" / "MessageList.tsx").read_text("utf-8")
saved, streaming = source.split("function StreamingBubble()", 1)
assert "!isUser && 'chat-assistant-bubble min-w-0'" in saved
assert "'chat-assistant-bubble min-w-0'" in streaming
assert "bubbleWidthClass(chatWidth)" in saved
assert "bubbleWidthClass(chatWidth)" in streaming
widths = (SOURCE / "lib" / "chatWidth.ts").read_text("utf-8")
assert "'max-w-[min(64rem,90%)]' : 'max-w-[min(46rem,85%)]'" in widths
assert "'max-w-none' : 'max-w-4xl'" in widths


def test_table_expansion_is_scoped_to_rendered_tables_and_cells_can_wrap():
css = (SOURCE / "styles" / "theme.css").read_text("utf-8")
bubble = re.search(r"\.chat-assistant-bubble:has\(\.chat-markdown-table\)\s*\{([^}]+)\}", css)
assert bubble is not None
assert "width: 100%;" in bubble.group(1)
assert "max-width: 100%;" in bubble.group(1)
cells = re.search(r"\.chat-markdown-table :is\(th, td\)\s*\{([^}]+)\}", css)
assert cells is not None
assert "min-width: clamp(3.5rem, 8vw, 8rem);" in cells.group(1)
assert "overflow-wrap: anywhere;" in cells.group(1)
assert "white-space: normal;" in cells.group(1)
assert "table-layout: fixed" not in cells.group(1)


def test_shared_markdown_table_wrapper_preserves_safe_semantic_rendering():
source = (SOURCE / "components" / "chat" / "AssistantMarkdown.tsx").read_text("utf-8")
wrapper = source.split("table: ({ node: _node, ...props }) => (", 1)[1].split("td:", 1)[0]
assert "chat-markdown-table min-w-0 max-w-full overflow-x-auto" in wrapper
assert 'role="region"' in wrapper
assert 'aria-label="Table"' in wrapper
assert "tabIndex={0}" in wrapper
assert "<table {...props} />" in wrapper
assert "focus-visible:outline-accent" in wrapper
assert "dangerouslySetInnerHTML" not in source
assert "rehype-raw" not in source.split("const components =", 1)[1]
assert "td: ({ children }) => <td>{renderTokens(children)}</td>" in source
assert "th: ({ children }) => <th>{renderTokens(children)}</th>" in source


if __name__ == "__main__":
raise SystemExit(pytest.main([__file__, "-q"]))
Loading
Loading