Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e02cb11
fix: render buyer-facing image evidence
seonghobae Aug 20, 2026
b0edd9b
fix: label unavailable image evidence
seonghobae Aug 20, 2026
116a8dc
Merge remote-tracking branch 'origin/fix/oidc-deep-link-locale' into …
seonghobae Aug 20, 2026
5deb067
docs: avoid buyer image adr number collision
seonghobae Aug 20, 2026
313d38a
docs: align image evidence ADR heading
seonghobae Aug 20, 2026
a4d1de5
test(image): cover source placement and OCR rendering
seonghobae Aug 20, 2026
49d85d0
Merge remote-tracking branch 'origin/fix/oidc-deep-link-locale' into …
seonghobae Aug 20, 2026
516603c
Merge remote-tracking branch 'origin/fix/oidc-deep-link-locale' into …
seonghobae Aug 20, 2026
e5ee5db
Merge remote-tracking branch 'origin/fix/oidc-deep-link-locale' into …
seonghobae Aug 20, 2026
9e17c2a
fix: validate buyer image sources at render boundary
seonghobae Aug 20, 2026
fbecd77
Merge remote-tracking branch 'origin/fix/oidc-deep-link-locale' into …
seonghobae Aug 21, 2026
ef6f2b0
Merge latest OIDC deep-link fixes into image evidence
seonghobae Aug 21, 2026
390c8c4
fix: close remaining browser provider error leaks
seonghobae Aug 21, 2026
d11fae2
docs: record browser provider error boundary
seonghobae Aug 21, 2026
ff562a9
Merge commit '5c42aa2ee35aa27ba26b74c7204adb8c0781e8d6' into fix/pr30…
seonghobae Aug 21, 2026
6bb6d4e
Merge remote-tracking branch 'origin/fix/oidc-deep-link-locale' into …
seonghobae Aug 21, 2026
a1b99a1
Merge remote-tracking branch 'origin/fix/buyer-image-evidence' into c…
seonghobae Aug 21, 2026
b818ef8
Merge remote-tracking branch 'origin/fix/buyer-image-evidence' into c…
seonghobae Aug 21, 2026
3131868
Merge commit '1e7eb26fef76ce2cd5dedf7cbf3235a5a9b1d661' into fix/pr30…
seonghobae Aug 21, 2026
20b904a
Merge branch 'fix/oidc-deep-link-locale' of https://github.com/Contex…
seonghobae Aug 21, 2026
f7b5140
fix: avoid inventing image table headers
seonghobae Aug 21, 2026
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
1 change: 1 addition & 0 deletions CHANGELOG.d/2.12.6-provider-error-boundary.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## Fixed

- Keep contextual-orchestrator, OIDC, RankWeave, TEPP, and durable-ingestion diagnostics behind stable product error boundaries while retaining the original exception for server-side chaining.
- Route browser transport and tenant-settings failures through the same boundary so 5xx provider details never reach buyer-facing error text.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Changelog and ADR edits are unrelated to the image-evidence change

The PR is titled "render buyer-facing image evidence" and the author description references ADR 0110, yet the non-code edits in this PR touch 2.12.6-provider-error-boundary.md and 0123-provider-error-boundary.md, both concerning the provider/browser error boundary rather than image rendering. The changelog line addition describes routing transport/tenant-settings failures through the error boundary, and the ADR change is a whitespace reflow. These appear to be leftover or misplaced doc edits that do not correspond to the code change; worth confirming they belong in this PR.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

- Keep browser 5xx and transport failures behind the same stable client error boundary.
6 changes: 3 additions & 3 deletions docs/adr/0123-provider-error-boundary.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ cannot escape as raw ``KeyError`` or type-error payloads from a library
boundary.

The browser API client is a second trust boundary: HTTP 5xx details are
discarded, and transport failures become a stable status-0 client error
before any UI handler can render them. Client-error details remain available
only for actionable validation or authorization responses.
discarded, and transport failures become a stable status-0 client error before
any UI handler can render them. Client-error details remain available only for
actionable validation or authorization responses.

Missing or malformed evidence remains unavailable; it is never converted into
a fabricated negative result. Existing input-validation errors outside a
Expand Down
38 changes: 38 additions & 0 deletions frontend/src/PostBody.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,44 @@ describe("PostBody", () => {
expect(screen.getByText("Panel")).toBeInTheDocument();
});

it("keeps escaped pipes and does not invent headers for unmarked region OCR", () => {
render(
<PostBody
body={'<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" />'}
imageContent={[
{
unit_index: 0,
mime_type: "image/png",
status_code: "described",
extracted_text: "| Item | State |\n| --- | --- |\n| Review \\| approve | Ready |",
caption: "A table image",
tags: [],
regions: [
{
region_index: 0,
x_ratio: 0,
y_ratio: 0,
width_ratio: 1,
height_ratio: 1,
status_code: "described",
extracted_text: "| 1 | Panel |\n| 2 | Ready |",
caption: "An OCR region without a Markdown header marker.",
tags: [],
},
],
},
]}
/>,
);

expect(screen.getByText("Review | approve")).toBeInTheDocument();
expect(screen.getByRole("columnheader", { name: "Item" })).toBeInTheDocument();
expect(screen.queryByRole("columnheader", { name: "1" })).not.toBeInTheDocument();
expect(document.querySelector(".post-image-region-text p")?.textContent).toBe(
"| 1 | Panel |\n| 2 | Ready |",
);
});

it("keeps source-image placement while showing persisted OCR and caption evidence", () => {
render(
<PostBody
Expand Down
29 changes: 24 additions & 5 deletions frontend/src/PostBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import type { PostContentUnit, PostImageContent } from "./api";
import type { ReactNode } from "react";

function parsePipeDelimitedTable(text: string): string[][] | null {
const rows = text
const parsedRows = text
.split(/\r?\n/)
.map((row) => {
const cells = row.split("|").map((cell) => cell.trim());
const cells = row.split(/(?<!\\)\|/).map((cell) => cell.trim().replace(/\\\|/g, "|"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Escaped-pipe split depends on regex lookbehind

Cell splitting uses negative lookbehind row.split(/(?<!\\)\|/) then unescapes with .replace(/\\\|/g, "|"). Correct for the tested cases and supported by current runtimes; noted only because it relies on lookbehind availability in the target environment.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

if (cells[0] === "") cells.shift();
if (cells[cells.length - 1] === "") cells.pop();
return cells;
})
});
if (!parsedRows.some((row) => row.every((cell) => /^:?-{3,}:?$/.test(cell)))) return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Blank line makes unmarked OCR render as a table with an invented header

A blank or whitespace-only line becomes an empty cell array, and [].every(...) returns true vacuously, so the separator-row gate passes without any real | --- | marker. OCR text containing a blank line plus two consistent pipe rows is then rendered as a table whose first data row is promoted to an invented header — the very case the change means to reject.

Suggested change
if (!parsedRows.some((row) => row.every((cell) => /^:?-{3,}:?$/.test(cell)))) return null;
if (!parsedRows.some((row) => row.length > 0 && row.every((cell) => /^:?-{3,}:?$/.test(cell)))) return null;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

const rows = parsedRows
.filter((row) => !row.every((cell) => /^:?-{3,}:?$/.test(cell)))
.filter((row) => row.length > 1 && row.some(Boolean));
if (rows.length < 2 || rows.some((row) => row.length !== rows[0].length)) return null;
Expand All @@ -22,10 +24,20 @@ function parsePipeDelimitedTable(text: string): string[][] | null {
function renderImageText(text: string) {
const rows = parsePipeDelimitedTable(text);
if (!rows) return <p>{text}</p>;
const [header, ...bodyRows] = rows;
return (
<table className="post-body-table post-image-text-table">
<thead>
<tr>
{header.map((cell, cellIndex) => (
<th key={`post-image-text-header-${cellIndex}`} scope="col">
{cell}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, rowIndex) => (
{bodyRows.map((row, rowIndex) => (
<tr key={`post-image-text-row-${rowIndex}`}>
{row.map((cell, cellIndex) => (
<td key={`post-image-text-cell-${rowIndex}-${cellIndex}`}>{cell}</td>
Expand Down Expand Up @@ -72,7 +84,14 @@ function renderImageEvidence(
<ol>
{imageContent.regions.map((region) => (
<li key={region.region_index}>
<span>{region.caption || region.extracted_text || t("Unknown")}</span>
{region.caption ? <p>{region.caption}</p> : null}
{region.extracted_text ? (
<div className="post-image-region-text">
{renderImageText(region.extracted_text)}
</div>
) : region.caption ? null : (
t("Unknown")
)}
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
{region.tags.length ? (
<small>
{t("Image tags")}: {region.tags.join(", ")}
Expand Down
Loading