From 17e9be1e9dd08c604f707f7e51259bd426713c9d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 23:21:24 +0000 Subject: [PATCH 01/13] feat: show persisted image-region locations Render each stored visual-region bounding range beside its caption, OCR, and tags so buyers can locate evidence on the source image (ADR 0133). Hide internal LLM instructions and omit non-finite boxes. --- AGENTS.md | 3 +- CHANGELOG.d/2.12.8-image-region-locations.md | 6 ++ CHANGELOG.md | 12 +++ .../0110-buyer-image-evidence-rendering.md | 3 +- docs/adr/0133-buyer-image-region-locations.md | 42 +++++++++++ docs/product-technical-gap-baseline.md | 17 +++-- docs/storybook-inventory.md | 1 + frontend/package.json | 2 +- frontend/src/App.css | 7 ++ frontend/src/PostBody.stories.tsx | 63 ++++++++++++++++ frontend/src/PostBody.test.tsx | 70 ++++++++++++++++++ frontend/src/PostBody.tsx | 37 ++++++--- .../fixtures/synthetic-process-diagram.png | Bin 0 -> 6363 bytes frontend/src/i18n.test.ts | 12 +++ frontend/src/i18n.ts | 4 + frontend/src/vite-env.d.ts | 5 ++ lineageweave/__init__.py | 2 +- pyproject.toml | 2 +- uv.lock | 2 +- 19 files changed, 267 insertions(+), 23 deletions(-) create mode 100644 CHANGELOG.d/2.12.8-image-region-locations.md create mode 100644 docs/adr/0133-buyer-image-region-locations.md create mode 100644 frontend/src/PostBody.stories.tsx create mode 100644 frontend/src/fixtures/synthetic-process-diagram.png diff --git a/AGENTS.md b/AGENTS.md index 1728f9e61..97b62724f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -126,7 +126,8 @@ contextual-orchestrator owns model discovery and selection. flatten transparent pixels onto white for the derived analysis image while retaining the original asset and provenance. Recognize image DOM/visual regions before OCR, descriptions, Keyman extraction, or embeddings. Store - region-level evidence; never show an internal LLM instruction such as + region-level evidence and show each region's bounding range beside its + caption and OCR (ADR 0133); never show an internal LLM instruction such as `This post is an image` to a buyer. ## Source parsing and semantic units diff --git a/CHANGELOG.d/2.12.8-image-region-locations.md b/CHANGELOG.d/2.12.8-image-region-locations.md new file mode 100644 index 000000000..73c6bbb8a --- /dev/null +++ b/CHANGELOG.d/2.12.8-image-region-locations.md @@ -0,0 +1,6 @@ +## 2.12.8 — Buyer-visible image region locations + +- Show persisted image-region bounding ranges beside captions, OCR, and tags. +- Keep the existing buyer-safe image evidence boundary and hide internal LLM + guidance. +- Add the location label to the existing five-locale catalog. diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ed1a099..76de7bace 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,18 @@ All notable changes to this project are documented here. Format follows environment, so local OIDC and synthetic-data workflows resolve the same pinned dependencies as CI. +## [2.12.8] - 2026-08-22 + +### Added + +- Opening a post with persisted image-region evidence now shows each region's + bounding range beside its caption, OCR, and tags (ADR 0133). After + `make seed`, a synthetic process-diagram region reads **Region location: + 0%, 0% – 100%, 100%**. Internal LLM instructions stay hidden. Click the + region list to compare that box with the caption, then read the source + image. + + ## [2.12.6] - 2026-08-20 ### Added diff --git a/docs/adr/0110-buyer-image-evidence-rendering.md b/docs/adr/0110-buyer-image-evidence-rendering.md index e6aa26aea..a2f47cde7 100644 --- a/docs/adr/0110-buyer-image-evidence-rendering.md +++ b/docs/adr/0110-buyer-image-evidence-rendering.md @@ -33,4 +33,5 @@ placeholder and is not buyer content. Buyers see useful image evidence without seeing instructions intended for an LLM. Search and embedding artifacts remain backed by the existing normalized -parent/region tables and contextual-orchestrator boundary. +parent/region tables and contextual-orchestrator boundary. Buyer-visible +region bounding ranges are specified in [0133](0133-buyer-image-region-locations.md). diff --git a/docs/adr/0133-buyer-image-region-locations.md b/docs/adr/0133-buyer-image-region-locations.md new file mode 100644 index 000000000..c42df237f --- /dev/null +++ b/docs/adr/0133-buyer-image-region-locations.md @@ -0,0 +1,42 @@ +# ADR 0133: Show persisted image-region locations to buyers + +- Status: Accepted +- Date: 2026-08-22 +- Depends on: [0067](0067-visual-region-vision-agent.md), [0091](0091-visual-region-embedding-persistence.md), [0110](0110-buyer-image-evidence-rendering.md) + +## Context + +Normalized visual-region coordinates (`x_ratio`, `y_ratio`, `width_ratio`, +`height_ratio`) already persist beside captions, OCR, and tags. The buyer +popup listed those regions as unlabeled evidence, so an operator could not +tell where on the source image a caption or OCR excerpt came from. Cai, Yu, +Wen, and Ma (2003) treat a visual block's bounding box as first-class +evidence; hiding the box after extraction made the stored coordinates +unusable at the product boundary. + +Stacked PR work already rendered these locations on a non-main branch. This +record lands the same buyer action on protected `main` without mixing into +unrelated stacks. + +## Decision + +- Render each persisted region's axis-aligned box as a percent range next to + its caption or OCR excerpt: `Region location: left%, top% – right%, bottom%`. +- Translate the label through the five-locale UI catalog. +- Omit the location row when any coordinate is non-finite. Do not invent a + box, a pixel overlay, or an internal LLM instruction. +- Keep image tags, OCR tables, and the source raster on their existing + contracts. This is a presentation boundary, not a new VISION call. + +## Consequences + +After opening a post whose image analysis produced regions, a buyer can read +where each caption or OCR excerpt sits. Internal prompts such as `This post +is an image` remain hidden. Complete overlay rendering and authorized-corpus +quality remain later slices. + +## References + +Cai, D., Yu, S., Wen, J.-R., & Ma, W.-Y. (2003). *VIPS: A vision-based page +segmentation algorithm* (Microsoft Research Technical Report MSR-TR-2003-79). +Microsoft Research. https://www.microsoft.com/en-us/research/publication/vips-a-vision-based-page-segmentation-algorithm/ diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index e65883463..86215b562 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,12 +1,17 @@ # Product & Technical Gap Baseline +> Repository artifacts contain synthetic fixtures and derived, non-identifying +> evidence only. Real PostgreSQL rows, source payloads, images, names, and +> identifiers remain in a protected external runtime and are never copied into +> this repository, screenshots, tests, logs, or buyer evidence. + ## 1. Known Parsing & Frontend Display Gaps -- **Footnote Parsing**: `post=00505695-3e61-1fd1-83c5-263f88a9e77a` fails to recognize footnotes (li/oi level errors). -- **Table Parsing**: `post=00505695-3e61-1fd1-80c6-86bb61c8ddc5` completely fails at parsing tables. -- **Indentation**: Incorrect indentation rendering in `post=00505695-7571-1fd1-83c3-d521b187ad5b` and `post=00505695-3e61-1fd1-83c0-497b3c1c455e`. -- **Image/Table OCR**: `post=00505695-7571-1fd1-83dd-3d22a61a5734` fails text recognition for tables inside images, markdown parsing fails, and image OCR description is too shallow for Ontology & Semantics. -- **Math/Superscripts**: `post=00505695-9612-1fe1-83a7-e30153323f25` fails to parse superscripts like m^3 properly. Needs strict Ontology grammar for math formulas. -- **Missing UI Elements**: DAG (Directed Acyclic Graph) view is currently missing from the frontend for `post=00505695-7571-1fd1-83c5-895ed333cdbc`. +- **Footnote Parsing**: synthetic case `case-footnote-01` exercises numbered footnote recognition; authorized production/browser corpus evidence remains pending. +- **Table Parsing**: synthetic case `case-table-01` exercises malformed row boundaries and empty cells; region-aware image tables and protected-corpus evidence remain open. +- **Indentation**: synthetic cases `case-indent-01` and `case-indent-02` retain corpus coverage gaps; authorized production/browser evidence remains open. +- **Image/Table OCR**: synthetic case `case-image-table-01` now shows buyer-visible normalized region locations through v2.12.8 / ADR 0133; authorized production/browser evidence and complete image-region overlay coverage remain open. +- **Math/Superscripts**: synthetic case `case-math-01` covers bounded metric normalization such as m³; arbitrary formula semantics and authorized runtime verification remain open. +- **Missing UI Elements**: synthetic case `case-dag-01` tracks the Event Lineage DAG surface; current source includes the DAG, but corpus coverage and browser evidence remain open. ## 2. LLM Extraction & Knowledge Graph Gaps - **Multiple Project Extraction**: (Resolved) LLM prompt updated to request key_events as objects with project_name, separating events correctly. diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index 28c59bd48..1014fd5b6 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -6,6 +6,7 @@ buyer-facing control you can click before changing product CSS. | Story | Buyer next action | Token / module | |---|---|---| | `Evidence/CitationChip` | Click a cited title to open that source post. | `--color-chip-border`, `--radius-chip`, `CitationChip` | +| `Evidence/PostBody` | Open Image regions and read each bounding range beside its caption. | `--text-muted`, `PostBody` | | `AnalysisRun/CutoffKnownBody` | Read the cutoff-known sentence, then compare it with the live body below. | `--color-accent-border`, `--space-panel-block`, `--radius-panel`, `CutoffKnownBody` | | `Analysis/LineageEntityPicker` | Choose which corp to reconstruct, then click Request a lineage reconstruction. | `--space-control-gap`, `--size-control-min`, `--radius-control`, `LineageEntityPicker` | | `Chrome/PopupCloseButton` | Close the evidence panel or post popup. | `--space-close-inset`, `--font-size-close`, `PopupCloseButton` | diff --git a/frontend/package.json b/frontend/package.json index e2e996bbe..2e6f4ed8f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "2.12.6", + "version": "2.12.8", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.css b/frontend/src/App.css index c72aab078..98861b8da 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -370,6 +370,13 @@ color: var(--text); } +.post-image-region-location { + display: block; + margin-top: 0.2rem; + font-size: 0.8rem; + color: var(--text-muted); +} + .popup-placeholder { margin-top: 1.5rem; padding: 1rem; diff --git a/frontend/src/PostBody.stories.tsx b/frontend/src/PostBody.stories.tsx new file mode 100644 index 000000000..4307d028d --- /dev/null +++ b/frontend/src/PostBody.stories.tsx @@ -0,0 +1,63 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { PostBody } from "./PostBody"; +import diagramSrc from "./fixtures/synthetic-process-diagram.png?inline"; + +const meta = { + title: "Evidence/PostBody", + component: PostBody, +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const ImageRegionLocations: Story = { + args: { + body: ``, + imageContent: [ + { + unit_index: 0, + mime_type: "image/png", + status_code: "described", + extracted_text: "Demo Corp process diagram", + caption: "A process diagram", + tags: ["diagram", "process"], + regions: [ + { + region_index: 0, + x_ratio: 0.1, + y_ratio: 0.2, + width_ratio: 0.3, + height_ratio: 0.4, + status_code: "described", + extracted_text: "Title block", + caption: "Title block", + tags: ["title"], + }, + { + region_index: 1, + x_ratio: 0.5, + y_ratio: 0.18, + width_ratio: 0.4, + height_ratio: 0.28, + status_code: "described", + extracted_text: "1. Capture 2. Fuse 3. Land", + caption: "Process steps", + tags: ["steps"], + }, + { + region_index: 2, + x_ratio: 0.5, + y_ratio: 0.55, + width_ratio: 0.4, + height_ratio: 0.3, + status_code: "described", + extracted_text: "Synthetic fixture only", + caption: "Notes", + tags: ["notes"], + }, + ], + }, + ], + }, +}; diff --git a/frontend/src/PostBody.test.tsx b/frontend/src/PostBody.test.tsx index 1a9b00c4a..fa1030787 100644 --- a/frontend/src/PostBody.test.tsx +++ b/frontend/src/PostBody.test.tsx @@ -270,6 +270,7 @@ describe("PostBody", () => { expect(screen.getByText("A process diagram")).toBeInTheDocument(); expect(screen.getByText("diagram, process")).toBeInTheDocument(); expect(screen.getByText("Main panel")).toBeInTheDocument(); + expect(screen.getByText("Region location: 0%, 0% – 100%, 100%")).toBeInTheDocument(); expect(screen.queryByText(/This post is an image/)).not.toBeInTheDocument(); }); @@ -295,6 +296,75 @@ describe("PostBody", () => { expect(screen.getByText("Panel")).toBeInTheDocument(); }); + it("shows persisted image-region bounding ranges beside captions", () => { + render( + '} + imageContent={[ + { + unit_index: 0, + mime_type: "image/png", + status_code: "described", + extracted_text: "Visible OCR", + caption: "A process diagram", + tags: [], + regions: [ + { + region_index: 0, + x_ratio: 0.1, + y_ratio: 0.2, + width_ratio: 0.3, + height_ratio: 0.4, + status_code: "described", + extracted_text: "Region OCR", + caption: "Title block", + tags: ["title"], + }, + ], + }, + ]} + />, + ); + + expect(screen.getByText("Title block")).toBeInTheDocument(); + expect(screen.getByText("Region location: 10%, 20% – 40%, 60%")).toBeInTheDocument(); + expect(screen.queryByText(/This post is an image/)).not.toBeInTheDocument(); + }); + + it("omits the location row when a region coordinate is non-finite", () => { + render( + '} + imageContent={[ + { + unit_index: 0, + mime_type: "image/png", + status_code: "described", + extracted_text: null, + caption: "A process diagram", + tags: [], + regions: [ + { + region_index: 0, + x_ratio: Number.NaN, + y_ratio: 0.2, + width_ratio: 0.3, + height_ratio: 0.4, + status_code: "described", + extracted_text: "Region OCR", + caption: "Broken box", + tags: [], + }, + ], + }, + ]} + />, + ); + + expect(screen.getByText("Broken box")).toBeInTheDocument(); + expect(screen.queryByText(/Region location/)).not.toBeInTheDocument(); + }); + it("keeps source-image placement while showing persisted OCR and caption evidence", () => { render( !Number.isFinite(value))) return null; + const percent = (value: number) => `${Math.round(value * 100)}%`; + const right = region.x_ratio + region.width_ratio; + const bottom = region.y_ratio + region.height_ratio; + return `${t("Region location")}: ${percent(region.x_ratio)}, ${percent(region.y_ratio)} – ${percent(right)}, ${percent(bottom)}`; +} + function renderImageEvidence( index: number, imageContent?: PostImageContent, @@ -70,16 +79,22 @@ function renderImageEvidence(
{t("Image regions")}
    - {imageContent.regions.map((region) => ( -
  1. - {region.caption || region.extracted_text || t("Unknown")} - {region.tags.length ? ( - - {t("Image tags")}: {region.tags.join(", ")} - - ) : null} -
  2. - ))} + {imageContent.regions.map((region) => { + const location = formatImageRegionLocation(region); + return ( +
  3. + {region.caption || region.extracted_text || t("Unknown")} + {region.tags.length ? ( + + {t("Image tags")}: {region.tags.join(", ")} + + ) : null} + {location ? ( + {location} + ) : null} +
  4. + ); + })}
) : null} diff --git a/frontend/src/fixtures/synthetic-process-diagram.png b/frontend/src/fixtures/synthetic-process-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..28ea9acc5d1073630de67aa0b451559387d3a78d GIT binary patch literal 6363 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 literal 0 HcmV?d00001 diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts index 5a3afbfc3..7ba0a0923 100644 --- a/frontend/src/i18n.test.ts +++ b/frontend/src/i18n.test.ts @@ -23,6 +23,8 @@ describe("i18n", () => { "Keymen", "Unknown", "Image tags", + "Image regions", + "Region location", "Counterparties", "due", "Activity", @@ -74,6 +76,16 @@ describe("i18n", () => { setLocale(locale); expect(tf("{post} is current in Event Lineage. Read Keyman and evaluation next.", { post: "DEMO" })).toBe(expected); }); + + it.each([ + ["ko", "영역 위치"], + ["zh", "区域位置"], + ["ja", "領域の位置"], + ["vi", "Vị trí vùng"], + ] as const)("translates image region location in %s", (locale, expected) => { + setLocale(locale); + expect(t("Region location")).toBe(expected); + }); }); describe("locale-aware source labels", () => { diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 650acfca8..689d9f300 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -275,6 +275,7 @@ const TRANSLATIONS: Partial>> = { "Embedded image": "삽입 이미지", "Text detected in image": "이미지에서 인식된 텍스트", "Image regions": "이미지 영역", + "Region location": "영역 위치", "Image tags": "이미지 태그", "Embedded image could not be decoded. Re-export the source post and open it again.": "첨부 이미지를 해독할 수 없습니다. 원문을 다시 내보내고 다시 여세요.", @@ -613,6 +614,7 @@ const TRANSLATIONS: Partial>> = { "Embedded image": "嵌入图像", "Text detected in image": "图像中识别的文字", "Image regions": "图像区域", + "Region location": "区域位置", "Image tags": "图像标签", "Embedded image could not be decoded. Re-export the source post and open it again.": "无法解码嵌入图像。请重新导出原始文章后再打开。", @@ -966,6 +968,7 @@ const TRANSLATIONS: Partial>> = { "Embedded image": "埋め込み画像", "Text detected in image": "画像から認識されたテキスト", "Image regions": "画像領域", + "Region location": "領域の位置", "Image tags": "画像タグ", "Embedded image could not be decoded. Re-export the source post and open it again.": "埋め込み画像をデコードできませんでした。原文を再エクスポートして、もう一度開いてください。", @@ -1304,6 +1307,7 @@ const TRANSLATIONS: Partial>> = { "Embedded image": "Hình ảnh nhúng", "Text detected in image": "Văn bản nhận dạng trong hình ảnh", "Image regions": "Các vùng trong hình ảnh", + "Region location": "Vị trí vùng", "Image tags": "Thẻ hình ảnh", "Embedded image could not be decoded. Re-export the source post and open it again.": "Không thể giải mã hình ảnh nhúng. Hãy xuất lại bài viết gốc rồi mở lại.", diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index bb17fb571..b218e965f 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -1,5 +1,10 @@ /// +declare module "*.png?inline" { + const src: string; + export default src; +} + interface ImportMetaEnv { readonly VITE_KEYVERSE_ISSUER?: string; readonly VITE_KEYVERSE_CLIENT_ID?: string; diff --git a/lineageweave/__init__.py b/lineageweave/__init__.py index 95330cb50..b7084b559 100644 --- a/lineageweave/__init__.py +++ b/lineageweave/__init__.py @@ -55,4 +55,4 @@ "sentence_excerpts", ] -__version__ = "2.12.6" +__version__ = "2.12.8" diff --git a/pyproject.toml b/pyproject.toml index cb4be2916..e9d4b8f1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "lineageweave" -version = "2.12.6" +version = "2.12.8" description = "Reconstructs git-branch-style lineage DAGs from scattered short records using multi-channel score fusion and LLM adjudication." readme = "README.md" license = { text = "MIT" } diff --git a/uv.lock b/uv.lock index 10bcf9ff1..75905753b 100644 --- a/uv.lock +++ b/uv.lock @@ -454,7 +454,7 @@ wheels = [ [[package]] name = "lineageweave" -version = "2.12.6" +version = "2.12.8" source = { editable = "." } dependencies = [ { name = "certifi" }, From 04bdb18e21f1cb58e005af1a37dcd525f61fe7d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 23:24:08 +0000 Subject: [PATCH 02/13] fix: open image-region locations and separate tag rows Keep the region list expanded so the bounding range is the next readable action, and put captions, tags, and locations on separate lines. --- frontend/src/App.css | 9 +++++++++ frontend/src/PostBody.test.tsx | 1 + frontend/src/PostBody.tsx | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index 98861b8da..3ad9b4a53 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -370,6 +370,15 @@ color: var(--text); } +.post-image-regions li { + margin: 0.45rem 0; +} + +.post-image-regions li > span, +.post-image-regions li > small { + display: block; +} + .post-image-region-location { display: block; margin-top: 0.2rem; diff --git a/frontend/src/PostBody.test.tsx b/frontend/src/PostBody.test.tsx index fa1030787..d9826da27 100644 --- a/frontend/src/PostBody.test.tsx +++ b/frontend/src/PostBody.test.tsx @@ -328,6 +328,7 @@ describe("PostBody", () => { expect(screen.getByText("Title block")).toBeInTheDocument(); expect(screen.getByText("Region location: 10%, 20% – 40%, 60%")).toBeInTheDocument(); + expect(screen.getByText("Image regions").closest("details")).toHaveAttribute("open"); expect(screen.queryByText(/This post is an image/)).not.toBeInTheDocument(); }); diff --git a/frontend/src/PostBody.tsx b/frontend/src/PostBody.tsx index e5e15552d..aebd8f742 100644 --- a/frontend/src/PostBody.tsx +++ b/frontend/src/PostBody.tsx @@ -76,7 +76,7 @@ function renderImageEvidence( ) : null} {imageContent?.regions?.length ? ( -
+
{t("Image regions")}
    {imageContent.regions.map((region) => { From 3528ab48459a0533803f96426b0c47dc66ab4424 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 22 Aug 2026 08:32:25 +0900 Subject: [PATCH 03/13] fix: validate image evidence and restore frontend build --- frontend/src/App.tsx | 4 +- frontend/src/PostBody.test.tsx | 73 +++++++++++++++++++--------------- frontend/src/PostBody.tsx | 16 ++++++-- 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6fba0dd41..1b5b351ab 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4610,7 +4610,8 @@ export default function App({ showLabPanels = false }: { showLabPanels?: boolean
    - {destination === "admin" ? : null}