- {t("Source author evidence")} · {t("Hint only")}
+ {t("Author context")} · {t("Hint only")}
{sourceAuthorContext.source_author_name || sourceAuthorContext.source_author_code || t("Unknown")}
@@ -2166,8 +2166,8 @@ function PostDetailPopup({
post.source_project_name ||
post.source_system_code ||
post.source_record_key) && (
-
- {t("Original source state")}
+
+ {t("Earlier source version")}
{post.source_stage_code ? (
<>
@@ -4734,7 +4734,7 @@ function CustomerMasterPanel({
) : null}
{master && master.source_author_hints.length > 0 ? (
- {t("Source author evidence")}
+ {t("Author context")}
{master.source_author_hints.length > HINT_RENDER_LIMIT && (
{tf("Showing the first {shown} of {total} observed source authors, ranked by post count.", {
diff --git a/frontend/src/components/OntologyExplorer.test.tsx b/frontend/src/components/OntologyExplorer.test.tsx
index 4ca22ac4f..ad25d69e1 100644
--- a/frontend/src/components/OntologyExplorer.test.tsx
+++ b/frontend/src/components/OntologyExplorer.test.tsx
@@ -161,7 +161,7 @@ describe("OntologyExplorer", () => {
expect(await screen.findByText("Loading ontology neighborhood...")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Select node: Post Demo public post" })).toBeInTheDocument();
rejectContinuation(new BackendError("/api/ontology/neighborhood", 500));
- expect(await screen.findByText("Ontology neighborhood is unavailable. Open a visible post next.")).toBeInTheDocument();
+ expect(await screen.findByText("Related concepts are unavailable. Open a visible post next.")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Select node: Post Demo public post" })).toBeInTheDocument();
expect(screen.getByRole("heading", { name: "Demo public post" })).toBeInTheDocument();
expect(fetchNeighborhood).toHaveBeenNthCalledWith(
@@ -188,10 +188,10 @@ describe("OntologyExplorer", () => {
);
await userEvent.click(await screen.findByRole("button", { name: "Load next relation page" }));
- expect(await screen.findByText("Ontology neighborhood is unavailable. Open a visible post next.")).toBeInTheDocument();
+ expect(await screen.findByText("Related concepts are unavailable. Open a visible post next.")).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Load next relation page" }));
await waitFor(() => expect(fetchNeighborhood).toHaveBeenCalledTimes(3));
- expect(screen.queryByText("Ontology neighborhood is unavailable. Open a visible post next.")).not.toBeInTheDocument();
+ expect(screen.queryByText("Related concepts are unavailable. Open a visible post next.")).not.toBeInTheDocument();
expect(fetchNeighborhood).toHaveBeenNthCalledWith(
3,
"synthetic-access-token",
diff --git a/frontend/src/components/OntologyExplorer.tsx b/frontend/src/components/OntologyExplorer.tsx
index cd99d97b8..41f8cdf50 100644
--- a/frontend/src/components/OntologyExplorer.tsx
+++ b/frontend/src/components/OntologyExplorer.tsx
@@ -185,10 +185,10 @@ export function OntologyExplorer({
}
return (
-
+
-
{t("Ontology neighborhood")}
+
{t("Related concepts")}
{t("Typed relations, not Event Lineage")}
{t("This is an ontology neighborhood, not Event Lineage.")}{" "}
@@ -325,7 +325,7 @@ function statusMessage(
denied: t("Access denied for this ontology neighborhood. Open a visible post next."),
stale: t("This neighborhood is bound to a knowledge cutoff. Compare with live evidence next."),
rejected: t("Rejected proposal. Open the evidence and do not treat it as authoritative."),
- error: t("Ontology neighborhood is unavailable. Open a visible post next."),
+ error: t("Related concepts are unavailable. Open a visible post next."),
};
const text = messages[status];
if (!text) return null;
@@ -379,7 +379,7 @@ function OntologyGraph({
width="100%"
height={Math.max(180, layout.height)}
>
-
{t("Ontology neighborhood")}
+
{t("Related concepts")}
{layout.edges.map((edge) => {
const midX = (edge.fromX + edge.toX) / 2;
const midY = (edge.fromY + edge.toY) / 2;
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index ed971c5e1..eddfd27fc 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -112,6 +112,7 @@ const TRANSLATIONS: Partial
>> = {
"Source draft marker present": "원천 임시저장 표식 있음",
"Source deletion marker present": "원천 삭제 표식 있음",
"Original source state": "원천 상태",
+ "Earlier source version": "이전 원문 버전",
"Source system": "원천 시스템",
"Source record key": "원천 레코드 키",
"Source stage": "원천 단계상태",
@@ -172,6 +173,7 @@ const TRANSLATIONS: Partial>> = {
"Weak source hint": "신뢰도가 낮은 원본 힌트",
"Source hint": "원본 힌트",
"Source author evidence": "원본 작성자 증거",
+ "Author context": "작성자 맥락",
"Hint only": "힌트 전용",
"Authorization context": "권한 계정 맥락",
"Our-side Keymen hints": "우리측 Keymen 힌트",
@@ -439,6 +441,7 @@ const TRANSLATIONS: Partial>> = {
"대화형 질문을 지금 사용할 수 없습니다. 저장된 근거는 계속 확인할 수 있습니다.",
"Inspect ontology neighborhood": "온톨로지 이웃 검사",
"Ontology neighborhood": "온톨로지 이웃",
+ "Related concepts": "관련 개념",
Post: "글",
"Typed relations, not Event Lineage": "유형 관계이며 이벤트 계보가 아닙니다",
"This is an ontology neighborhood, not Event Lineage.":
@@ -458,6 +461,8 @@ const TRANSLATIONS: Partial>> = {
"거절된 제안입니다. 근거를 열고 권위 있는 사실로 다루지 마세요.",
"Ontology neighborhood is unavailable. Open a visible post next.":
"온톨로지 이웃을 사용할 수 없습니다. 다음으로 볼 수 있는 글을 여세요.",
+ "Related concepts are unavailable. Open a visible post next.":
+ "관련 개념을 사용할 수 없습니다. 다음으로 볼 수 있는 글을 여세요.",
"Search within this neighborhood": "이 이웃에서 검색",
"Reset focus": "초점 재설정",
"Export CSV": "CSV 내보내기",
@@ -622,6 +627,7 @@ const TRANSLATIONS: Partial>> = {
"Source draft marker present": "存在来源草稿标记",
"Source deletion marker present": "存在来源删除标记",
"Original source state": "原始来源状态",
+ "Earlier source version": "较早的原文版本",
"Source system": "来源系统",
"Source record key": "来源记录键",
"Source stage": "来源阶段状态",
@@ -682,6 +688,7 @@ const TRANSLATIONS: Partial>> = {
"Weak source hint": "低可信源提示",
"Source hint": "源提示",
"Source author evidence": "源作者证据",
+ "Author context": "作者背景",
"Hint only": "仅作提示",
"Authorization context": "授权账户上下文",
"Our-side Keymen hints": "我方关键联系人提示",
@@ -942,6 +949,7 @@ const TRANSLATIONS: Partial>> = {
"交互式提问暂不可用;已保存的证据仍可查看。",
"Inspect ontology neighborhood": "检查本体邻域",
"Ontology neighborhood": "本体邻域",
+ "Related concepts": "相关概念",
Post: "文章",
"Typed relations, not Event Lineage": "这是类型化关系,不是事件谱系",
"This is an ontology neighborhood, not Event Lineage.": "这是本体邻域,不是事件谱系。",
@@ -960,6 +968,8 @@ const TRANSLATIONS: Partial>> = {
"提案已被拒绝。打开证据,不要将其视为权威事实。",
"Ontology neighborhood is unavailable. Open a visible post next.":
"无法使用本体邻域。接下来打开一篇可见文章。",
+ "Related concepts are unavailable. Open a visible post next.":
+ "相关概念不可用。接下来打开一篇可见文章。",
"Search within this neighborhood": "在此邻域中搜索",
"Reset focus": "重置焦点",
"Export CSV": "导出 CSV",
@@ -1148,6 +1158,7 @@ const TRANSLATIONS: Partial>> = {
"Source draft marker present": "原典に下書き標識あり",
"Source deletion marker present": "原典に削除標識あり",
"Original source state": "原典の状態",
+ "Earlier source version": "以前の原文バージョン",
"Source system": "原典システム",
"Source record key": "原典レコードキー",
"Source stage": "原典の段階状態",
@@ -1208,6 +1219,7 @@ const TRANSLATIONS: Partial>> = {
"Weak source hint": "信頼度の低いソースヒント",
"Source hint": "ソースヒント",
"Source author evidence": "ソース作成者の証拠",
+ "Author context": "作成者の背景",
"Hint only": "ヒントのみ",
"Authorization context": "認証アカウントのコンテキスト",
"Our-side Keymen hints": "自社側キーパーソンのヒント",
@@ -1446,6 +1458,7 @@ const TRANSLATIONS: Partial>> = {
"対話形式の質問は現在利用できません。保存された証拠は確認できます。",
"Inspect ontology neighborhood": "オントロジー近傍を調べる",
"Ontology neighborhood": "オントロジー近傍",
+ "Related concepts": "関連する概念",
Post: "投稿",
"Typed relations, not Event Lineage": "型付き関係であり、イベント系譜ではありません",
"This is an ontology neighborhood, not Event Lineage.":
@@ -1465,6 +1478,8 @@ const TRANSLATIONS: Partial>> = {
"却下された提案です。証拠を開き、権威ある事実として扱わないでください。",
"Ontology neighborhood is unavailable. Open a visible post next.":
"オントロジー近傍を利用できません。次に表示可能な投稿を開いてください。",
+ "Related concepts are unavailable. Open a visible post next.":
+ "関連する概念を利用できません。次に表示可能な投稿を開いてください。",
"Search within this neighborhood": "この近傍内を検索",
"Reset focus": "焦点をリセット",
"Export CSV": "CSV を書き出す",
@@ -1653,6 +1668,7 @@ const TRANSLATIONS: Partial>> = {
"Source draft marker present": "Có dấu hiệu bản nháp nguồn",
"Source deletion marker present": "Có dấu hiệu xóa nguồn",
"Original source state": "Trạng thái nguồn gốc",
+ "Earlier source version": "Phiên bản nội dung trước",
"Source system": "Hệ thống nguồn",
"Source record key": "Khóa bản ghi nguồn",
"Source stage": "Trạng thái giai đoạn nguồn",
@@ -1713,6 +1729,7 @@ const TRANSLATIONS: Partial>> = {
"Weak source hint": "Gợi ý nguồn có độ tin cậy thấp",
"Source hint": "Gợi ý nguồn",
"Source author evidence": "Bằng chứng tác giả nguồn",
+ "Author context": "Bối cảnh tác giả",
"Hint only": "Chỉ là gợi ý",
"Authorization context": "Ngữ cảnh tài khoản quyền hạn",
"Our-side Keymen hints": "Gợi ý người liên hệ chính phía chúng ta",
@@ -1951,6 +1968,7 @@ const TRANSLATIONS: Partial>> = {
"Câu hỏi tương tác hiện không khả dụng; bằng chứng đã lưu vẫn có thể xem.",
"Inspect ontology neighborhood": "Kiểm tra lân cận bản thể",
"Ontology neighborhood": "Lân cận bản thể",
+ "Related concepts": "Các khái niệm liên quan",
Post: "Bài viết",
"Typed relations, not Event Lineage": "Quan hệ có kiểu, không phải Dòng sự kiện",
"This is an ontology neighborhood, not Event Lineage.":
@@ -1970,6 +1988,8 @@ const TRANSLATIONS: Partial>> = {
"Đề xuất đã bị từ chối. Mở bằng chứng và đừng coi đó là sự thật có thẩm quyền.",
"Ontology neighborhood is unavailable. Open a visible post next.":
"Không dùng được lân cận bản thể. Hãy mở một bài viết hiển thị tiếp theo.",
+ "Related concepts are unavailable. Open a visible post next.":
+ "Các khái niệm liên quan không khả dụng. Hãy mở một bài viết hiển thị tiếp theo.",
"Search within this neighborhood": "Tìm trong lân cận này",
"Reset focus": "Đặt lại tiêu điểm",
"Export CSV": "Xuất CSV",
From 4e41bcd14db56f7867bff0d422df78719f0144b5 Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 10:25:01 +0900
Subject: [PATCH 05/21] test(ui): assert complete ranking guidance
---
frontend/src/App.test.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index cf5bce93d..51fe6fc4b 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -3200,7 +3200,7 @@ describe("App, authenticated", () => {
expect(rankingButton).toHaveTextContent("rank 1");
expect(
screen.getByText(
- "Open a result to review the records most relevant to your search.",
+ "Open a result to review the records most relevant to your search. These results are not calibrated measurements.",
),
).toBeInTheDocument();
expect(
From be34c335bc2b515e76915a491fa1351e4c629442 Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 10:36:47 +0900
Subject: [PATCH 06/21] fix(ui): describe source record details accurately
---
frontend/src/App.tsx | 6 +++---
frontend/src/i18n.ts | 16 ++++++++--------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index b4b527670..da0d2fc8d 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -2166,8 +2166,8 @@ function PostDetailPopup({
post.source_project_name ||
post.source_system_code ||
post.source_record_key) && (
-
- {t("Earlier source version")}
+
+ {t("Source record details")}
{post.source_stage_code ? (
<>
@@ -2278,7 +2278,7 @@ function PostDetailPopup({
>
) : null}
- {t("Raw source codes are shown; no state label was inferred.")}
+ {t("Use these details to compare this record in its original system.")}
)}
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index eddfd27fc..1a4a1a860 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -112,7 +112,7 @@ const TRANSLATIONS: Partial>> = {
"Source draft marker present": "원천 임시저장 표식 있음",
"Source deletion marker present": "원천 삭제 표식 있음",
"Original source state": "원천 상태",
- "Earlier source version": "이전 원문 버전",
+ "Source record details": "원본 레코드 세부 정보",
"Source system": "원천 시스템",
"Source record key": "원천 레코드 키",
"Source stage": "원천 단계상태",
@@ -134,7 +134,7 @@ const TRANSLATIONS: Partial>> = {
"Source project code": "원천 프로젝트 코드",
"Source project name": "원천 프로젝트 이름",
"Business unit (PU)": "사업부(PU)",
- "Raw source codes are shown; no state label was inferred.": "원천 코드를 그대로 표시하며 상태 라벨은 추정하지 않습니다.",
+ "Use these details to compare this record in its original system.": "이 세부 정보를 사용해 원본 시스템의 레코드와 비교하세요.",
"Search and filter posts": "글 검색 및 필터",
"Search semantic evidence": "의미 기반 증거 검색",
Search: "검색",
@@ -627,7 +627,7 @@ const TRANSLATIONS: Partial>> = {
"Source draft marker present": "存在来源草稿标记",
"Source deletion marker present": "存在来源删除标记",
"Original source state": "原始来源状态",
- "Earlier source version": "较早的原文版本",
+ "Source record details": "源记录详情",
"Source system": "来源系统",
"Source record key": "来源记录键",
"Source stage": "来源阶段状态",
@@ -649,7 +649,7 @@ const TRANSLATIONS: Partial>> = {
"Source project code": "来源项目代码",
"Source project name": "来源项目名称",
"Business unit (PU)": "事业部 (PU)",
- "Raw source codes are shown; no state label was inferred.": "显示原始来源代码;未推断状态标签。",
+ "Use these details to compare this record in its original system.": "使用这些详情与原始系统中的记录进行比较。",
"Search and filter posts": "搜索和筛选文章",
"Search semantic evidence": "搜索语义证据",
Search: "搜索",
@@ -1158,7 +1158,7 @@ const TRANSLATIONS: Partial>> = {
"Source draft marker present": "原典に下書き標識あり",
"Source deletion marker present": "原典に削除標識あり",
"Original source state": "原典の状態",
- "Earlier source version": "以前の原文バージョン",
+ "Source record details": "元レコードの詳細",
"Source system": "原典システム",
"Source record key": "原典レコードキー",
"Source stage": "原典の段階状態",
@@ -1180,7 +1180,7 @@ const TRANSLATIONS: Partial>> = {
"Source project code": "原典のプロジェクトコード",
"Source project name": "原典のプロジェクト名",
"Business unit (PU)": "事業部 (PU)",
- "Raw source codes are shown; no state label was inferred.": "原典コードをそのまま表示し、状態ラベルは推定していません。",
+ "Use these details to compare this record in its original system.": "これらの詳細を使って、元のシステムのレコードと比較してください。",
"Search and filter posts": "投稿を検索・絞り込み",
"Search semantic evidence": "意味に基づく証拠を検索",
Search: "検索",
@@ -1668,7 +1668,7 @@ const TRANSLATIONS: Partial>> = {
"Source draft marker present": "Có dấu hiệu bản nháp nguồn",
"Source deletion marker present": "Có dấu hiệu xóa nguồn",
"Original source state": "Trạng thái nguồn gốc",
- "Earlier source version": "Phiên bản nội dung trước",
+ "Source record details": "Chi tiết bản ghi nguồn",
"Source system": "Hệ thống nguồn",
"Source record key": "Khóa bản ghi nguồn",
"Source stage": "Trạng thái giai đoạn nguồn",
@@ -1690,7 +1690,7 @@ const TRANSLATIONS: Partial>> = {
"Source project code": "Mã dự án nguồn",
"Source project name": "Tên dự án nguồn",
"Business unit (PU)": "Đơn vị kinh doanh (PU)",
- "Raw source codes are shown; no state label was inferred.": "Hiển thị mã nguồn gốc; không suy đoán nhãn trạng thái.",
+ "Use these details to compare this record in its original system.": "Dùng các chi tiết này để đối chiếu bản ghi trong hệ thống gốc.",
"Search and filter posts": "Tìm kiếm và lọc bài viết",
"Search semantic evidence": "Tìm kiếm bằng chứng ngữ nghĩa",
Search: "Tìm",
From cc1b779ea590bfe85548bdc68761764e4465a33a Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 10:42:37 +0900
Subject: [PATCH 07/21] fix(ui): align ranking guidance with available actions
---
frontend/src/App.test.tsx | 2 +-
frontend/src/App.tsx | 4 ++--
frontend/src/i18n.test.ts | 2 +-
frontend/src/i18n.ts | 16 ++++++++--------
4 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 51fe6fc4b..aab496a41 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -3200,7 +3200,7 @@ describe("App, authenticated", () => {
expect(rankingButton).toHaveTextContent("rank 1");
expect(
screen.getByText(
- "Open a result to review the records most relevant to your search. These results are not calibrated measurements.",
+ "Open a result to review why it appears in this ranking. These results are not calibrated measurements.",
),
).toBeInTheDocument();
expect(
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index da0d2fc8d..dc5a2cca4 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -3372,13 +3372,13 @@ function RankingsPanel({
{t("Rankings are temporarily unavailable. Try again later.")}
)}
{ranking && ranking.status === "accepted" && ranking.rankings.length === 0 && (
- {t("No ranked records are available. Try a broader search.")}
+ {t("No ranked records are available. Check again after more records are added.")}
)}
{ranking && ranking.rankings.length > 0 && (
<>
{t(
- "Open a result to review the records most relevant to your search. These results are not calibrated measurements.",
+ "Open a result to review why it appears in this ranking. These results are not calibrated measurements.",
)}
diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts
index 8fb5cf227..c985b06ae 100644
--- a/frontend/src/i18n.test.ts
+++ b/frontend/src/i18n.test.ts
@@ -73,7 +73,7 @@ describe("i18n", () => {
"This is an ontology neighborhood, not Event Lineage.",
"Rankings",
"Title overlap",
- "Open a result to review the records most relevant to your search. These results are not calibrated measurements.",
+ "Open a result to review why it appears in this ranking. These results are not calibrated measurements.",
"Workspace navigation",
"Observed calendar events",
"No observed calendar events are available.",
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index 1a4a1a860..3a314b426 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -42,8 +42,8 @@ const TRANSLATIONS: Partial>> = {
"Ranked result": "순위 결과",
"Rankings temporarily unavailable": "순위를 잠시 사용할 수 없습니다",
"Rankings are temporarily unavailable. Try again later.": "순위를 잠시 사용할 수 없습니다. 나중에 다시 시도하세요.",
- "No ranked records are available. Try a broader search.": "순위가 매겨진 기록이 없습니다. 검색 범위를 넓혀 보세요.",
- "Open a result to review the records most relevant to your search. These results are not calibrated measurements.": "검색과 가장 관련 있는 기록을 확인하려면 결과를 여세요. 이 결과는 보정된 측정값이 아닙니다.",
+ "No ranked records are available. Check again after more records are added.": "순위가 매겨진 기록이 없습니다. 기록이 더 추가된 후 다시 확인하세요.",
+ "Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "이 순위에 포함된 이유를 검토하려면 결과를 여세요. 이 결과는 보정된 측정값이 아닙니다.",
"Loading rankings...": "순위를 불러오는 중...",
"No fused rankings from RankWeave.": "RankWeave가 융합한 순위가 없습니다.",
"Ranked records": "추천 기록",
@@ -557,8 +557,8 @@ const TRANSLATIONS: Partial>> = {
"Ranked result": "排名结果",
"Rankings temporarily unavailable": "排名暂时不可用",
"Rankings are temporarily unavailable. Try again later.": "排名暂时不可用。请稍后再试。",
- "No ranked records are available. Try a broader search.": "没有可用的排名记录。请扩大搜索范围。",
- "Open a result to review the records most relevant to your search. These results are not calibrated measurements.": "打开结果以查看与搜索最相关的记录。这些结果不是校准测量值。",
+ "No ranked records are available. Check again after more records are added.": "没有可用的排名记录。添加更多记录后请再次查看。",
+ "Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "打开结果以查看它为何出现在此排名中。这些结果不是校准测量值。",
"Loading rankings...": "正在加载排名...",
"No fused rankings from RankWeave.": "RankWeave 未返回融合排名。",
"Ranked records": "推荐记录",
@@ -1088,8 +1088,8 @@ const TRANSLATIONS: Partial>> = {
"Ranked result": "ランキング結果",
"Rankings temporarily unavailable": "ランキングは一時的に利用できません",
"Rankings are temporarily unavailable. Try again later.": "ランキングは一時的に利用できません。後でもう一度お試しください。",
- "No ranked records are available. Try a broader search.": "利用できるランキング結果がありません。検索範囲を広げてください。",
- "Open a result to review the records most relevant to your search. These results are not calibrated measurements.": "検索に最も関連する記録を確認するには結果を開いてください。これは校正された測定値ではありません。",
+ "No ranked records are available. Check again after more records are added.": "利用できるランキング結果がありません。記録が追加された後でもう一度確認してください。",
+ "Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "このランキングに表示された理由を確認するには結果を開いてください。これは校正された測定値ではありません。",
"Loading rankings...": "ランキングを読み込み中...",
"No fused rankings from RankWeave.": "RankWeave の融合ランキングはありません。",
"Ranked records": "おすすめの記録",
@@ -1598,8 +1598,8 @@ const TRANSLATIONS: Partial>> = {
"Ranked result": "Kết quả xếp hạng",
"Rankings temporarily unavailable": "Xếp hạng tạm thời không khả dụng",
"Rankings are temporarily unavailable. Try again later.": "Xếp hạng tạm thời không khả dụng. Hãy thử lại sau.",
- "No ranked records are available. Try a broader search.": "Không có bản ghi được xếp hạng. Hãy mở rộng phạm vi tìm kiếm.",
- "Open a result to review the records most relevant to your search. These results are not calibrated measurements.": "Mở một kết quả để xem các bản ghi phù hợp nhất với tìm kiếm. Đây không phải là số đo đã hiệu chuẩn.",
+ "No ranked records are available. Check again after more records are added.": "Không có bản ghi được xếp hạng. Hãy kiểm tra lại sau khi có thêm bản ghi.",
+ "Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "Mở một kết quả để xem lý do nó xuất hiện trong bảng xếp hạng này. Đây không phải là số đo đã hiệu chuẩn.",
"Loading rankings...": "Đang tải xếp hạng...",
"No fused rankings from RankWeave.": "Không có xếp hạng hợp nhất từ RankWeave.",
"Ranked records": "Bản ghi được đề xuất",
From 5c246a545a339c2ea7ccd5096a8d0652b98f0914 Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 10:53:18 +0900
Subject: [PATCH 08/21] style(ui): align related-concepts heading
---
frontend/src/components/OntologyExplorer.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/components/OntologyExplorer.tsx b/frontend/src/components/OntologyExplorer.tsx
index 41f8cdf50..d618095f9 100644
--- a/frontend/src/components/OntologyExplorer.tsx
+++ b/frontend/src/components/OntologyExplorer.tsx
@@ -188,7 +188,7 @@ export function OntologyExplorer({
-
{t("Related concepts")}
+
{t("Related concepts")}
{t("Typed relations, not Event Lineage")}
{t("This is an ontology neighborhood, not Event Lineage.")}{" "}
From b6c8c46da66e28ad2b762d6b3908e6590b20b266 Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 15:40:50 +0900
Subject: [PATCH 09/21] docs(ui): align ranking copy with product boundary
---
docs/adr/0024-rankweave-fusion-fail-closed.md | 7 ++++---
.../0167-rankweave-ranking-channel-evidence.md | 7 ++++---
frontend/src/i18n.ts | 16 ----------------
3 files changed, 8 insertions(+), 22 deletions(-)
diff --git a/docs/adr/0024-rankweave-fusion-fail-closed.md b/docs/adr/0024-rankweave-fusion-fail-closed.md
index c03f73b45..4b9b839a4 100644
--- a/docs/adr/0024-rankweave-fusion-fail-closed.md
+++ b/docs/adr/0024-rankweave-fusion-fail-closed.md
@@ -32,9 +32,10 @@ tables, and does not bind the demo IdP to production Keyverse.
Samuel et al. (2025) unequal-channel weights (`temporal` 0.25,
`lexical` 0.75). The buyer sees 1-based `fused_rank` and the post
title — not a TEPP theta.
-4. After login, Rankings sits above Calendar. Unavailable copy is
- **Rankings · RankWeave not available**. An accepted hit lists the
- title; click opens that `source_post`.
+4. After login, Rankings sits above Calendar. Customer copy describes the
+ available action and result state without naming the internal ranking
+ dependency. An accepted hit lists the title; click opens that
+ `source_post`.
5. Accepted hits also disclose owned-channel evidence (ADR 0167):
1-based `channel_rank` and Cormack contribution
`weight / (η + rank)` for each channel the post actually appears
diff --git a/docs/adr/0167-rankweave-ranking-channel-evidence.md b/docs/adr/0167-rankweave-ranking-channel-evidence.md
index d7fe2e30b..7b2591aaf 100644
--- a/docs/adr/0167-rankweave-ranking-channel-evidence.md
+++ b/docs/adr/0167-rankweave-ranking-channel-evidence.md
@@ -38,9 +38,10 @@ reasons.
3. Labels: `temporal` = **Newest first**, `lexical` = **Title
overlap**. The payload never includes a fused score or a theta.
4. Rankings lists the evidence under each accepted hit as an
- accessible sibling list, not hover-only. Copy states that
- RankWeave fused newest-first and title-overlap ranks, and that
- this is not a calibrated score. Click still opens that post.
+ accessible sibling list, not hover-only. Customer copy tells the reader
+ to open a result to review its channel evidence and states that the result
+ is not a calibrated measurement; it does not name the internal ranking
+ dependency. Click still opens that post.
5. Unavailable RankWeave stays empty (`rankweave_not_available`).
Hidden posts remain omitted from every channel.
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index 3a314b426..87c44c86e 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -37,7 +37,6 @@ const TRANSLATIONS: Partial>> = {
"Log out": "로그아웃",
Calendar: "캘린더",
Rankings: "순위",
- "Rankings · RankWeave not available": "순위 · RankWeave를 사용할 수 없음",
"Rankings · rankweave": "순위 · rankweave",
"Ranked result": "순위 결과",
"Rankings temporarily unavailable": "순위를 잠시 사용할 수 없습니다",
@@ -45,13 +44,10 @@ const TRANSLATIONS: Partial>> = {
"No ranked records are available. Check again after more records are added.": "순위가 매겨진 기록이 없습니다. 기록이 더 추가된 후 다시 확인하세요.",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "이 순위에 포함된 이유를 검토하려면 결과를 여세요. 이 결과는 보정된 측정값이 아닙니다.",
"Loading rankings...": "순위를 불러오는 중...",
- "No fused rankings from RankWeave.": "RankWeave가 융합한 순위가 없습니다.",
"Ranked records": "추천 기록",
"Open ranking: {title}": "순위 열기: {title}",
"rank {rank}": "순위 {rank}",
"Title overlap": "제목 겹침",
- "RankWeave fused newest-first and title-overlap ranks. This is not a calibrated score.":
- "RankWeave가 최신순과 제목 겹침 순위를 융합했습니다. 보정된 점수가 아닙니다.",
"Ranking evidence for {title}": "{title}의 순위 근거",
"{label} rank {rank}, contribution {contribution}":
"{label} 순위 {rank}, 기여 {contribution}",
@@ -552,7 +548,6 @@ const TRANSLATIONS: Partial>> = {
"Log out": "退出登录",
Calendar: "日历",
Rankings: "排名",
- "Rankings · RankWeave not available": "排名 · RankWeave 不可用",
"Rankings · rankweave": "排名 · rankweave",
"Ranked result": "排名结果",
"Rankings temporarily unavailable": "排名暂时不可用",
@@ -560,13 +555,10 @@ const TRANSLATIONS: Partial>> = {
"No ranked records are available. Check again after more records are added.": "没有可用的排名记录。添加更多记录后请再次查看。",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "打开结果以查看它为何出现在此排名中。这些结果不是校准测量值。",
"Loading rankings...": "正在加载排名...",
- "No fused rankings from RankWeave.": "RankWeave 未返回融合排名。",
"Ranked records": "推荐记录",
"Open ranking: {title}": "打开排名:{title}",
"rank {rank}": "排名 {rank}",
"Title overlap": "标题重叠",
- "RankWeave fused newest-first and title-overlap ranks. This is not a calibrated score.":
- "RankWeave 融合了最新优先和标题重叠排名。这不是校准分数。",
"Ranking evidence for {title}": "{title} 的排名证据",
"{label} rank {rank}, contribution {contribution}":
"{label} 排名 {rank},贡献 {contribution}",
@@ -1083,7 +1075,6 @@ const TRANSLATIONS: Partial>> = {
"Log out": "ログアウト",
Calendar: "カレンダー",
Rankings: "ランキング",
- "Rankings · RankWeave not available": "ランキング · RankWeave を利用できません",
"Rankings · rankweave": "ランキング · rankweave",
"Ranked result": "ランキング結果",
"Rankings temporarily unavailable": "ランキングは一時的に利用できません",
@@ -1091,13 +1082,10 @@ const TRANSLATIONS: Partial>> = {
"No ranked records are available. Check again after more records are added.": "利用できるランキング結果がありません。記録が追加された後でもう一度確認してください。",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "このランキングに表示された理由を確認するには結果を開いてください。これは校正された測定値ではありません。",
"Loading rankings...": "ランキングを読み込み中...",
- "No fused rankings from RankWeave.": "RankWeave の融合ランキングはありません。",
"Ranked records": "おすすめの記録",
"Open ranking: {title}": "ランキングを開く: {title}",
"rank {rank}": "順位 {rank}",
"Title overlap": "タイトル一致",
- "RankWeave fused newest-first and title-overlap ranks. This is not a calibrated score.":
- "RankWeave が新しい順とタイトル一致の順位を融合しました。校正されたスコアではありません。",
"Ranking evidence for {title}": "{title} の順位根拠",
"{label} rank {rank}, contribution {contribution}":
"{label} 順位 {rank}、寄与 {contribution}",
@@ -1593,7 +1581,6 @@ const TRANSLATIONS: Partial>> = {
"Log out": "Đăng xuất",
Calendar: "Lịch",
Rankings: "Xếp hạng",
- "Rankings · RankWeave not available": "Xếp hạng · RankWeave không khả dụng",
"Rankings · rankweave": "Xếp hạng · rankweave",
"Ranked result": "Kết quả xếp hạng",
"Rankings temporarily unavailable": "Xếp hạng tạm thời không khả dụng",
@@ -1601,13 +1588,10 @@ const TRANSLATIONS: Partial>> = {
"No ranked records are available. Check again after more records are added.": "Không có bản ghi được xếp hạng. Hãy kiểm tra lại sau khi có thêm bản ghi.",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "Mở một kết quả để xem lý do nó xuất hiện trong bảng xếp hạng này. Đây không phải là số đo đã hiệu chuẩn.",
"Loading rankings...": "Đang tải xếp hạng...",
- "No fused rankings from RankWeave.": "Không có xếp hạng hợp nhất từ RankWeave.",
"Ranked records": "Bản ghi được đề xuất",
"Open ranking: {title}": "Mở xếp hạng: {title}",
"rank {rank}": "hạng {rank}",
"Title overlap": "Trùng tiêu đề",
- "RankWeave fused newest-first and title-overlap ranks. This is not a calibrated score.":
- "RankWeave đã hợp nhất hạng mới nhất trước và trùng tiêu đề. Đây không phải điểm đã hiệu chỉnh.",
"Ranking evidence for {title}": "Bằng chứng xếp hạng cho {title}",
"{label} rank {rank}, contribution {contribution}":
"{label} hạng {rank}, đóng góp {contribution}",
From cc2b7182045669207e784bd83ecdd0ba5e829b4b Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 16:23:15 +0900
Subject: [PATCH 10/21] fix(ui): remove internal ontology copy
---
frontend/src/components/OntologyExplorer.test.tsx | 2 +-
frontend/src/components/OntologyExplorer.tsx | 2 +-
frontend/src/i18n.test.ts | 2 +-
frontend/src/i18n.ts | 14 +++++++-------
4 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/frontend/src/components/OntologyExplorer.test.tsx b/frontend/src/components/OntologyExplorer.test.tsx
index ad25d69e1..c27f6eaef 100644
--- a/frontend/src/components/OntologyExplorer.test.tsx
+++ b/frontend/src/components/OntologyExplorer.test.tsx
@@ -234,7 +234,7 @@ describe("OntologyExplorer", () => {
/>,
);
expect(
- screen.getByText(/This is an ontology neighborhood, not Event Lineage/),
+ screen.getByText(/This view is separate from Event Lineage/),
).toBeInTheDocument();
expect(screen.getByRole("region", { name: "Exact values" })).toHaveAttribute("tabindex", "0");
await userEvent.click(screen.getByRole("button", { name: "Select node: Post Demo public post" }));
diff --git a/frontend/src/components/OntologyExplorer.tsx b/frontend/src/components/OntologyExplorer.tsx
index d618095f9..7cc187edc 100644
--- a/frontend/src/components/OntologyExplorer.tsx
+++ b/frontend/src/components/OntologyExplorer.tsx
@@ -191,7 +191,7 @@ export function OntologyExplorer({
{t("Related concepts")}
{t("Typed relations, not Event Lineage")}
- {t("This is an ontology neighborhood, not Event Lineage.")}{" "}
+ {t("This view is separate from Event Lineage.")}{" "}
{t("Event Lineage shows reconstructed post-to-post parents. This graph shows typed people, organizations, teams, and posts.")}
diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts
index c985b06ae..693a2ef97 100644
--- a/frontend/src/i18n.test.ts
+++ b/frontend/src/i18n.test.ts
@@ -70,7 +70,7 @@ describe("i18n", () => {
"Interval relations",
"Inspect ontology neighborhood",
"Ontology neighborhood",
- "This is an ontology neighborhood, not Event Lineage.",
+ "This view is separate from Event Lineage.",
"Rankings",
"Title overlap",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.",
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index 87c44c86e..b29cb7ce6 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -440,8 +440,8 @@ const TRANSLATIONS: Partial>> = {
"Related concepts": "관련 개념",
Post: "글",
"Typed relations, not Event Lineage": "유형 관계이며 이벤트 계보가 아닙니다",
- "This is an ontology neighborhood, not Event Lineage.":
- "이벤트 계보가 아니라 온톨로지 이웃입니다.",
+ "This view is separate from Event Lineage.":
+ "이 보기는 이벤트 계보와 구분됩니다.",
"Event Lineage shows reconstructed post-to-post parents. This graph shows typed people, organizations, teams, and posts.":
"이벤트 계보는 복원된 글-글 부모 링크입니다. 이 그래프는 유형이 있는 사람, 조직, 팀, 글을 보여 줍니다.",
"Loading ontology neighborhood...": "온톨로지 이웃을 불러오는 중...",
@@ -944,7 +944,7 @@ const TRANSLATIONS: Partial>> = {
"Related concepts": "相关概念",
Post: "文章",
"Typed relations, not Event Lineage": "这是类型化关系,不是事件谱系",
- "This is an ontology neighborhood, not Event Lineage.": "这是本体邻域,不是事件谱系。",
+ "This view is separate from Event Lineage.": "此视图与事件谱系相互独立。",
"Event Lineage shows reconstructed post-to-post parents. This graph shows typed people, organizations, teams, and posts.":
"事件谱系显示重建的文章父子链接。此图显示带类型的人员、组织、团队和文章。",
"Loading ontology neighborhood...": "正在加载本体邻域...",
@@ -1449,8 +1449,8 @@ const TRANSLATIONS: Partial>> = {
"Related concepts": "関連する概念",
Post: "投稿",
"Typed relations, not Event Lineage": "型付き関係であり、イベント系譜ではありません",
- "This is an ontology neighborhood, not Event Lineage.":
- "これはイベント系譜ではなく、オントロジー近傍です。",
+ "This view is separate from Event Lineage.":
+ "このビューはイベント系譜とは別のものです。",
"Event Lineage shows reconstructed post-to-post parents. This graph shows typed people, organizations, teams, and posts.":
"イベント系譜は復元された投稿間の親子リンクです。このグラフは型付きの人・組織・チーム・投稿を示します。",
"Loading ontology neighborhood...": "オントロジー近傍を読み込んでいます...",
@@ -1955,8 +1955,8 @@ const TRANSLATIONS: Partial>> = {
"Related concepts": "Các khái niệm liên quan",
Post: "Bài viết",
"Typed relations, not Event Lineage": "Quan hệ có kiểu, không phải Dòng sự kiện",
- "This is an ontology neighborhood, not Event Lineage.":
- "Đây là lân cận bản thể, không phải Dòng sự kiện.",
+ "This view is separate from Event Lineage.":
+ "Chế độ xem này tách biệt với Dòng sự kiện.",
"Event Lineage shows reconstructed post-to-post parents. This graph shows typed people, organizations, teams, and posts.":
"Dòng sự kiện hiện liên kết cha-con đã tái tạo giữa các bài viết. Đồ thị này hiện người, tổ chức, nhóm và bài viết có kiểu.",
"Loading ontology neighborhood...": "Đang tải lân cận bản thể...",
From 6a9a3dec572152879966bfd7067b30ffdd9221d4 Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 17:58:40 +0900
Subject: [PATCH 11/21] fix(ui): preserve product capability labels
---
frontend/src/App.css | 9 +++++
frontend/src/App.test.tsx | 2 +-
frontend/src/App.tsx | 4 +--
.../src/components/OntologyExplorer.test.tsx | 8 ++---
frontend/src/components/OntologyExplorer.tsx | 10 +++---
frontend/src/i18n.test.ts | 2 +-
frontend/src/i18n.ts | 34 ++++---------------
7 files changed, 29 insertions(+), 40 deletions(-)
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 901b17b1a..e276b6c72 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -978,6 +978,15 @@
justify-content: flex-start;
}
+.ranking-hit > button {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: var(--space-control-gap);
+ width: 100%;
+ text-align: left;
+}
+
.ranking-channel-evidence-copy {
margin: 0 0 0.75rem;
}
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index aab496a41..53c5a5694 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -2183,7 +2183,7 @@ describe("App, authenticated", () => {
"hint-disclosure",
);
- const authorSection = screen.getByRole("region", { name: "Author context" });
+ const authorSection = screen.getByRole("region", { name: "Source author evidence" });
expect(within(authorSection).getByText("AUTH-HINT · Hint only").closest("details")).toHaveClass(
"hint-disclosure",
);
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index dd6ba34cc..28b382ce9 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1232,7 +1232,7 @@ function KeymanPanel({
{error && {error}
}
{sourceAuthorContext ? (
- {t("Author context")} · {t("Hint only")}
+ {t("Source author evidence")} · {t("Hint only")}
{sourceAuthorContext.source_author_name || sourceAuthorContext.source_author_code || t("Unknown")}
@@ -4735,7 +4735,7 @@ function CustomerMasterPanel({
) : null}
{master && master.source_author_hints.length > 0 ? (
- {t("Author context")}
+ {t("Source author evidence")}
{master.source_author_hints.length > HINT_RENDER_LIMIT && (
{tf("Showing the first {shown} of {total} observed source authors, ranked by post count.", {
diff --git a/frontend/src/components/OntologyExplorer.test.tsx b/frontend/src/components/OntologyExplorer.test.tsx
index a22a2a281..da876419d 100644
--- a/frontend/src/components/OntologyExplorer.test.tsx
+++ b/frontend/src/components/OntologyExplorer.test.tsx
@@ -184,7 +184,7 @@ describe("OntologyExplorer", () => {
expect(await screen.findByText("Loading ontology neighborhood...")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Select node: Post Demo public post" })).toBeInTheDocument();
rejectContinuation(new BackendError("/api/ontology/neighborhood", 500));
- expect(await screen.findByText("Related concepts are unavailable. Open a visible post next.")).toBeInTheDocument();
+ expect(await screen.findByText("Ontology neighborhood is unavailable. Open a visible post next.")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Select node: Post Demo public post" })).toBeInTheDocument();
expect(screen.getByRole("heading", { name: "Demo public post" })).toBeInTheDocument();
expect(fetchNeighborhood).toHaveBeenNthCalledWith(
@@ -211,10 +211,10 @@ describe("OntologyExplorer", () => {
);
await userEvent.click(await screen.findByRole("button", { name: "Load next relation page" }));
- expect(await screen.findByText("Related concepts are unavailable. Open a visible post next.")).toBeInTheDocument();
+ expect(await screen.findByText("Ontology neighborhood is unavailable. Open a visible post next.")).toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "Load next relation page" }));
await waitFor(() => expect(fetchNeighborhood).toHaveBeenCalledTimes(3));
- expect(screen.queryByText("Related concepts are unavailable. Open a visible post next.")).not.toBeInTheDocument();
+ expect(screen.queryByText("Ontology neighborhood is unavailable. Open a visible post next.")).not.toBeInTheDocument();
expect(fetchNeighborhood).toHaveBeenNthCalledWith(
3,
"synthetic-access-token",
@@ -257,7 +257,7 @@ describe("OntologyExplorer", () => {
/>,
);
expect(
- screen.getByText(/This view is separate from Event Lineage/),
+ screen.getByText(/This is an ontology neighborhood, not Event Lineage/),
).toBeInTheDocument();
expect(screen.getByRole("columnheader", { name: "Valid from" })).toBeInTheDocument();
expect(screen.getByRole("columnheader", { name: "Valid to" })).toBeInTheDocument();
diff --git a/frontend/src/components/OntologyExplorer.tsx b/frontend/src/components/OntologyExplorer.tsx
index ad779fa2b..f5c6dbc99 100644
--- a/frontend/src/components/OntologyExplorer.tsx
+++ b/frontend/src/components/OntologyExplorer.tsx
@@ -193,13 +193,13 @@ export function OntologyExplorer({
}
return (
-
+
-
{t("Related concepts")}
+
{t("Ontology neighborhood")}
{t("Typed relations, not Event Lineage")}
- {t("This view is separate from Event Lineage.")}{" "}
+ {t("This is an ontology neighborhood, not Event Lineage.")}{" "}
{t("Event Lineage shows reconstructed post-to-post parents. This graph shows typed people, organizations, teams, projects, and posts.")}
@@ -333,7 +333,7 @@ function statusMessage(
denied: t("Access denied for this ontology neighborhood. Open a visible post next."),
stale: t("This neighborhood is bound to a knowledge cutoff. Compare with live evidence next."),
rejected: t("Rejected proposal. Open the evidence and do not treat it as authoritative."),
- error: t("Related concepts are unavailable. Open a visible post next."),
+ error: t("Ontology neighborhood is unavailable. Open a visible post next."),
};
const text = messages[status];
if (!text) return null;
@@ -388,7 +388,7 @@ function OntologyGraph({
width="100%"
height={Math.max(180, layout.height)}
>
- {t("Related concepts")}
+ {t("Ontology neighborhood")}
{layout.edges.map((edge) => {
const midX = (edge.fromX + edge.toX) / 2;
const midY = (edge.fromY + edge.toY) / 2;
diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts
index 07b922858..3ef5cfcce 100644
--- a/frontend/src/i18n.test.ts
+++ b/frontend/src/i18n.test.ts
@@ -70,7 +70,7 @@ describe("i18n", () => {
"Interval relations",
"Inspect ontology neighborhood",
"Ontology neighborhood",
- "This view is separate from Event Lineage.",
+ "This is an ontology neighborhood, not Event Lineage.",
"Rankings",
"Title overlap",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.",
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index 97661931c..35bb8b462 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -37,7 +37,6 @@ const TRANSLATIONS: Partial>> = {
"Log out": "로그아웃",
Calendar: "캘린더",
Rankings: "순위",
- "Rankings · rankweave": "순위 · rankweave",
"Ranked result": "순위 결과",
"Rankings temporarily unavailable": "순위를 잠시 사용할 수 없습니다",
"Rankings are temporarily unavailable. Try again later.": "순위를 잠시 사용할 수 없습니다. 나중에 다시 시도하세요.",
@@ -169,7 +168,6 @@ const TRANSLATIONS: Partial>> = {
"Weak source hint": "신뢰도가 낮은 원본 힌트",
"Source hint": "원본 힌트",
"Source author evidence": "원본 작성자 증거",
- "Author context": "작성자 맥락",
"Hint only": "힌트 전용",
"Authorization context": "권한 계정 맥락",
"Our-side Keymen hints": "우리측 Keymen 힌트",
@@ -458,11 +456,10 @@ const TRANSLATIONS: Partial>> = {
"대화형 질문을 지금 사용할 수 없습니다. 저장된 근거는 계속 확인할 수 있습니다.",
"Inspect ontology neighborhood": "온톨로지 이웃 검사",
"Ontology neighborhood": "온톨로지 이웃",
- "Related concepts": "관련 개념",
Post: "글",
"Typed relations, not Event Lineage": "유형 관계이며 이벤트 계보가 아닙니다",
- "This view is separate from Event Lineage.":
- "이 보기는 이벤트 계보와 구분됩니다.",
+ "This is an ontology neighborhood, not Event Lineage.":
+ "이벤트 계보가 아니라 온톨로지 이웃입니다.",
"Event Lineage shows reconstructed post-to-post parents. This graph shows typed people, organizations, teams, projects, and posts.":
"이벤트 계보는 복원된 글-글 부모 링크입니다. 이 그래프는 유형이 있는 사람, 조직, 팀, 프로젝트, 글을 보여 줍니다.",
"Loading ontology neighborhood...": "온톨로지 이웃을 불러오는 중...",
@@ -478,8 +475,6 @@ const TRANSLATIONS: Partial>> = {
"거절된 제안입니다. 근거를 열고 권위 있는 사실로 다루지 마세요.",
"Ontology neighborhood is unavailable. Open a visible post next.":
"온톨로지 이웃을 사용할 수 없습니다. 다음으로 볼 수 있는 글을 여세요.",
- "Related concepts are unavailable. Open a visible post next.":
- "관련 개념을 사용할 수 없습니다. 다음으로 볼 수 있는 글을 여세요.",
"Search within this neighborhood": "이 이웃에서 검색",
"Reset focus": "초점 재설정",
"Export CSV": "CSV 내보내기",
@@ -570,7 +565,6 @@ const TRANSLATIONS: Partial>> = {
"Log out": "退出登录",
Calendar: "日历",
Rankings: "排名",
- "Rankings · rankweave": "排名 · rankweave",
"Ranked result": "排名结果",
"Rankings temporarily unavailable": "排名暂时不可用",
"Rankings are temporarily unavailable. Try again later.": "排名暂时不可用。请稍后再试。",
@@ -702,7 +696,6 @@ const TRANSLATIONS: Partial>> = {
"Weak source hint": "低可信源提示",
"Source hint": "源提示",
"Source author evidence": "源作者证据",
- "Author context": "作者背景",
"Hint only": "仅作提示",
"Authorization context": "授权账户上下文",
"Our-side Keymen hints": "我方关键联系人提示",
@@ -984,10 +977,9 @@ const TRANSLATIONS: Partial>> = {
"交互式提问暂不可用;已保存的证据仍可查看。",
"Inspect ontology neighborhood": "检查本体邻域",
"Ontology neighborhood": "本体邻域",
- "Related concepts": "相关概念",
Post: "文章",
"Typed relations, not Event Lineage": "这是类型化关系,不是事件谱系",
- "This view is separate from Event Lineage.": "此视图与事件谱系相互独立。",
+ "This is an ontology neighborhood, not Event Lineage.": "这是本体邻域,不是事件谱系。",
"Event Lineage shows reconstructed post-to-post parents. This graph shows typed people, organizations, teams, projects, and posts.":
"事件谱系显示重建的文章父子链接。此图显示带类型的人员、组织、团队、项目和文章。",
"Loading ontology neighborhood...": "正在加载本体邻域...",
@@ -1003,8 +995,6 @@ const TRANSLATIONS: Partial>> = {
"提案已被拒绝。打开证据,不要将其视为权威事实。",
"Ontology neighborhood is unavailable. Open a visible post next.":
"无法使用本体邻域。接下来打开一篇可见文章。",
- "Related concepts are unavailable. Open a visible post next.":
- "相关概念不可用。接下来打开一篇可见文章。",
"Search within this neighborhood": "在此邻域中搜索",
"Reset focus": "重置焦点",
"Export CSV": "导出 CSV",
@@ -1119,7 +1109,6 @@ const TRANSLATIONS: Partial>> = {
"Log out": "ログアウト",
Calendar: "カレンダー",
Rankings: "ランキング",
- "Rankings · rankweave": "ランキング · rankweave",
"Ranked result": "ランキング結果",
"Rankings temporarily unavailable": "ランキングは一時的に利用できません",
"Rankings are temporarily unavailable. Try again later.": "ランキングは一時的に利用できません。後でもう一度お試しください。",
@@ -1251,7 +1240,6 @@ const TRANSLATIONS: Partial>> = {
"Weak source hint": "信頼度の低いソースヒント",
"Source hint": "ソースヒント",
"Source author evidence": "ソース作成者の証拠",
- "Author context": "作成者の背景",
"Hint only": "ヒントのみ",
"Authorization context": "認証アカウントのコンテキスト",
"Our-side Keymen hints": "自社側キーパーソンのヒント",
@@ -1511,11 +1499,10 @@ const TRANSLATIONS: Partial>> = {
"対話形式の質問は現在利用できません。保存された証拠は確認できます。",
"Inspect ontology neighborhood": "オントロジー近傍を調べる",
"Ontology neighborhood": "オントロジー近傍",
- "Related concepts": "関連する概念",
Post: "投稿",
"Typed relations, not Event Lineage": "型付き関係であり、イベント系譜ではありません",
- "This view is separate from Event Lineage.":
- "このビューはイベント系譜とは別のものです。",
+ "This is an ontology neighborhood, not Event Lineage.":
+ "これはイベント系譜ではなく、オントロジー近傍です。",
"Event Lineage shows reconstructed post-to-post parents. This graph shows typed people, organizations, teams, projects, and posts.":
"イベント系譜は復元された投稿間の親子リンクです。このグラフは型付きの人・組織・チーム・プロジェクト・投稿を示します。",
"Loading ontology neighborhood...": "オントロジー近傍を読み込んでいます...",
@@ -1531,8 +1518,6 @@ const TRANSLATIONS: Partial>> = {
"却下された提案です。証拠を開き、権威ある事実として扱わないでください。",
"Ontology neighborhood is unavailable. Open a visible post next.":
"オントロジー近傍を利用できません。次に表示可能な投稿を開いてください。",
- "Related concepts are unavailable. Open a visible post next.":
- "関連する概念を利用できません。次に表示可能な投稿を開いてください。",
"Search within this neighborhood": "この近傍内を検索",
"Reset focus": "焦点をリセット",
"Export CSV": "CSV を書き出す",
@@ -1647,7 +1632,6 @@ const TRANSLATIONS: Partial>> = {
"Log out": "Đăng xuất",
Calendar: "Lịch",
Rankings: "Xếp hạng",
- "Rankings · rankweave": "Xếp hạng · rankweave",
"Ranked result": "Kết quả xếp hạng",
"Rankings temporarily unavailable": "Xếp hạng tạm thời không khả dụng",
"Rankings are temporarily unavailable. Try again later.": "Xếp hạng tạm thời không khả dụng. Hãy thử lại sau.",
@@ -1779,7 +1763,6 @@ const TRANSLATIONS: Partial>> = {
"Weak source hint": "Gợi ý nguồn có độ tin cậy thấp",
"Source hint": "Gợi ý nguồn",
"Source author evidence": "Bằng chứng tác giả nguồn",
- "Author context": "Bối cảnh tác giả",
"Hint only": "Chỉ là gợi ý",
"Authorization context": "Ngữ cảnh tài khoản quyền hạn",
"Our-side Keymen hints": "Gợi ý người liên hệ chính phía chúng ta",
@@ -2039,11 +2022,10 @@ const TRANSLATIONS: Partial>> = {
"Câu hỏi tương tác hiện không khả dụng; bằng chứng đã lưu vẫn có thể xem.",
"Inspect ontology neighborhood": "Kiểm tra lân cận bản thể",
"Ontology neighborhood": "Lân cận bản thể",
- "Related concepts": "Các khái niệm liên quan",
Post: "Bài viết",
"Typed relations, not Event Lineage": "Quan hệ có kiểu, không phải Dòng sự kiện",
- "This view is separate from Event Lineage.":
- "Chế độ xem này tách biệt với Dòng sự kiện.",
+ "This is an ontology neighborhood, not Event Lineage.":
+ "Đây là lân cận bản thể, không phải Dòng sự kiện.",
"Event Lineage shows reconstructed post-to-post parents. This graph shows typed people, organizations, teams, projects, and posts.":
"Dòng sự kiện hiện liên kết cha-con đã tái tạo giữa các bài viết. Đồ thị này hiện người, tổ chức, nhóm, dự án và bài viết có kiểu.",
"Loading ontology neighborhood...": "Đang tải lân cận bản thể...",
@@ -2059,8 +2041,6 @@ const TRANSLATIONS: Partial>> = {
"Đề xuất đã bị từ chối. Mở bằng chứng và đừng coi đó là sự thật có thẩm quyền.",
"Ontology neighborhood is unavailable. Open a visible post next.":
"Không dùng được lân cận bản thể. Hãy mở một bài viết hiển thị tiếp theo.",
- "Related concepts are unavailable. Open a visible post next.":
- "Các khái niệm liên quan không khả dụng. Hãy mở một bài viết hiển thị tiếp theo.",
"Search within this neighborhood": "Tìm trong lân cận này",
"Reset focus": "Đặt lại tiêu điểm",
"Export CSV": "Xuất CSV",
From eafa9e06bb6ed068bd57703a9eba3ff046d6dcee Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 18:04:46 +0900
Subject: [PATCH 12/21] fix(i18n): remove retired source-state copy
---
frontend/src/i18n.ts | 4 ----
1 file changed, 4 deletions(-)
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index 35bb8b462..de79b0c73 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -106,7 +106,6 @@ const TRANSLATIONS: Partial>> = {
"Publication state unknown": "공개 상태 미확인",
"Source draft marker present": "원천 임시저장 표식 있음",
"Source deletion marker present": "원천 삭제 표식 있음",
- "Original source state": "원천 상태",
"Source record details": "원본 레코드 세부 정보",
"Source system": "원천 시스템",
"Source record key": "원천 레코드 키",
@@ -634,7 +633,6 @@ const TRANSLATIONS: Partial>> = {
"Publication state unknown": "公开状态未知",
"Source draft marker present": "存在来源草稿标记",
"Source deletion marker present": "存在来源删除标记",
- "Original source state": "原始来源状态",
"Source record details": "源记录详情",
"Source system": "来源系统",
"Source record key": "来源记录键",
@@ -1178,7 +1176,6 @@ const TRANSLATIONS: Partial>> = {
"Publication state unknown": "公開状態不明",
"Source draft marker present": "原典に下書き標識あり",
"Source deletion marker present": "原典に削除標識あり",
- "Original source state": "原典の状態",
"Source record details": "元レコードの詳細",
"Source system": "原典システム",
"Source record key": "原典レコードキー",
@@ -1701,7 +1698,6 @@ const TRANSLATIONS: Partial>> = {
"Publication state unknown": "Chưa xác định trạng thái công khai",
"Source draft marker present": "Có dấu hiệu bản nháp nguồn",
"Source deletion marker present": "Có dấu hiệu xóa nguồn",
- "Original source state": "Trạng thái nguồn gốc",
"Source record details": "Chi tiết bản ghi nguồn",
"Source system": "Hệ thống nguồn",
"Source record key": "Khóa bản ghi nguồn",
From cd2cfb810fc6c16f88c3df0a592d9743cc9656c0 Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 18:08:16 +0900
Subject: [PATCH 13/21] fix(i18n): align ranked-record labels
---
frontend/src/i18n.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index de79b0c73..f1b4b1ef1 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -43,7 +43,7 @@ const TRANSLATIONS: Partial>> = {
"No ranked records are available. Check again after more records are added.": "순위가 매겨진 기록이 없습니다. 기록이 더 추가된 후 다시 확인하세요.",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "이 순위에 포함된 이유를 검토하려면 결과를 여세요. 이 결과는 보정된 측정값이 아닙니다.",
"Loading rankings...": "순위를 불러오는 중...",
- "Ranked records": "추천 기록",
+ "Ranked records": "순위가 매겨진 기록",
"Open ranking: {title}": "순위 열기: {title}",
"rank {rank}": "순위 {rank}",
"Title overlap": "제목 겹침",
@@ -570,7 +570,7 @@ const TRANSLATIONS: Partial>> = {
"No ranked records are available. Check again after more records are added.": "没有可用的排名记录。添加更多记录后请再次查看。",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "打开结果以查看它为何出现在此排名中。这些结果不是校准测量值。",
"Loading rankings...": "正在加载排名...",
- "Ranked records": "推荐记录",
+ "Ranked records": "排名记录",
"Open ranking: {title}": "打开排名:{title}",
"rank {rank}": "排名 {rank}",
"Title overlap": "标题重叠",
@@ -1113,7 +1113,7 @@ const TRANSLATIONS: Partial>> = {
"No ranked records are available. Check again after more records are added.": "利用できるランキング結果がありません。記録が追加された後でもう一度確認してください。",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "このランキングに表示された理由を確認するには結果を開いてください。これは校正された測定値ではありません。",
"Loading rankings...": "ランキングを読み込み中...",
- "Ranked records": "おすすめの記録",
+ "Ranked records": "ランキング記録",
"Open ranking: {title}": "ランキングを開く: {title}",
"rank {rank}": "順位 {rank}",
"Title overlap": "タイトル一致",
@@ -1635,7 +1635,7 @@ const TRANSLATIONS: Partial>> = {
"No ranked records are available. Check again after more records are added.": "Không có bản ghi được xếp hạng. Hãy kiểm tra lại sau khi có thêm bản ghi.",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "Mở một kết quả để xem lý do nó xuất hiện trong bảng xếp hạng này. Đây không phải là số đo đã hiệu chuẩn.",
"Loading rankings...": "Đang tải xếp hạng...",
- "Ranked records": "Bản ghi được đề xuất",
+ "Ranked records": "Bản ghi xếp hạng",
"Open ranking: {title}": "Mở xếp hạng: {title}",
"rank {rank}": "hạng {rank}",
"Title overlap": "Trùng tiêu đề",
From ff4d9eafdf7e60d32fd4c9c703f7b83da8ab346c Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 18:24:32 +0900
Subject: [PATCH 14/21] fix(ui): provide durable rankings next action
---
frontend/src/App.test.tsx | 6 +++++-
frontend/src/App.tsx | 6 ++++--
frontend/src/i18n.ts | 16 ++++++++--------
3 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 53c5a5694..169643061 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -3092,7 +3092,11 @@ describe("App, authenticated", () => {
stubBackend();
render( );
- expect(await screen.findByText("Rankings are temporarily unavailable. Try again later.")).toBeInTheDocument();
+ expect(
+ await screen.findByText(
+ "Rankings are unavailable for this workspace. Ask your workspace administrator to enable rankings.",
+ ),
+ ).toBeInTheDocument();
expect(screen.queryByText("Pricing renegotiation: revised quote sent")).not.toBeInTheDocument();
});
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 28b382ce9..f71c51cdd 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -3363,14 +3363,16 @@ function RankingsPanel({
{ranking.status === "accepted"
? t("Ranked result")
- : t("Rankings temporarily unavailable")}
+ : t("Rankings unavailable")}
)}
{error && {error}
}
{ranking === null && !error && {t("Loading rankings...")}
}
{ranking && ranking.status === "unavailable" && (
- {t("Rankings are temporarily unavailable. Try again later.")}
+
+ {t("Rankings are unavailable for this workspace. Ask your workspace administrator to enable rankings.")}
+
)}
{ranking && ranking.status === "accepted" && ranking.rankings.length === 0 && (
{t("No ranked records are available. Check again after more records are added.")}
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index f1b4b1ef1..318b62026 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -38,8 +38,8 @@ const TRANSLATIONS: Partial>> = {
Calendar: "캘린더",
Rankings: "순위",
"Ranked result": "순위 결과",
- "Rankings temporarily unavailable": "순위를 잠시 사용할 수 없습니다",
- "Rankings are temporarily unavailable. Try again later.": "순위를 잠시 사용할 수 없습니다. 나중에 다시 시도하세요.",
+ "Rankings unavailable": "순위를 사용할 수 없습니다",
+ "Rankings are unavailable for this workspace. Ask your workspace administrator to enable rankings.": "이 작업 공간에서는 순위를 사용할 수 없습니다. 작업 공간 관리자에게 순위 기능 활성화를 요청하세요.",
"No ranked records are available. Check again after more records are added.": "순위가 매겨진 기록이 없습니다. 기록이 더 추가된 후 다시 확인하세요.",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "이 순위에 포함된 이유를 검토하려면 결과를 여세요. 이 결과는 보정된 측정값이 아닙니다.",
"Loading rankings...": "순위를 불러오는 중...",
@@ -565,8 +565,8 @@ const TRANSLATIONS: Partial>> = {
Calendar: "日历",
Rankings: "排名",
"Ranked result": "排名结果",
- "Rankings temporarily unavailable": "排名暂时不可用",
- "Rankings are temporarily unavailable. Try again later.": "排名暂时不可用。请稍后再试。",
+ "Rankings unavailable": "排名不可用",
+ "Rankings are unavailable for this workspace. Ask your workspace administrator to enable rankings.": "此工作区无法使用排名。请联系工作区管理员启用排名。",
"No ranked records are available. Check again after more records are added.": "没有可用的排名记录。添加更多记录后请再次查看。",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "打开结果以查看它为何出现在此排名中。这些结果不是校准测量值。",
"Loading rankings...": "正在加载排名...",
@@ -1108,8 +1108,8 @@ const TRANSLATIONS: Partial>> = {
Calendar: "カレンダー",
Rankings: "ランキング",
"Ranked result": "ランキング結果",
- "Rankings temporarily unavailable": "ランキングは一時的に利用できません",
- "Rankings are temporarily unavailable. Try again later.": "ランキングは一時的に利用できません。後でもう一度お試しください。",
+ "Rankings unavailable": "ランキングを利用できません",
+ "Rankings are unavailable for this workspace. Ask your workspace administrator to enable rankings.": "このワークスペースではランキングを利用できません。ワークスペース管理者にランキングの有効化を依頼してください。",
"No ranked records are available. Check again after more records are added.": "利用できるランキング結果がありません。記録が追加された後でもう一度確認してください。",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "このランキングに表示された理由を確認するには結果を開いてください。これは校正された測定値ではありません。",
"Loading rankings...": "ランキングを読み込み中...",
@@ -1630,8 +1630,8 @@ const TRANSLATIONS: Partial>> = {
Calendar: "Lịch",
Rankings: "Xếp hạng",
"Ranked result": "Kết quả xếp hạng",
- "Rankings temporarily unavailable": "Xếp hạng tạm thời không khả dụng",
- "Rankings are temporarily unavailable. Try again later.": "Xếp hạng tạm thời không khả dụng. Hãy thử lại sau.",
+ "Rankings unavailable": "Xếp hạng không khả dụng",
+ "Rankings are unavailable for this workspace. Ask your workspace administrator to enable rankings.": "Xếp hạng không khả dụng cho không gian làm việc này. Hãy yêu cầu quản trị viên không gian làm việc bật tính năng xếp hạng.",
"No ranked records are available. Check again after more records are added.": "Không có bản ghi được xếp hạng. Hãy kiểm tra lại sau khi có thêm bản ghi.",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.": "Mở một kết quả để xem lý do nó xuất hiện trong bảng xếp hạng này. Đây không phải là số đo đã hiệu chuẩn.",
"Loading rankings...": "Đang tải xếp hạng...",
From 6013821a552c96e557214ee61bed7d7c040f9624 Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 21:25:01 +0900
Subject: [PATCH 15/21] fix(ui): replace implementation copy with user actions
---
frontend/src/App.test.tsx | 34 ++++++++++++----
frontend/src/App.tsx | 27 ++++++-------
frontend/src/i18n.ts | 85 ++++++++++++++++++++-------------------
3 files changed, 83 insertions(+), 63 deletions(-)
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 169643061..725e02d84 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -116,6 +116,7 @@ describe("App, authenticated", () => {
customerEntityHierarchy?: boolean;
staleSummary?: boolean;
contentAfterSummary?: boolean;
+ emptyPosts?: boolean;
organizationAliases?: boolean;
askLineageGraph?: boolean;
askImageCitation?: boolean;
@@ -1136,6 +1137,8 @@ describe("App, authenticated", () => {
jsonResponse(
postsUrl.searchParams.get("search")
? []
+ : options?.emptyPosts
+ ? { posts: [], total_count: 0, limit: 50, offset: 0 }
: {
posts: [
{
@@ -1734,7 +1737,7 @@ describe("App, authenticated", () => {
ask_job_id: "ask-job-1",
job_status_code: "succeeded",
answer: {
- answer_text: "The cited project is supported by the stored semantic evidence.",
+ answer_text: "The cited project is supported by the linked record.",
cited_post_ids: ["post-2"],
cited_posts: [{ post_id: "post-2", post_title: "Linked post" }],
cited_post_evidence: [
@@ -2220,7 +2223,7 @@ describe("App, authenticated", () => {
await screen.findByRole("button", { name: "Search related posts for: Semantic project" }),
);
- const searchInput = await screen.findByRole("searchbox", { name: "Search semantic evidence" });
+ const searchInput = await screen.findByRole("searchbox", { name: "Search records and evidence" });
expect(searchInput).toHaveValue("Semantic project");
expect(screen.queryByRole("button", { name: "Close" })).not.toBeInTheDocument();
});
@@ -2249,7 +2252,7 @@ describe("App, authenticated", () => {
const board = await screen.findByRole("region", { name: "Board" });
expect(within(board).getByRole("search", { name: "Search and filter posts" })).toBeInTheDocument();
- expect(within(board).getByLabelText("Search semantic evidence")).toHaveAttribute("type", "search");
+ expect(within(board).getByLabelText("Search records and evidence")).toHaveAttribute("type", "search");
expect(within(board).getByRole("list", { name: "Board posts" })).toBeInTheDocument();
expect(within(board).getByText(/Posts shown:/)).toBeInTheDocument();
expect(within(board).getByLabelText("Voice of Partner")).toBeInTheDocument();
@@ -2259,13 +2262,24 @@ describe("App, authenticated", () => {
expect(fetchMock.mock.calls.some(([url]) => String(url).includes("sort=title"))).toBe(true),
);
- await userEvent.type(within(board).getByLabelText("Search semantic evidence"), "not found");
+ await userEvent.type(within(board).getByLabelText("Search records and evidence"), "not found");
await userEvent.click(within(board).getByRole("button", { name: "Search" }));
expect(within(board).getByRole("status")).toHaveTextContent("No posts match the current filters.");
await userEvent.click(within(board).getByRole("button", { name: "Reset filters" }));
expect(within(board).getByRole("button", { name: "View post: Public post" })).toBeInTheDocument();
});
+ it("guides an empty board without exposing a developer command", async () => {
+ stubBackend({ emptyPosts: true });
+ render( );
+
+ const status = await screen.findByRole("status");
+ expect(status).toHaveTextContent(
+ "No posts are available. Ask an administrator to check your access or import source records.",
+ );
+ expect(status).not.toHaveTextContent("make seed");
+ });
+
it("opens a post from a DAG node click", async () => {
stubBackend();
render( );
@@ -2461,8 +2475,8 @@ describe("App, authenticated", () => {
expect(provenance).not.toHaveAttribute("open");
await userEvent.click(screen.getByText("Evidence provenance"));
expect(screen.getByText(/Ontology class:/)).toBeInTheDocument();
- expect(screen.getByText(/Extraction source: Semantic extraction/)).toBeInTheDocument();
- expect(screen.getByText(/Evidence field: Stored semantic evidence/)).toBeInTheDocument();
+ expect(screen.getByText(/Extraction source: Record content/)).toBeInTheDocument();
+ expect(screen.getByText(/Evidence field: Supporting record text/)).toBeInTheDocument();
expect(screen.queryByText("contextual_orchestrator_semantic")).not.toBeInTheDocument();
expect(screen.queryByText("https://contextualwisdomlab.github.io/LineageWeave/ontology#Project")).not.toBeInTheDocument();
expect(screen.getByText("첫 번째 이벤트")).toBeInTheDocument();
@@ -2512,13 +2526,17 @@ describe("App, authenticated", () => {
expect(keyman.compareDocumentPosition(ask) & Node.DOCUMENT_POSITION_FOLLOWING).not.toBe(0);
});
- it("labels a stale summary and retries the semantic refresh on request", async () => {
+ it("labels a stale summary and retries the summary refresh on request", async () => {
const fetchMock = stubBackend({ staleSummary: true });
render( );
await userEvent.click(await screen.findByRole("button", { name: "View post: Public post" }));
await waitFor(() =>
- expect(screen.getByText("Last saved summary shown. Retry semantic refresh.")).toBeInTheDocument(),
+ expect(
+ screen.getByText(
+ "The last saved summary is shown. Select Retry summary refresh to check for an update.",
+ ),
+ ).toBeInTheDocument(),
);
const summaryCallsBeforeRetry = fetchMock.mock.calls.filter(([input]) =>
String(input).endsWith("/api/posts/post-1/summary"),
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index f71c51cdd..9a6ad0103 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -795,13 +795,13 @@ function relatedNodeCaption(node: RelatedNode): string {
const PROJECT_EXTRACTION_LABELS: Record = {
source_field_hint: "Explicit source field",
- contextual_orchestrator_semantic: "Semantic extraction",
+ contextual_orchestrator_semantic: "Record content",
};
const PROJECT_PROVENANCE_LABELS: Record = {
"source_post.source_project_code": "Source project code",
"source_post.source_project_name": "Source project name",
- "post_project_mention.evidence_text": "Stored semantic evidence",
+ "post_project_mention.evidence_text": "Supporting record text",
};
function projectExtractionLabel(method: string): string {
@@ -2145,7 +2145,7 @@ function PostDetailPopup({
) : (
- {t("Source body was not imported; summary and semantic extraction are unavailable.")}
+ {t("The source text is missing. Open the original record, or ask an administrator to import its text before reviewing this post.")}
)}
@@ -2286,8 +2286,8 @@ function PostDetailPopup({
{post.project_evidence && post.project_evidence.length > 0 ? (
-
- {t("Projects / semantic evidence")}
+
+ {t("Projects and supporting evidence")}
{post.project_evidence.map((project) => (
@@ -2329,7 +2329,7 @@ function PostDetailPopup({
<>
{summary.summary_status === "stale" ? (
- {t("Last saved summary shown. Retry semantic refresh.")} {" "}
+ {t("The last saved summary is shown. Select Retry summary refresh to check for an update.")} {" "}
setSummaryRetry((value) => value + 1)}>
{t("Retry summary refresh")}
@@ -3139,8 +3139,7 @@ function AnalysisRunsPanel({
{(error || entitiesLoadError) &&
{error ?? entitiesLoadError}
}
{runs.length === 0 ? (
- No analysis runs visible to this account yet. Request a lineage
- reconstruction, or ask an administrator to run make seed.
+ {t("No analysis runs are available. Select Request lineage reconstruction, or ask an administrator to check your data access.")}
) : (
@@ -4153,17 +4152,17 @@ function PostList({
}}
>
- {t("Search semantic evidence")}
+ {t("Search records and evidence")}
setSearchInput(event.target.value)}
- placeholder={t("Search semantic evidence")}
- aria-label={t("Search semantic evidence")}
+ placeholder={t("Search records and evidence")}
+ aria-label={t("Search records and evidence")}
/>
{t("Search")}
- {t("Search includes post text and semantic evidence.")}
+ {t("Enter words from a record or its supporting evidence, then select Search.")}
{t("Filter by VOC type")}
{vocTypeOptions.map((option) => (
@@ -4220,7 +4219,7 @@ function PostList({
{hasBoardFilters
? t("No posts match the current filters.")
- : t("No posts visible to this account yet -- try `make seed`.")}
+ : t("No posts are available. Ask an administrator to check your access or import source records.")}
) : filteredPosts.length === 0 ? (
@@ -4685,7 +4684,7 @@ function CustomerMasterPanel({
{t("Observed customer evidence")}
- {t("Source identifiers are hints only; ontology and semantic evidence must resolve them before binding a customer.")}
+ {t("These identifiers are unverified. Open the related records, then select Resolve only after the customer identity is confirmed.")}
{master.source_customer_hints.length > HINT_RENDER_LIMIT && (
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index 318b62026..8eaf1d14a 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -71,9 +71,9 @@ const TRANSLATIONS: Partial>> = {
"Ontology class": "온톨로지 분류",
"Extraction source": "추출 경로",
"Explicit source field": "명시 원본 필드",
- "Semantic extraction": "의미 기반 추출",
+ "Record content": "기록 내용",
"Recorded extraction": "기록된 추출",
- "Stored semantic evidence": "저장된 의미 기반 근거",
+ "Supporting record text": "뒷받침하는 기록 본문",
"Recorded evidence": "기록된 근거",
"Lineage maintenance": "계보 관리",
"Verification unavailable (search is not configured).": "검증을 사용할 수 없습니다(검색이 설정되지 않았습니다).",
@@ -121,8 +121,8 @@ const TRANSLATIONS: Partial>> = {
"Source process unit name": "원천 사업부(PU) 이름",
"Source sales pool": "원천 수주풀",
"Source sales pool name": "원천 수주풀 이름",
- "Source body was not imported; summary and semantic extraction are unavailable.":
- "원천 본문이 수집되지 않아 요약과 Semantic 추출을 사용할 수 없습니다.",
+ "The source text is missing. Open the original record, or ask an administrator to import its text before reviewing this post.":
+ "원본 본문이 없습니다. 원본 기록을 열거나, 이 게시물을 검토하기 전에 관리자에게 본문을 가져오도록 요청하세요.",
"Source customer code": "원천 고객 코드",
"Source customer name": "원천 고객사 이름",
"Source project code": "원천 프로젝트 코드",
@@ -130,9 +130,9 @@ const TRANSLATIONS: Partial>> = {
"Business unit (PU)": "사업부(PU)",
"Use these details to compare this record in its original system.": "이 세부 정보를 사용해 원본 시스템의 레코드와 비교하세요.",
"Search and filter posts": "글 검색 및 필터",
- "Search semantic evidence": "의미 기반 증거 검색",
+ "Search records and evidence": "기록 및 근거 검색",
Search: "검색",
- "Search includes post text and semantic evidence.": "게시물 본문과 의미 기반 증거를 함께 검색합니다.",
+ "Enter words from a record or its supporting evidence, then select Search.": "기록이나 뒷받침 근거에 포함된 단어를 입력한 다음 검색을 선택하세요.",
"Filter by VOC type": "VOC 유형으로 필터",
"Filter by visibility": "공개 여부로 필터",
"Sort posts": "글 정렬",
@@ -149,6 +149,8 @@ const TRANSLATIONS: Partial>> = {
"Posts shown:": "표시 글:",
"Board posts": "게시판 글",
"No posts match the current filters.": "현재 필터에 맞는 글이 없습니다.",
+ "No posts are available. Ask an administrator to check your access or import source records.": "표시할 글이 없습니다. 관리자에게 접근 권한을 확인하거나 원본 기록을 가져오도록 요청하세요.",
+ "No analysis runs are available. Select Request lineage reconstruction, or ask an administrator to check your data access.": "사용 가능한 분석 실행이 없습니다. 계보 재구성 요청을 선택하거나 관리자에게 데이터 접근 권한을 확인하도록 요청하세요.",
"Customer master": "고객 마스터",
"Ask Agent": "Ask Agent",
"Workspace navigation": "워크스페이스 메뉴",
@@ -162,7 +164,7 @@ const TRANSLATIONS: Partial>> = {
"A counterparty can hold more than one role over time -- a customer in one post can be a competitor, supplier, or partner in another. Every role observed for a name is listed, not just the most frequent.":
"거래처는 시간에 따라 여러 역할을 동시에 가질 수 있습니다 -- 한 게시물에서는 고객이지만 다른 게시물에서는 경쟁사, 공급자, 파트너일 수 있습니다. 가장 빈번한 역할만이 아니라 관측된 모든 역할을 표시합니다.",
"Multiple roles observed": "복수 역할 관측됨",
- "Source identifiers are hints only; ontology and semantic evidence must resolve them before binding a customer.": "원본 식별자는 힌트일 뿐이며, 고객에 연결하기 전에 온톨로지와 의미 증거로 확인해야 합니다.",
+ "These identifiers are unverified. Open the related records, then select Resolve only after the customer identity is confirmed.": "식별자가 확인되지 않았습니다. 관련 기록을 연 다음 고객 신원이 확인된 경우에만 확인을 선택하세요.",
"Unresolved source identifier": "미해결 원본 식별자",
"Weak source hint": "신뢰도가 낮은 원본 힌트",
"Source hint": "원본 힌트",
@@ -252,7 +254,7 @@ const TRANSLATIONS: Partial>> = {
Activity: "활동",
Tickets: "티켓",
Summary: "요약",
- "Last saved summary shown. Retry semantic refresh.": "마지막 저장 요약을 표시합니다. 의미 기반 새로고침을 다시 시도하세요.",
+ "The last saved summary is shown. Select Retry summary refresh to check for an update.": "마지막 저장 요약을 표시합니다. 업데이트를 확인하려면 요약 새로고침 재시도를 선택하세요.",
"Retry summary refresh": "요약 새로고침 재시도",
"5W1H": "5W1H",
Who: "누가",
@@ -268,7 +270,7 @@ const TRANSLATIONS: Partial>> = {
Counterparties: "관련 주체",
"Issue tickets": "이슈 티켓",
"Key events": "주요 이벤트",
- "Projects / semantic evidence": "프로젝트 / 의미 기반 근거",
+ "Projects and supporting evidence": "프로젝트와 뒷받침 근거",
"R&R": "역할과 책임",
"Major event actions": "주요 이벤트별 요청 및 처리",
Requester: "요청자",
@@ -301,8 +303,6 @@ const TRANSLATIONS: Partial>> = {
"Loading reports...": "리포트를 불러오는 중...",
"Loading posts...": "글을 불러오는 중...",
"Loading lineage graph...": "계보 그래프를 불러오는 중...",
- "No posts visible to this account yet -- try `make seed`.":
- "이 계정에 표시할 글이 아직 없습니다. `make seed`를 실행해 보세요.",
"Load more posts": "글 더 불러오기",
"Board pages": "게시판 페이지",
"Previous page": "이전 페이지",
@@ -598,9 +598,9 @@ const TRANSLATIONS: Partial>> = {
"Ontology class": "本体分类",
"Extraction source": "提取路径",
"Explicit source field": "显式源字段",
- "Semantic extraction": "语义提取",
+ "Record content": "记录内容",
"Recorded extraction": "已记录的提取",
- "Stored semantic evidence": "已存储的语义证据",
+ "Supporting record text": "支持此项的记录正文",
"Recorded evidence": "已记录的证据",
"Lineage maintenance": "谱系维护",
"Verification unavailable (search is not configured).": "无法验证(未配置搜索)。",
@@ -648,8 +648,8 @@ const TRANSLATIONS: Partial>> = {
"Source process unit name": "来源事业部名称 (PU)",
"Source sales pool": "来源销售池",
"Source sales pool name": "来源销售池名称",
- "Source body was not imported; summary and semantic extraction are unavailable.":
- "尚未导入来源正文,因此无法进行摘要和语义提取。",
+ "The source text is missing. Open the original record, or ask an administrator to import its text before reviewing this post.":
+ "来源正文缺失。请打开原始记录,或在查看此文章前请管理员导入正文。",
"Source customer code": "来源客户代码",
"Source customer name": "来源客户名称",
"Source project code": "来源项目代码",
@@ -657,9 +657,9 @@ const TRANSLATIONS: Partial>> = {
"Business unit (PU)": "事业部 (PU)",
"Use these details to compare this record in its original system.": "使用这些详情与原始系统中的记录进行比较。",
"Search and filter posts": "搜索和筛选文章",
- "Search semantic evidence": "搜索语义证据",
+ "Search records and evidence": "搜索记录和证据",
Search: "搜索",
- "Search includes post text and semantic evidence.": "同时搜索文章正文和语义证据。",
+ "Enter words from a record or its supporting evidence, then select Search.": "输入记录或支持证据中的文字,然后选择“搜索”。",
"Filter by VOC type": "按 VOC 类型筛选",
"Filter by visibility": "按公开状态筛选",
"Sort posts": "排序文章",
@@ -676,6 +676,8 @@ const TRANSLATIONS: Partial>> = {
"Posts shown:": "显示文章:",
"Board posts": "看板文章",
"No posts match the current filters.": "没有文章符合当前筛选条件。",
+ "No posts are available. Ask an administrator to check your access or import source records.": "没有可用文章。请让管理员检查您的访问权限或导入源记录。",
+ "No analysis runs are available. Select Request lineage reconstruction, or ask an administrator to check your data access.": "没有可用的分析运行。请选择“请求谱系重建”,或让管理员检查您的数据访问权限。",
"Customer master": "客户主数据",
"Ask Agent": "Ask Agent",
"Workspace navigation": "工作区导航",
@@ -689,7 +691,7 @@ const TRANSLATIONS: Partial>> = {
"A counterparty can hold more than one role over time -- a customer in one post can be a competitor, supplier, or partner in another. Every role observed for a name is listed, not just the most frequent.":
"同一交易对手可能随时间拥有多个角色 -- 在一篇文章中是客户,在另一篇文章中可能是竞争对手、供应商或合作伙伴。会列出观测到的每一个角色,而不仅是最频繁的那个。",
"Multiple roles observed": "观测到多个角色",
- "Source identifiers are hints only; ontology and semantic evidence must resolve them before binding a customer.": "源标识符仅是提示;绑定客户前必须通过本体和语义证据解析它们。",
+ "These identifiers are unverified. Open the related records, then select Resolve only after the customer identity is confirmed.": "这些标识符尚未核实。请打开相关记录,并仅在确认客户身份后选择“解析”。",
"Unresolved source identifier": "未解析的源标识符",
"Weak source hint": "低可信源提示",
"Source hint": "源提示",
@@ -775,7 +777,7 @@ const TRANSLATIONS: Partial>> = {
Activity: "活动",
Tickets: "工单",
Summary: "摘要",
- "Last saved summary shown. Retry semantic refresh.": "正在显示上次保存的摘要。请重试语义刷新。",
+ "The last saved summary is shown. Select Retry summary refresh to check for an update.": "正在显示上次保存的摘要。请选择“重试摘要刷新”以检查更新。",
"Retry summary refresh": "重试摘要刷新",
"5W1H": "5W1H",
Who: "谁",
@@ -791,7 +793,7 @@ const TRANSLATIONS: Partial>> = {
Counterparties: "相关方",
"Issue tickets": "问题工单",
"Key events": "关键事件",
- "Projects / semantic evidence": "项目 / 语义证据",
+ "Projects and supporting evidence": "项目和支持证据",
"R&R": "角色与责任",
"Major event actions": "主要事件请求与处理",
Requester: "请求方",
@@ -824,7 +826,6 @@ const TRANSLATIONS: Partial>> = {
"Loading reports...": "正在加载报告...",
"Loading posts...": "正在加载文章...",
"Loading lineage graph...": "正在加载谱系图...",
- "No posts visible to this account yet -- try `make seed`.": "此账户暂时没有可见文章。请尝试运行 `make seed`。",
"Load more posts": "加载更多文章",
"Board pages": "看板页码",
"Previous page": "上一页",
@@ -1141,9 +1142,9 @@ const TRANSLATIONS: Partial>> = {
"Ontology class": "オントロジー分類",
"Extraction source": "抽出経路",
"Explicit source field": "明示されたソースフィールド",
- "Semantic extraction": "意味抽出",
+ "Record content": "記録本文",
"Recorded extraction": "記録された抽出",
- "Stored semantic evidence": "保存された意味的証拠",
+ "Supporting record text": "根拠となる記録本文",
"Recorded evidence": "記録された証拠",
"Lineage maintenance": "系譜管理",
"Verification unavailable (search is not configured).": "確認できません(検索が設定されていません)。",
@@ -1191,8 +1192,8 @@ const TRANSLATIONS: Partial>> = {
"Source process unit name": "原典の事業部名 (PU)",
"Source sales pool": "原典の受注プール",
"Source sales pool name": "原典の受注プール名",
- "Source body was not imported; summary and semantic extraction are unavailable.":
- "原典本文が取り込まれていないため、要約とセマンティック抽出は利用できません。",
+ "The source text is missing. Open the original record, or ask an administrator to import its text before reviewing this post.":
+ "原典本文がありません。元の記録を開くか、この投稿を確認する前に管理者へ本文の取り込みを依頼してください。",
"Source customer code": "原典の顧客コード",
"Source customer name": "原典の顧客名",
"Source project code": "原典のプロジェクトコード",
@@ -1200,9 +1201,9 @@ const TRANSLATIONS: Partial>> = {
"Business unit (PU)": "事業部 (PU)",
"Use these details to compare this record in its original system.": "これらの詳細を使って、元のシステムのレコードと比較してください。",
"Search and filter posts": "投稿を検索・絞り込み",
- "Search semantic evidence": "意味に基づく証拠を検索",
+ "Search records and evidence": "記録と根拠を検索",
Search: "検索",
- "Search includes post text and semantic evidence.": "投稿本文と意味に基づく証拠を検索します。",
+ "Enter words from a record or its supporting evidence, then select Search.": "記録またはその根拠に含まれる語句を入力し、「検索」を選択してください。",
"Filter by VOC type": "VOC 種類で絞り込み",
"Filter by visibility": "公開状態で絞り込み",
"Sort posts": "投稿を並べ替え",
@@ -1219,6 +1220,8 @@ const TRANSLATIONS: Partial>> = {
"Posts shown:": "表示投稿:",
"Board posts": "掲示板の投稿",
"No posts match the current filters.": "現在の絞り込みに一致する投稿はありません。",
+ "No posts are available. Ask an administrator to check your access or import source records.": "利用できる投稿がありません。管理者にアクセス権の確認または原典記録の取り込みを依頼してください。",
+ "No analysis runs are available. Select Request lineage reconstruction, or ask an administrator to check your data access.": "利用できる分析実行がありません。「系譜の再構築を依頼」を選択するか、管理者にデータアクセスの確認を依頼してください。",
"Customer master": "顧客マスター",
"Ask Agent": "Ask Agent",
"Workspace navigation": "ワークスペースナビゲーション",
@@ -1232,7 +1235,7 @@ const TRANSLATIONS: Partial>> = {
"A counterparty can hold more than one role over time -- a customer in one post can be a competitor, supplier, or partner in another. Every role observed for a name is listed, not just the most frequent.":
"取引先は時間の経過とともに複数の役割を持つことがあります -- ある投稿では顧客でも、別の投稿では競合他社、サプライヤー、またはパートナーである場合があります。最も頻繁な役割だけでなく、観測されたすべての役割を表示します。",
"Multiple roles observed": "複数の役割が観測されました",
- "Source identifiers are hints only; ontology and semantic evidence must resolve them before binding a customer.": "ソース識別子はヒントにすぎません。顧客に紐付ける前にオントロジーと意味証拠で解決する必要があります。",
+ "These identifiers are unverified. Open the related records, then select Resolve only after the customer identity is confirmed.": "これらの識別子は未確認です。関連する記録を開き、顧客の身元を確認できた場合のみ「解決」を選択してください。",
"Unresolved source identifier": "未解決のソース識別子",
"Weak source hint": "信頼度の低いソースヒント",
"Source hint": "ソースヒント",
@@ -1318,14 +1321,14 @@ const TRANSLATIONS: Partial>> = {
Activity: "アクティビティ",
Tickets: "チケット",
Summary: "概要",
- "Last saved summary shown. Retry semantic refresh.": "保存済みの最新の要約を表示しています。意味更新を再試行してください。",
+ "The last saved summary is shown. Select Retry summary refresh to check for an update.": "最後に保存された要約を表示しています。更新を確認するには「要約の更新を再試行」を選択してください。",
"Retry summary refresh": "要約の更新を再試行",
Evidence: "証拠",
"Post quality (IRT)": "投稿品質(IRT)",
Counterparties: "関係者",
"Issue tickets": "課題チケット",
"Key events": "主なイベント",
- "Projects / semantic evidence": "プロジェクト / 意味的証拠",
+ "Projects and supporting evidence": "プロジェクトと根拠",
"R&R": "役割と責任",
"Major event actions": "主要イベントの依頼と処理",
Requester: "依頼者",
@@ -1358,7 +1361,6 @@ const TRANSLATIONS: Partial>> = {
"Loading reports...": "レポートを読み込んでいます...",
"Loading posts...": "投稿を読み込んでいます...",
"Loading lineage graph...": "系譜グラフを読み込んでいます...",
- "No posts visible to this account yet -- try `make seed`.": "このアカウントに表示できる投稿はまだありません。`make seed`を試してください。",
"Load more posts": "さらに投稿を読み込む",
"Board pages": "掲示板のページ",
"Previous page": "前のページ",
@@ -1663,9 +1665,9 @@ const TRANSLATIONS: Partial>> = {
"Ontology class": "Lớp ontology",
"Extraction source": "Nguồn trích xuất",
"Explicit source field": "Trường nguồn rõ ràng",
- "Semantic extraction": "Trích xuất ngữ nghĩa",
+ "Record content": "Nội dung bản ghi",
"Recorded extraction": "Bản trích xuất đã ghi nhận",
- "Stored semantic evidence": "Bằng chứng ngữ nghĩa đã lưu",
+ "Supporting record text": "Nội dung bản ghi làm căn cứ",
"Recorded evidence": "Bằng chứng đã ghi nhận",
"Lineage maintenance": "Bảo trì dòng sự kiện",
"Verification unavailable (search is not configured).": "Không thể xác minh (chưa cấu hình tìm kiếm).",
@@ -1713,8 +1715,8 @@ const TRANSLATIONS: Partial>> = {
"Source process unit name": "Tên đơn vị kinh doanh nguồn (PU)",
"Source sales pool": "Nhóm bán hàng nguồn",
"Source sales pool name": "Tên nhóm bán hàng nguồn",
- "Source body was not imported; summary and semantic extraction are unavailable.":
- "Nội dung nguồn chưa được nhập nên không thể tóm tắt hoặc trích xuất ngữ nghĩa.",
+ "The source text is missing. Open the original record, or ask an administrator to import its text before reviewing this post.":
+ "Thiếu nội dung nguồn. Hãy mở bản ghi gốc hoặc yêu cầu quản trị viên nhập nội dung trước khi xem bài viết này.",
"Source customer code": "Mã khách hàng nguồn",
"Source customer name": "Tên khách hàng nguồn",
"Source project code": "Mã dự án nguồn",
@@ -1722,9 +1724,9 @@ const TRANSLATIONS: Partial>> = {
"Business unit (PU)": "Đơn vị kinh doanh (PU)",
"Use these details to compare this record in its original system.": "Dùng các chi tiết này để đối chiếu bản ghi trong hệ thống gốc.",
"Search and filter posts": "Tìm kiếm và lọc bài viết",
- "Search semantic evidence": "Tìm kiếm bằng chứng ngữ nghĩa",
+ "Search records and evidence": "Tìm kiếm bản ghi và bằng chứng",
Search: "Tìm",
- "Search includes post text and semantic evidence.": "Tìm trong nội dung bài viết và bằng chứng ngữ nghĩa.",
+ "Enter words from a record or its supporting evidence, then select Search.": "Nhập từ có trong bản ghi hoặc bằng chứng hỗ trợ, rồi chọn Tìm.",
"Filter by VOC type": "Lọc theo loại VOC",
"Filter by visibility": "Lọc theo trạng thái hiển thị",
"Sort posts": "Sắp xếp bài viết",
@@ -1741,6 +1743,8 @@ const TRANSLATIONS: Partial>> = {
"Posts shown:": "Bài viết hiển thị:",
"Board posts": "Bài viết trên bảng tin",
"No posts match the current filters.": "Không có bài viết nào khớp với bộ lọc hiện tại.",
+ "No posts are available. Ask an administrator to check your access or import source records.": "Không có bài viết nào. Hãy yêu cầu quản trị viên kiểm tra quyền truy cập của bạn hoặc nhập các bản ghi nguồn.",
+ "No analysis runs are available. Select Request lineage reconstruction, or ask an administrator to check your data access.": "Không có lượt phân tích nào. Hãy chọn Yêu cầu tái dựng phả hệ hoặc yêu cầu quản trị viên kiểm tra quyền truy cập dữ liệu của bạn.",
"Customer master": "Danh mục khách hàng",
"Ask Agent": "Ask Agent",
"Workspace navigation": "Điều hướng không gian làm việc",
@@ -1754,7 +1758,7 @@ const TRANSLATIONS: Partial>> = {
"A counterparty can hold more than one role over time -- a customer in one post can be a competitor, supplier, or partner in another. Every role observed for a name is listed, not just the most frequent.":
"Một đối tác có thể giữ nhiều vai trò theo thời gian -- là khách hàng trong bài viết này nhưng có thể là đối thủ cạnh tranh, nhà cung cấp, hoặc đối tác trong bài viết khác. Mọi vai trò được quan sát đều được liệt kê, không chỉ vai trò phổ biến nhất.",
"Multiple roles observed": "Đã quan sát nhiều vai trò",
- "Source identifiers are hints only; ontology and semantic evidence must resolve them before binding a customer.": "Mã định danh nguồn chỉ là gợi ý; ontology và bằng chứng ngữ nghĩa phải phân giải trước khi gắn với khách hàng.",
+ "These identifiers are unverified. Open the related records, then select Resolve only after the customer identity is confirmed.": "Các mã định danh này chưa được xác minh. Hãy mở các bản ghi liên quan và chỉ chọn Giải quyết sau khi xác nhận danh tính khách hàng.",
"Unresolved source identifier": "Mã định danh nguồn chưa được phân giải",
"Weak source hint": "Gợi ý nguồn có độ tin cậy thấp",
"Source hint": "Gợi ý nguồn",
@@ -1840,14 +1844,14 @@ const TRANSLATIONS: Partial>> = {
Activity: "Hoạt động",
Tickets: "Phiếu công việc",
Summary: "Tóm tắt",
- "Last saved summary shown. Retry semantic refresh.": "Đang hiển thị bản tóm tắt đã lưu gần nhất. Hãy thử lại việc làm mới ngữ nghĩa.",
+ "The last saved summary is shown. Select Retry summary refresh to check for an update.": "Đang hiển thị bản tóm tắt đã lưu gần nhất. Chọn Thử lại làm mới bản tóm tắt để kiểm tra cập nhật.",
"Retry summary refresh": "Thử lại việc làm mới bản tóm tắt",
Evidence: "Bằng chứng",
"Post quality (IRT)": "Chất lượng bài viết (IRT)",
Counterparties: "Các bên liên quan",
"Issue tickets": "Phiếu vấn đề",
"Key events": "Sự kiện chính",
- "Projects / semantic evidence": "Dự án / bằng chứng ngữ nghĩa",
+ "Projects and supporting evidence": "Dự án và bằng chứng hỗ trợ",
"R&R": "Vai trò và trách nhiệm",
"Major event actions": "Yêu cầu và xử lý theo sự kiện chính",
Requester: "Người yêu cầu",
@@ -1880,7 +1884,6 @@ const TRANSLATIONS: Partial>> = {
"Loading reports...": "Đang tải báo cáo...",
"Loading posts...": "Đang tải bài viết...",
"Loading lineage graph...": "Đang tải đồ thị dòng sự kiện...",
- "No posts visible to this account yet -- try `make seed`.": "Tài khoản này chưa có bài viết hiển thị. Hãy thử chạy `make seed`.",
"Load more posts": "Tải thêm bài viết",
"Board pages": "Các trang bảng tin",
"Previous page": "Trang trước",
From bebab3d601c958ddab0024260e5cc5bcda3aad81 Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 21:37:50 +0900
Subject: [PATCH 16/21] fix(ui): keep recovery guidance product-facing
---
backend/app/analysis_run_ingestion.py | 10 ++++++----
backend/app/analysis_run_start.py | 8 ++++----
frontend/src/App.test.tsx | 18 +++++++++---------
frontend/src/App.tsx | 14 +++++++-------
tests/test_analysis_run_create.py | 6 +++---
tests/test_analysis_run_outbox.py | 2 +-
tests/test_analysis_run_start.py | 2 +-
7 files changed, 31 insertions(+), 29 deletions(-)
diff --git a/backend/app/analysis_run_ingestion.py b/backend/app/analysis_run_ingestion.py
index 7335f6fb9..457675fb8 100644
--- a/backend/app/analysis_run_ingestion.py
+++ b/backend/app/analysis_run_ingestion.py
@@ -640,14 +640,16 @@ def _require_lineage_create_kind(run_kind_code: str) -> None:
if run_kind_code == _TEPP_RUN_KIND:
raise AnalysisRunCreateError(
422,
- "Connect a TEPP transport from a Failed TEPP row; this endpoint "
- "does not invent a measurement.",
+ "Open the failed measurement run. Ask an administrator to restore "
+ "measurement access before retrying; this request cannot produce "
+ "a measurement.",
)
if run_kind_code == _TOPIC_LINEAGE_RUN_KIND:
raise AnalysisRunCreateError(
422,
- "Connect a TEPP transport from a Failed topic-lineage row; this "
- "endpoint does not invent a topic model.",
+ "Open the failed topic analysis. Ask an administrator to restore "
+ "topic measurement access before retrying; this request cannot "
+ "produce a topic model.",
)
if run_kind_code == _REPORT_RUN_KIND:
raise AnalysisRunCreateError(
diff --git a/backend/app/analysis_run_start.py b/backend/app/analysis_run_start.py
index c08810078..73f73f45f 100644
--- a/backend/app/analysis_run_start.py
+++ b/backend/app/analysis_run_start.py
@@ -141,13 +141,13 @@ def start_kind_rejection(run_kind_code: str) -> AnalysisRunStartError | None:
if run_kind_code == _REPORT_KIND:
return AnalysisRunStartError(
422,
- "Rebuild the period report from the reports panel. "
- "This start path does not invent a measurement.",
+ "Open Reports and rebuild the period report. "
+ "This action cannot produce a measurement.",
)
return AnalysisRunStartError(
422,
- "Start reconstructs a Pending lineage run or submits TEPP. "
- "This start path does not invent a measurement.",
+ "Open a pending lineage or measurement run and select its available action. "
+ "An unsupported run cannot produce a measurement.",
)
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 725e02d84..9ea944ef3 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -650,7 +650,7 @@ describe("App, authenticated", () => {
JSON.stringify({
detail:
payload.run_kind_code === "analysis_run_tepp"
- ? "Connect a TEPP transport from a Failed TEPP row; this endpoint does not invent a measurement."
+ ? "Open the failed measurement run. Ask an administrator to restore measurement access before retrying; this request cannot produce a measurement."
: "Rebuild the period report from the Reports panel.",
}),
{ status: 422, headers: { "Content-Type": "application/json" } },
@@ -3271,7 +3271,7 @@ describe("App, authenticated", () => {
expect(list).toHaveTextContent("TEPP measurement · Failed · Demo Corp");
expect(list).toHaveTextContent("Period report · Succeeded · Demo Corp");
expect(list).toHaveTextContent(
- "Open this run to see why it failed, then connect the measurement service and re-run.",
+ "Open this run to review the failure. Ask an administrator to restore measurement access, then re-run it.",
);
expect(list).toHaveTextContent("3 documents");
expect(list).not.toHaveTextContent("postgresql://");
@@ -3421,7 +3421,7 @@ describe("App, authenticated", () => {
expect(screen.queryByRole("heading", { name: "Body this run knew" })).not.toBeInTheDocument();
});
- it("tells a running lineage run to refresh the durable outbox", async () => {
+ it("tells a running lineage run to refresh for completion", async () => {
stubBackend({ runningLineageRun: true });
render( );
@@ -3429,16 +3429,16 @@ describe("App, authenticated", () => {
name: "Open analysis run: Lineage reconstruction · Running · Demo Corp",
});
expect(lineageButton).toHaveTextContent(
- "Refresh this run. Start already queued the work on the durable outbox.",
+ "Refresh this run to check whether the work has finished.",
);
await userEvent.click(lineageButton);
expect(screen.getByRole("button", { name: "Start reconstruction" })).toBeInTheDocument();
expect(
- screen.getAllByText("Refresh this run. Start already queued the work on the durable outbox."),
+ screen.getAllByText("Refresh this run to check whether the work has finished."),
).not.toHaveLength(0);
});
- it("does not tell a failed lineage run to connect the measurement service", async () => {
+ it("does not tell a failed lineage run to restore measurement access", async () => {
stubBackend({ failedLineageRun: true });
render( );
@@ -3454,7 +3454,7 @@ describe("App, authenticated", () => {
);
expect(lineageButton).not.toHaveTextContent("measurement service");
expect(teppButton).toHaveTextContent(
- "Open this run to see why it failed, then connect the measurement service and re-run.",
+ "Open this run to review the failure. Ask an administrator to restore measurement access, then re-run it.",
);
expect(teppButton).not.toHaveTextContent("reconstruction");
});
@@ -3723,7 +3723,7 @@ describe("App, authenticated", () => {
}
});
- it("does not tell a failed period report to connect the measurement service", async () => {
+ it("does not tell a failed period report to restore measurement access", async () => {
stubBackend({ failedReportRun: true });
render( );
@@ -3800,7 +3800,7 @@ describe("App, authenticated", () => {
);
expect(
await screen.findByText(
- "Connect a TEPP transport from this Failed row. Request a lineage reconstruction does not invent a measurement.",
+ "Ask an administrator to restore measurement access, then re-run this analysis. A lineage reconstruction will not produce a measurement.",
),
).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Request a new TEPP measurement" })).not.toBeInTheDocument();
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 9a6ad0103..cc2b0ae93 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -2688,9 +2688,9 @@ function analysisRunNextAction(run: AnalysisRun): string | null {
case "analysis_status_failed":
switch (run.run_kind_code) {
case "analysis_run_tepp":
- return "Open this run to see why it failed, then connect the measurement service and re-run.";
+ return "Open this run to review the failure. Ask an administrator to restore measurement access, then re-run it.";
case "analysis_run_topic_lineage":
- return "Open this run to see why it failed, then connect the TEPP transport and re-run.";
+ return "Open this run to review the failure. Ask an administrator to restore topic measurement access, then re-run it.";
case "analysis_run_lineage":
return "Open this run to see why it failed, then retry reconstruction from a current snapshot.";
case "analysis_run_report":
@@ -2701,7 +2701,7 @@ function analysisRunNextAction(run: AnalysisRun): string | null {
}
}
case "analysis_status_running":
- return "Refresh this run. Start already queued the work on the durable outbox.";
+ return "Refresh this run to check whether the work has finished.";
case "analysis_status_succeeded":
case "analysis_status_cancelled":
case null:
@@ -3202,10 +3202,10 @@ function AnalysisRunsPanel({
{analysisRunCanRequestTeppRetry(selected) && (
{selected.run_kind_code === "analysis_run_topic_lineage"
- ? "Connect a TEPP transport from this Failed row. Request a " +
- "lineage reconstruction does not invent a topic model."
- : "Connect a TEPP transport from this Failed row. Request a lineage " +
- "reconstruction does not invent a measurement."}
+ ? "Ask an administrator to restore topic measurement access, then re-run this analysis. " +
+ "A lineage reconstruction will not produce a topic result."
+ : "Ask an administrator to restore measurement access, then re-run this analysis. " +
+ "A lineage reconstruction will not produce a measurement."}
)}
{analysisRunReportPeriod(selected) && onSelectReportPeriod && (
diff --git a/tests/test_analysis_run_create.py b/tests/test_analysis_run_create.py
index ef9dc9f71..38a202513 100644
--- a/tests/test_analysis_run_create.py
+++ b/tests/test_analysis_run_create.py
@@ -144,11 +144,11 @@ def test_create_rejects_tepp_and_report_kinds_without_a_fake_score() -> None:
with pytest.raises(AnalysisRunCreateError) as tepp:
_require_lineage_create_kind("analysis_run_tepp")
assert tepp.value.status_code == 422
- assert "invent a measurement" in tepp.value.detail
+ assert "cannot produce a measurement" in tepp.value.detail
with pytest.raises(AnalysisRunCreateError) as topic_lineage:
_require_lineage_create_kind("analysis_run_topic_lineage")
assert topic_lineage.value.status_code == 422
- assert "invent a topic model" in topic_lineage.value.detail
+ assert "cannot produce a topic model" in topic_lineage.value.detail
with pytest.raises(AnalysisRunCreateError) as report:
_require_lineage_create_kind("analysis_run_report")
assert report.value.status_code == 422
@@ -180,7 +180,7 @@ async def _run() -> None:
idempotency_key="client-key-1",
)
assert err.value.status_code == 422
- assert "invent a measurement" in err.value.detail
+ assert "cannot produce a measurement" in err.value.detail
asyncio.run(_run())
diff --git a/tests/test_analysis_run_outbox.py b/tests/test_analysis_run_outbox.py
index 240006ba1..1f82c28ba 100644
--- a/tests/test_analysis_run_outbox.py
+++ b/tests/test_analysis_run_outbox.py
@@ -102,4 +102,4 @@ def test_period_report_never_enters_the_start_outbox() -> None:
report = start_kind_rejection("analysis_run_report")
assert report is not None
assert report.status_code == 422
- assert "invent a measurement" in report.detail
+ assert "cannot produce a measurement" in report.detail
diff --git a/tests/test_analysis_run_start.py b/tests/test_analysis_run_start.py
index 8f508e954..aa5b5ed38 100644
--- a/tests/test_analysis_run_start.py
+++ b/tests/test_analysis_run_start.py
@@ -200,7 +200,7 @@ def test_period_report_start_is_unprocessable_and_tepp_is_allowed() -> None:
report = start_kind_rejection("analysis_run_report")
assert report is not None
assert report.status_code == 422
- assert "invent a measurement" in report.detail
+ assert "cannot produce a measurement" in report.detail
assert "period report" in report.detail
assert start_kind_rejection("analysis_run_lineage") is None
assert start_kind_rejection("analysis_run_tepp") is None
From efe864e5faf0ea657342270eee9e0e501e6dddfc Mon Sep 17 00:00:00 2001
From: Codex
Date: Wed, 26 Aug 2026 23:12:33 +0900
Subject: [PATCH 17/21] fix(ui): keep recovery guidance product-facing
---
backend/app/global_ask_queue.py | 2 +-
frontend/src/App.test.tsx | 7 ++++++-
frontend/src/App.tsx | 4 ++--
frontend/src/i18n.ts | 8 ++++----
tests/test_global_ask_queue.py | 13 +++++++++++++
5 files changed, 26 insertions(+), 8 deletions(-)
diff --git a/backend/app/global_ask_queue.py b/backend/app/global_ask_queue.py
index 9bffd8502..4314ee3af 100644
--- a/backend/app/global_ask_queue.py
+++ b/backend/app/global_ask_queue.py
@@ -168,7 +168,7 @@ def _verification_next_action(status_code: str) -> str:
return {
VERIFICATION_SKIPPED: "Enable public verification to check eligible public claims.",
- VERIFICATION_UNAVAILABLE: "Configure public search and contextual-orchestrator, then retry.",
+ VERIFICATION_UNAVAILABLE: "Review the internal citations now, then retry public verification when it is available.",
VERIFICATION_NO_PUBLIC_CLAIMS: "Inspect the internal cited posts; no public claim was eligible.",
VERIFICATION_COMPLETED: "Inspect public evidence separately before any governed graph review.",
CLAIM_NOT_ENOUGH_INFORMATION: "Collect stronger authoritative evidence before accepting the claim.",
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 9ea944ef3..10b2cbbbe 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -3369,7 +3369,12 @@ describe("App, authenticated", () => {
).toBeInTheDocument();
const teppHistory = screen.getByRole("list", { name: "Analysis run status history" });
expect(teppHistory).toHaveTextContent("Failed 2026-01-12 12:37 · tepp_not_available");
- expect(screen.getByText(/cutoff corpus TEPP would measure/i)).toBeInTheDocument();
+ expect(
+ screen.getByText(
+ "These posts are the cutoff corpus TEPP would measure. Review the failure details, then ask a workspace administrator to restore measurement access before re-running for a calibrated result.",
+ ),
+ ).toBeInTheDocument();
+ expect(screen.queryByText(/TEPP transport/i)).not.toBeInTheDocument();
expect(teppHistory).not.toHaveTextContent("Succeeded");
});
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index cc2b0ae93..e3cbdb22e 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -2761,8 +2761,8 @@ function analysisRunCorpusHint(run: AnalysisRun): string | null {
switch (run.status_code) {
case "analysis_status_failed":
return (
- `These posts are the cutoff corpus ${service} would ${verb}. Connect a TEPP ` +
- `transport, then re-run, to replace Failed with ${result}.`
+ `These posts are the cutoff corpus ${service} would ${verb}. Review the failure details, ` +
+ `then ask a workspace administrator to restore measurement access before re-running for ${result}.`
);
case "analysis_status_succeeded":
return `These posts are the cutoff corpus this ${service} run ${verbPast}.`;
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index 8eaf1d14a..472bc4718 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -191,7 +191,7 @@ const TRANSLATIONS: Partial>> = {
"Conflicts with public evidence": "공개 근거와 충돌합니다",
"Not enough public information": "공개 정보가 충분하지 않습니다",
"Enable public verification to check eligible public claims.": "검증 가능한 공개 주장을 확인하려면 공개 자료 검증을 켜세요.",
- "Configure public search and contextual-orchestrator, then retry.": "공개 검색과 contextual-orchestrator를 구성한 후 다시 시도하세요.",
+ "Review the internal citations now, then retry public verification when it is available.": "지금 내부 인용 기록을 검토한 다음 공개 검증을 사용할 수 있게 되면 다시 시도하세요.",
"Inspect the internal cited posts; no public claim was eligible.": "검증 가능한 공개 주장이 없으므로 내부 인용 글을 확인하세요.",
"Inspect public evidence separately before any governed graph review.": "거버넌스 그래프 검토 전에 공개 근거를 별도로 확인하세요.",
"Collect stronger authoritative evidence before accepting the claim.": "주장을 받아들이기 전에 더 강한 권위 있는 근거를 확보하세요.",
@@ -718,7 +718,7 @@ const TRANSLATIONS: Partial>> = {
"Conflicts with public evidence": "与公开证据冲突",
"Not enough public information": "公开信息不足",
"Enable public verification to check eligible public claims.": "启用公开资料核验以检查符合条件的声明。",
- "Configure public search and contextual-orchestrator, then retry.": "配置公开搜索和 contextual-orchestrator 后重试。",
+ "Review the internal citations now, then retry public verification when it is available.": "请先查看内部引用记录,并在公共验证可用后重试。",
"Inspect the internal cited posts; no public claim was eligible.": "没有符合条件的公开声明,请检查内部引用文章。",
"Inspect public evidence separately before any governed graph review.": "在治理图谱审查前单独检查公开证据。",
"Collect stronger authoritative evidence before accepting the claim.": "接受该声明前,请收集更有力的权威证据。",
@@ -1262,7 +1262,7 @@ const TRANSLATIONS: Partial>> = {
"Conflicts with public evidence": "公開証拠と矛盾しています",
"Not enough public information": "公開情報が不十分です",
"Enable public verification to check eligible public claims.": "対象となる主張を確認するには公開検証を有効にしてください。",
- "Configure public search and contextual-orchestrator, then retry.": "公開検索と contextual-orchestrator を設定して再試行してください。",
+ "Review the internal citations now, then retry public verification when it is available.": "まず内部の引用記録を確認し、公開検証が利用可能になったら再試行してください。",
"Inspect the internal cited posts; no public claim was eligible.": "対象となる公開主張がないため、内部の引用投稿を確認してください。",
"Inspect public evidence separately before any governed graph review.": "管理対象グラフをレビューする前に公開証拠を別途確認してください。",
"Collect stronger authoritative evidence before accepting the claim.": "主張を受け入れる前に、より強い権威ある証拠を集めてください。",
@@ -1785,7 +1785,7 @@ const TRANSLATIONS: Partial>> = {
"Conflicts with public evidence": "Mâu thuẫn với bằng chứng công khai",
"Not enough public information": "Không đủ thông tin công khai",
"Enable public verification to check eligible public claims.": "Bật xác minh công khai để kiểm tra các tuyên bố đủ điều kiện.",
- "Configure public search and contextual-orchestrator, then retry.": "Cấu hình tìm kiếm công khai và contextual-orchestrator rồi thử lại.",
+ "Review the internal citations now, then retry public verification when it is available.": "Hãy xem các bản ghi nội bộ được trích dẫn ngay bây giờ, rồi thử lại khi xác minh công khai khả dụng.",
"Inspect the internal cited posts; no public claim was eligible.": "Không có tuyên bố công khai đủ điều kiện; hãy xem các bài viết nội bộ được trích dẫn.",
"Inspect public evidence separately before any governed graph review.": "Kiểm tra riêng bằng chứng công khai trước khi rà soát đồ thị được quản trị.",
"Collect stronger authoritative evidence before accepting the claim.": "Thu thập bằng chứng có thẩm quyền mạnh hơn trước khi chấp nhận tuyên bố.",
diff --git a/tests/test_global_ask_queue.py b/tests/test_global_ask_queue.py
index fed1b90d7..7c26a3f3c 100644
--- a/tests/test_global_ask_queue.py
+++ b/tests/test_global_ask_queue.py
@@ -172,6 +172,19 @@ def verify(self, _claim):
assert results == ()
+def test_public_verification_unavailable_next_action_is_product_facing() -> None:
+ """Unavailable verification guides the reader without naming providers."""
+ next_action = global_ask_queue._verification_next_action(
+ cv.VERIFICATION_UNAVAILABLE
+ )
+
+ assert next_action == (
+ "Review the internal citations now, then retry public verification when "
+ "it is available."
+ )
+ assert "contextual-orchestrator" not in next_action
+
+
def test_question_embedding_finishes_before_global_ask_acquires_a_pool_slot(
monkeypatch,
) -> None:
From 53091d564b5d6abf9583e5d58aac7d1f8f23ba43 Mon Sep 17 00:00:00 2001
From: Codex
Date: Thu, 27 Aug 2026 01:35:01 +0900
Subject: [PATCH 18/21] fix(ui): align analysis next actions
---
frontend/src/App.test.tsx | 44 ++++++++++++++++++++++++++++++++++-----
frontend/src/App.tsx | 13 ++++++------
frontend/src/i18n.test.ts | 1 +
frontend/src/i18n.ts | 24 +++++++++++++++++----
4 files changed, 67 insertions(+), 15 deletions(-)
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 10b2cbbbe..7fd326247 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -106,6 +106,8 @@ describe("App, authenticated", () => {
succeededReportRun?: boolean;
succeededTeppRun?: boolean;
pendingTeppRun?: boolean;
+ topicLineageRun?: boolean;
+ emptyAnalysisRuns?: boolean;
pluralAffiliations?: boolean;
deferMe?: boolean;
deferPostOne?: boolean;
@@ -377,8 +379,10 @@ describe("App, authenticated", () => {
return Promise.resolve(
jsonResponse({
analysis_run_id: "run-demo-tepp",
- run_kind_code: "analysis_run_tepp",
- run_kind_label: "TEPP measurement",
+ run_kind_code: options?.topicLineageRun
+ ? "analysis_run_topic_lineage"
+ : "analysis_run_tepp",
+ run_kind_label: options?.topicLineageRun ? "Topic lineage" : "TEPP measurement",
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
@@ -686,7 +690,7 @@ describe("App, authenticated", () => {
if (url.endsWith("/api/analysis-runs")) {
return Promise.resolve(
jsonResponse({
- analysis_runs: [
+ analysis_runs: options?.emptyAnalysisRuns ? [] : [
...(createdPendingLineage ? [createdPendingLineage] : []),
...(createdPendingTepp ? [createdPendingTepp] : []),
{
@@ -721,8 +725,10 @@ describe("App, authenticated", () => {
},
{
analysis_run_id: "run-demo-tepp",
- run_kind_code: "analysis_run_tepp",
- run_kind_label: "TEPP measurement",
+ run_kind_code: options?.topicLineageRun
+ ? "analysis_run_topic_lineage"
+ : "analysis_run_tepp",
+ run_kind_label: options?.topicLineageRun ? "Topic lineage" : "TEPP measurement",
scope_kind_code: "analysis_scope_corporate_entity",
scope_kind_label: "Corporate entity",
scope_entity_name: "Demo Corp",
@@ -3378,6 +3384,34 @@ describe("App, authenticated", () => {
expect(teppHistory).not.toHaveTextContent("Succeeded");
});
+ it("names the exact reconstruction action when no analysis runs are available", async () => {
+ stubBackend({ emptyAnalysisRuns: true });
+ render( );
+
+ expect(
+ await screen.findByText(
+ "No analysis runs are available. Select Request a lineage reconstruction, or ask an administrator to check your data access.",
+ ),
+ ).toBeInTheDocument();
+ expect(screen.getByRole("button", { name: "Request a lineage reconstruction" })).toBeInTheDocument();
+ });
+
+ it("directs a failed topic-lineage run to restore topic measurement access", async () => {
+ stubBackend({ topicLineageRun: true });
+ render( );
+
+ await userEvent.click(
+ await screen.findByRole("button", {
+ name: "Open analysis run: Topic lineage · Failed · Demo Corp",
+ }),
+ );
+ expect(
+ screen.getByText(
+ "These posts are the cutoff corpus topic-lineage would thread. Review the failure details, then ask a workspace administrator to restore topic measurement access before re-running for a topic-identity result.",
+ ),
+ ).toBeInTheDocument();
+ });
+
it("warns that a cutoff-rewritten title opens the live body, not a snapshot", async () => {
stubBackend();
render( );
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index e3cbdb22e..5b99e6ad7 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -2755,6 +2755,7 @@ function analysisRunCorpusHint(run: AnalysisRun): string | null {
const isTopicLineage = run.run_kind_code === "analysis_run_topic_lineage";
if (run.run_kind_code !== "analysis_run_tepp" && !isTopicLineage) return null;
const service = isTopicLineage ? "topic-lineage" : "TEPP";
+ const access = isTopicLineage ? "topic measurement access" : "measurement access";
const result = isTopicLineage ? "a topic-identity result" : "a calibrated result";
const verb = isTopicLineage ? "thread" : "measure";
const verbPast = isTopicLineage ? "threaded" : "measured";
@@ -2762,7 +2763,7 @@ function analysisRunCorpusHint(run: AnalysisRun): string | null {
case "analysis_status_failed":
return (
`These posts are the cutoff corpus ${service} would ${verb}. Review the failure details, ` +
- `then ask a workspace administrator to restore measurement access before re-running for ${result}.`
+ `then ask a workspace administrator to restore ${access} before re-running for ${result}.`
);
case "analysis_status_succeeded":
return `These posts are the cutoff corpus this ${service} run ${verbPast}.`;
@@ -3016,12 +3017,12 @@ function AnalysisRunsPanel({
const inFlightKeyRef = useRef(null);
const entitiesReady = corporateEntities !== null && entitiesLoadError === null;
const requestLabel = requesting
- ? "Recording the run..."
+ ? t("Recording the run...")
: entitiesLoadError
- ? "Reload to choose a corporate entity"
+ ? t("Reload to choose a corporate entity")
: corporateEntities === null
- ? "Loading affiliated entities..."
- : "Request a lineage reconstruction";
+ ? t("Loading affiliated entities...")
+ : t("Request a lineage reconstruction");
useEffect(() => {
fetchAnalysisRuns(accessToken)
@@ -3139,7 +3140,7 @@ function AnalysisRunsPanel({
{(error || entitiesLoadError) && {error ?? entitiesLoadError}
}
{runs.length === 0 ? (
- {t("No analysis runs are available. Select Request lineage reconstruction, or ask an administrator to check your data access.")}
+ {t("No analysis runs are available. Select Request a lineage reconstruction, or ask an administrator to check your data access.")}
) : (
diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts
index 3ef5cfcce..7c4bf2cd8 100644
--- a/frontend/src/i18n.test.ts
+++ b/frontend/src/i18n.test.ts
@@ -72,6 +72,7 @@ describe("i18n", () => {
"Ontology neighborhood",
"This is an ontology neighborhood, not Event Lineage.",
"Rankings",
+ "Request a lineage reconstruction",
"Title overlap",
"Open a result to review why it appears in this ranking. These results are not calibrated measurements.",
"Workspace navigation",
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index 472bc4718..27339d9ff 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -150,7 +150,11 @@ const TRANSLATIONS: Partial>> = {
"Board posts": "게시판 글",
"No posts match the current filters.": "현재 필터에 맞는 글이 없습니다.",
"No posts are available. Ask an administrator to check your access or import source records.": "표시할 글이 없습니다. 관리자에게 접근 권한을 확인하거나 원본 기록을 가져오도록 요청하세요.",
- "No analysis runs are available. Select Request lineage reconstruction, or ask an administrator to check your data access.": "사용 가능한 분석 실행이 없습니다. 계보 재구성 요청을 선택하거나 관리자에게 데이터 접근 권한을 확인하도록 요청하세요.",
+ "No analysis runs are available. Select Request a lineage reconstruction, or ask an administrator to check your data access.": "사용 가능한 분석 실행이 없습니다. 계보 재구성 요청을 선택하거나 관리자에게 데이터 접근 권한을 확인하도록 요청하세요.",
+ "Recording the run...": "분석 실행을 기록하는 중...",
+ "Reload to choose a corporate entity": "법인을 선택하려면 다시 불러오세요",
+ "Loading affiliated entities...": "연결된 법인을 불러오는 중...",
+ "Request a lineage reconstruction": "계보 재구성 요청",
"Customer master": "고객 마스터",
"Ask Agent": "Ask Agent",
"Workspace navigation": "워크스페이스 메뉴",
@@ -677,7 +681,11 @@ const TRANSLATIONS: Partial>> = {
"Board posts": "看板文章",
"No posts match the current filters.": "没有文章符合当前筛选条件。",
"No posts are available. Ask an administrator to check your access or import source records.": "没有可用文章。请让管理员检查您的访问权限或导入源记录。",
- "No analysis runs are available. Select Request lineage reconstruction, or ask an administrator to check your data access.": "没有可用的分析运行。请选择“请求谱系重建”,或让管理员检查您的数据访问权限。",
+ "No analysis runs are available. Select Request a lineage reconstruction, or ask an administrator to check your data access.": "没有可用的分析运行。请选择“请求谱系重建”,或让管理员检查您的数据访问权限。",
+ "Recording the run...": "正在记录分析运行...",
+ "Reload to choose a corporate entity": "重新加载以选择法人实体",
+ "Loading affiliated entities...": "正在加载关联法人实体...",
+ "Request a lineage reconstruction": "请求谱系重建",
"Customer master": "客户主数据",
"Ask Agent": "Ask Agent",
"Workspace navigation": "工作区导航",
@@ -1221,7 +1229,11 @@ const TRANSLATIONS: Partial>> = {
"Board posts": "掲示板の投稿",
"No posts match the current filters.": "現在の絞り込みに一致する投稿はありません。",
"No posts are available. Ask an administrator to check your access or import source records.": "利用できる投稿がありません。管理者にアクセス権の確認または原典記録の取り込みを依頼してください。",
- "No analysis runs are available. Select Request lineage reconstruction, or ask an administrator to check your data access.": "利用できる分析実行がありません。「系譜の再構築を依頼」を選択するか、管理者にデータアクセスの確認を依頼してください。",
+ "No analysis runs are available. Select Request a lineage reconstruction, or ask an administrator to check your data access.": "利用できる分析実行がありません。「系譜の再構築を依頼」を選択するか、管理者にデータアクセスの確認を依頼してください。",
+ "Recording the run...": "分析実行を記録しています...",
+ "Reload to choose a corporate entity": "法人を選択するには再読み込みしてください",
+ "Loading affiliated entities...": "関連法人を読み込んでいます...",
+ "Request a lineage reconstruction": "系譜の再構築を依頼",
"Customer master": "顧客マスター",
"Ask Agent": "Ask Agent",
"Workspace navigation": "ワークスペースナビゲーション",
@@ -1744,7 +1756,11 @@ const TRANSLATIONS: Partial>> = {
"Board posts": "Bài viết trên bảng tin",
"No posts match the current filters.": "Không có bài viết nào khớp với bộ lọc hiện tại.",
"No posts are available. Ask an administrator to check your access or import source records.": "Không có bài viết nào. Hãy yêu cầu quản trị viên kiểm tra quyền truy cập của bạn hoặc nhập các bản ghi nguồn.",
- "No analysis runs are available. Select Request lineage reconstruction, or ask an administrator to check your data access.": "Không có lượt phân tích nào. Hãy chọn Yêu cầu tái dựng phả hệ hoặc yêu cầu quản trị viên kiểm tra quyền truy cập dữ liệu của bạn.",
+ "No analysis runs are available. Select Request a lineage reconstruction, or ask an administrator to check your data access.": "Không có lượt phân tích nào. Hãy chọn Yêu cầu tái dựng phả hệ hoặc yêu cầu quản trị viên kiểm tra quyền truy cập dữ liệu của bạn.",
+ "Recording the run...": "Đang ghi lượt phân tích...",
+ "Reload to choose a corporate entity": "Tải lại để chọn pháp nhân",
+ "Loading affiliated entities...": "Đang tải các pháp nhân liên kết...",
+ "Request a lineage reconstruction": "Yêu cầu tái dựng dòng sự kiện",
"Customer master": "Danh mục khách hàng",
"Ask Agent": "Ask Agent",
"Workspace navigation": "Điều hướng không gian làm việc",
From 876129e5b1066a4bfa63e2ed06bc11db47c4cbdf Mon Sep 17 00:00:00 2001
From: Codex
Date: Thu, 27 Aug 2026 01:43:48 +0900
Subject: [PATCH 19/21] fix(i18n): align Vietnamese reconstruction action
---
frontend/src/i18n.test.ts | 12 ++++++++++++
frontend/src/i18n.ts | 2 +-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/frontend/src/i18n.test.ts b/frontend/src/i18n.test.ts
index 7c4bf2cd8..107ecae38 100644
--- a/frontend/src/i18n.test.ts
+++ b/frontend/src/i18n.test.ts
@@ -113,6 +113,18 @@ describe("i18n", () => {
},
);
+ it.each(["ko", "zh", "ja", "vi"] as const)(
+ "names the exact reconstruction button in empty-run guidance in %s",
+ (locale) => {
+ setLocale(locale);
+ expect(
+ t(
+ "No analysis runs are available. Select Request a lineage reconstruction, or ask an administrator to check your data access.",
+ ),
+ ).toContain(t("Request a lineage reconstruction"));
+ },
+ );
+
it("keeps analyst GNB chrome on the Dashboard and four Korean labels", () => {
expect(ANALYST_GNB_LABELS).toEqual(["Dashboard", "게시판", "고객 마스터", "달력", "Ask Agent"]);
expect(ANALYST_GNB_LABELS.join(" ")).not.toMatch(/Buyer|Cubee|Board|Customer master/);
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index 27339d9ff..f77901283 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -1756,7 +1756,7 @@ const TRANSLATIONS: Partial>> = {
"Board posts": "Bài viết trên bảng tin",
"No posts match the current filters.": "Không có bài viết nào khớp với bộ lọc hiện tại.",
"No posts are available. Ask an administrator to check your access or import source records.": "Không có bài viết nào. Hãy yêu cầu quản trị viên kiểm tra quyền truy cập của bạn hoặc nhập các bản ghi nguồn.",
- "No analysis runs are available. Select Request a lineage reconstruction, or ask an administrator to check your data access.": "Không có lượt phân tích nào. Hãy chọn Yêu cầu tái dựng phả hệ hoặc yêu cầu quản trị viên kiểm tra quyền truy cập dữ liệu của bạn.",
+ "No analysis runs are available. Select Request a lineage reconstruction, or ask an administrator to check your data access.": "Không có lượt phân tích nào. Hãy chọn Yêu cầu tái dựng dòng sự kiện hoặc yêu cầu quản trị viên kiểm tra quyền truy cập dữ liệu của bạn.",
"Recording the run...": "Đang ghi lượt phân tích...",
"Reload to choose a corporate entity": "Tải lại để chọn pháp nhân",
"Loading affiliated entities...": "Đang tải các pháp nhân liên kết...",
From b05e31001a890b3fad30def2a2cad67a2d183418 Mon Sep 17 00:00:00 2001
From: Codex
Date: Thu, 27 Aug 2026 04:56:00 +0900
Subject: [PATCH 20/21] fix(ui): keep analysis guidance customer-facing
---
frontend/src/App.test.tsx | 18 ++++++++++--------
frontend/src/App.tsx | 12 +-----------
frontend/src/i18n.ts | 8 ++++----
3 files changed, 15 insertions(+), 23 deletions(-)
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index 7fd326247..d387123b8 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -2720,7 +2720,11 @@ describe("App, authenticated", () => {
await userEvent.click(await screen.findByRole("button", { name: /verify against web search/i }));
await waitFor(() =>
- expect(screen.getByText("Verification unavailable (search is not configured).")).toBeInTheDocument(),
+ expect(
+ screen.getByText(
+ "Public verification is temporarily unavailable. Review the saved evidence, then retry later.",
+ ),
+ ).toBeInTheDocument(),
);
expect(screen.queryByText(/HTTP 503/)).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /verify against web search/i })).not.toBeInTheDocument();
@@ -3315,11 +3319,8 @@ describe("App, authenticated", () => {
expect(history).toHaveTextContent("Pending 2026-01-12 12:31");
expect(history).toHaveTextContent("Running 2026-01-12 12:32");
expect(history).toHaveTextContent("Succeeded 2026-01-12 12:33");
- const outbox = screen.getByRole("list", { name: "Analysis run outbox delivery" });
- expect(outbox).toHaveTextContent("Claimed 2026-01-12 12:32");
- expect(outbox).toHaveTextContent("Delivered 2026-01-12 12:33");
- expect(outbox).not.toHaveTextContent("valkey");
- expect(outbox).not.toHaveTextContent("stream");
+ expect(screen.queryByText("tepp_not_available")).not.toBeInTheDocument();
+ expect(screen.queryByRole("list", { name: "Analysis run outbox delivery" })).not.toBeInTheDocument();
expect(screen.getByRole("list", { name: "Posts known at this run cutoff" })).toBeInTheDocument();
const seededFork = screen.getByRole("list", { name: "Reconstructed lineage edges" });
expect(seededFork).toHaveTextContent(
@@ -3374,7 +3375,8 @@ describe("App, authenticated", () => {
await screen.findByRole("heading", { name: "TEPP measurement · Failed · Demo Corp" }),
).toBeInTheDocument();
const teppHistory = screen.getByRole("list", { name: "Analysis run status history" });
- expect(teppHistory).toHaveTextContent("Failed 2026-01-12 12:37 · tepp_not_available");
+ expect(teppHistory).toHaveTextContent("Failed 2026-01-12 12:37");
+ expect(teppHistory).not.toHaveTextContent("tepp_not_available");
expect(
screen.getByText(
"These posts are the cutoff corpus TEPP would measure. Review the failure details, then ask a workspace administrator to restore measurement access before re-running for a calibrated result.",
@@ -3819,7 +3821,7 @@ describe("App, authenticated", () => {
expect(
await screen.findByRole("heading", { name: "TEPP measurement · Failed · Demo Corp" }),
).toBeInTheDocument();
- expect(screen.getByText(/tepp_not_available/)).toBeInTheDocument();
+ expect(screen.queryByText(/tepp_not_available/)).not.toBeInTheDocument();
expect(screen.queryByText(/theta/i)).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Start reconstruction" })).not.toBeInTheDocument();
const startCall = fetchMock.mock.calls.find((call) =>
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 5b99e6ad7..45e311835 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -157,7 +157,7 @@ function LanguageSwitcher({ accessToken }: { accessToken?: string }) {
function searchUnavailableMessage(err: unknown): string {
if (err instanceof BackendError && err.status === 503) {
- return t("Verification unavailable (search is not configured).");
+ return t("Public verification is temporarily unavailable. Review the saved evidence, then retry later.");
}
return String(err);
}
@@ -3277,16 +3277,6 @@ function AnalysisRunsPanel({
{selected.status_history.map((event) => (
{event.status_label} {event.occurred_at.slice(0, 16).replace("T", " ")}
- {event.failure_code ? ` · ${event.failure_code}` : ""}
-
- ))}
-
- )}
- {selected.outbox_deliveries && selected.outbox_deliveries.length > 0 && (
-
- {selected.outbox_deliveries.map((event) => (
-
- {event.delivery_status_label} {event.occurred_at.slice(0, 16).replace("T", " ")}
))}
diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts
index f77901283..70a46161c 100644
--- a/frontend/src/i18n.ts
+++ b/frontend/src/i18n.ts
@@ -76,7 +76,7 @@ const TRANSLATIONS: Partial>> = {
"Supporting record text": "뒷받침하는 기록 본문",
"Recorded evidence": "기록된 근거",
"Lineage maintenance": "계보 관리",
- "Verification unavailable (search is not configured).": "검증을 사용할 수 없습니다(검색이 설정되지 않았습니다).",
+ "Public verification is temporarily unavailable. Review the saved evidence, then retry later.": "공개 검증을 일시적으로 사용할 수 없습니다. 저장된 근거를 검토한 다음 나중에 다시 시도하세요.",
"No customer commitment found in this post.": "이 글에서 고객 약속을 찾지 못했습니다.",
due: "기한",
"Ticket created": "티켓 생성",
@@ -607,7 +607,7 @@ const TRANSLATIONS: Partial>> = {
"Supporting record text": "支持此项的记录正文",
"Recorded evidence": "已记录的证据",
"Lineage maintenance": "谱系维护",
- "Verification unavailable (search is not configured).": "无法验证(未配置搜索)。",
+ "Public verification is temporarily unavailable. Review the saved evidence, then retry later.": "公开验证暂时不可用。请先查看已保存的依据,稍后再试。",
"No customer commitment found in this post.": "未在此文章中找到客户承诺。",
due: "截止日期",
"Ticket created": "工单已创建",
@@ -1155,7 +1155,7 @@ const TRANSLATIONS: Partial>> = {
"Supporting record text": "根拠となる記録本文",
"Recorded evidence": "記録された証拠",
"Lineage maintenance": "系譜管理",
- "Verification unavailable (search is not configured).": "確認できません(検索が設定されていません)。",
+ "Public verification is temporarily unavailable. Review the saved evidence, then retry later.": "公開情報の確認は一時的に利用できません。保存済みの根拠を確認し、後で再試行してください。",
"No customer commitment found in this post.": "この投稿に顧客コミットメントは見つかりませんでした。",
due: "期限",
"Ticket created": "チケットを作成",
@@ -1682,7 +1682,7 @@ const TRANSLATIONS: Partial>> = {
"Supporting record text": "Nội dung bản ghi làm căn cứ",
"Recorded evidence": "Bằng chứng đã ghi nhận",
"Lineage maintenance": "Bảo trì dòng sự kiện",
- "Verification unavailable (search is not configured).": "Không thể xác minh (chưa cấu hình tìm kiếm).",
+ "Public verification is temporarily unavailable. Review the saved evidence, then retry later.": "Tạm thời không thể xác minh công khai. Hãy xem bằng chứng đã lưu, sau đó thử lại sau.",
"No customer commitment found in this post.": "Không tìm thấy cam kết của khách hàng trong bài viết này.",
due: "Hạn",
"Ticket created": "Đã tạo phiếu",
From d7f3e609aac29ae00a45a37d5764c9648518d8e9 Mon Sep 17 00:00:00 2001
From: Codex
Date: Thu, 27 Aug 2026 07:17:20 +0900
Subject: [PATCH 21/21] fix(ui): give failed runs actionable recovery copy
---
frontend/src/App.test.tsx | 12 ++++++------
frontend/src/App.tsx | 18 ++++++++----------
2 files changed, 14 insertions(+), 16 deletions(-)
diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx
index d387123b8..fb4398235 100644
--- a/frontend/src/App.test.tsx
+++ b/frontend/src/App.test.tsx
@@ -3281,7 +3281,7 @@ describe("App, authenticated", () => {
expect(list).toHaveTextContent("TEPP measurement · Failed · Demo Corp");
expect(list).toHaveTextContent("Period report · Succeeded · Demo Corp");
expect(list).toHaveTextContent(
- "Open this run to review the failure. Ask an administrator to restore measurement access, then re-run it.",
+ "Ask an administrator to restore measurement access, then re-run this measurement.",
);
expect(list).toHaveTextContent("3 documents");
expect(list).not.toHaveTextContent("postgresql://");
@@ -3379,7 +3379,7 @@ describe("App, authenticated", () => {
expect(teppHistory).not.toHaveTextContent("tepp_not_available");
expect(
screen.getByText(
- "These posts are the cutoff corpus TEPP would measure. Review the failure details, then ask a workspace administrator to restore measurement access before re-running for a calibrated result.",
+ "These posts are the cutoff corpus TEPP would measure. Ask a workspace administrator to restore measurement access before re-running for a calibrated result.",
),
).toBeInTheDocument();
expect(screen.queryByText(/TEPP transport/i)).not.toBeInTheDocument();
@@ -3409,7 +3409,7 @@ describe("App, authenticated", () => {
);
expect(
screen.getByText(
- "These posts are the cutoff corpus topic-lineage would thread. Review the failure details, then ask a workspace administrator to restore topic measurement access before re-running for a topic-identity result.",
+ "These posts are the cutoff corpus topic-lineage would thread. Ask a workspace administrator to restore topic measurement access before re-running for a topic-identity result.",
),
).toBeInTheDocument();
});
@@ -3491,11 +3491,11 @@ describe("App, authenticated", () => {
name: "Open analysis run: TEPP measurement · Failed · Demo Corp",
});
expect(lineageButton).toHaveTextContent(
- "Open this run to see why it failed, then retry reconstruction from a current snapshot.",
+ "Retry reconstruction from a current snapshot.",
);
expect(lineageButton).not.toHaveTextContent("measurement service");
expect(teppButton).toHaveTextContent(
- "Open this run to review the failure. Ask an administrator to restore measurement access, then re-run it.",
+ "Ask an administrator to restore measurement access, then re-run this measurement.",
);
expect(teppButton).not.toHaveTextContent("reconstruction");
});
@@ -3772,7 +3772,7 @@ describe("App, authenticated", () => {
name: "Open analysis run: Period report · Failed · Demo Corp",
});
expect(reportButton).toHaveTextContent(
- "Open this run to see why it failed, then rebuild the period report from a current snapshot.",
+ "Rebuild the period report from a current snapshot.",
);
expect(reportButton).not.toHaveTextContent("measurement service");
expect(reportButton).not.toHaveTextContent("reconstruction");
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 45e311835..3c378c80c 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -2663,10 +2663,8 @@ function analysisRunCaption(run: AnalysisRun): string {
/**
* Next action for a pending or failed run on the home list and detail.
*
- * The machine `failure_code` stays on detail history (ADR 0014). Copy
- * is pinned to registered kinds so a pending TEPP row is not mistaken
- * for reconstruction, and a failed lineage row is not mistaken for a
- * missing TEPP transport.
+ * Machine failure codes remain outside the customer view. Copy is pinned to
+ * registered kinds so each run offers the recovery action that can help.
*/
function analysisRunNextAction(run: AnalysisRun): string | null {
switch (run.status_code) {
@@ -2688,13 +2686,13 @@ function analysisRunNextAction(run: AnalysisRun): string | null {
case "analysis_status_failed":
switch (run.run_kind_code) {
case "analysis_run_tepp":
- return "Open this run to review the failure. Ask an administrator to restore measurement access, then re-run it.";
+ return "Ask an administrator to restore measurement access, then re-run this measurement.";
case "analysis_run_topic_lineage":
- return "Open this run to review the failure. Ask an administrator to restore topic measurement access, then re-run it.";
+ return "Ask an administrator to restore topic measurement access, then re-run this topic analysis.";
case "analysis_run_lineage":
- return "Open this run to see why it failed, then retry reconstruction from a current snapshot.";
+ return "Retry reconstruction from a current snapshot.";
case "analysis_run_report":
- return "Open this run to see why it failed, then rebuild the period report from a current snapshot.";
+ return "Rebuild the period report from a current snapshot.";
default: {
const unexpected: never = run.run_kind_code;
return unexpected;
@@ -2762,8 +2760,8 @@ function analysisRunCorpusHint(run: AnalysisRun): string | null {
switch (run.status_code) {
case "analysis_status_failed":
return (
- `These posts are the cutoff corpus ${service} would ${verb}. Review the failure details, ` +
- `then ask a workspace administrator to restore ${access} before re-running for ${result}.`
+ `These posts are the cutoff corpus ${service} would ${verb}. ` +
+ `Ask a workspace administrator to restore ${access} before re-running for ${result}.`
);
case "analysis_status_succeeded":
return `These posts are the cutoff corpus this ${service} run ${verbPast}.`;