From 96c6858a3caa66ce3a80961bd5cc5225983df1c8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 21:08:04 +0900 Subject: [PATCH 1/3] feat: show project history lineage counts --- .../2.23.2-project-history-lineage-counts.md | 5 ++ ...uthorized-project-history-buyer-surface.md | 11 ++++ docs/product-technical-gap-baseline.md | 15 ++++++ docs/storybook-inventory.md | 1 + frontend/src/App.test.tsx | 2 + .../AskProjectHistoryLinks.test.tsx | 2 + .../ProjectHistoryTimeline.stories.tsx | 2 + .../ProjectHistoryTimeline.tepp.test.tsx | 2 + .../ProjectHistoryTimeline.test.tsx | 3 ++ .../src/components/ProjectHistoryTimeline.tsx | 2 + frontend/src/projectHistory.ts | 12 +++-- lineageweave/project_history.py | 32 ++++++++++++ tests/test_project_history.py | 50 +++++++++++++++++++ 13 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 CHANGELOG.d/2.23.2-project-history-lineage-counts.md diff --git a/CHANGELOG.d/2.23.2-project-history-lineage-counts.md b/CHANGELOG.d/2.23.2-project-history-lineage-counts.md new file mode 100644 index 000000000..47e62ac4a --- /dev/null +++ b/CHANGELOG.d/2.23.2-project-history-lineage-counts.md @@ -0,0 +1,5 @@ +### Added + +- Project history now shows how many authorized project posts participate in + persisted forward lineage edges and how many distinct evidence-lineage groups + those posts form. The same counts appear from post and Ask entry points. diff --git a/docs/adr/0128-authorized-project-history-buyer-surface.md b/docs/adr/0128-authorized-project-history-buyer-surface.md index c0c3b4c10..7df6bcabc 100644 --- a/docs/adr/0128-authorized-project-history-buyer-surface.md +++ b/docs/adr/0128-authorized-project-history-buyer-surface.md @@ -35,6 +35,15 @@ preserving Project History as the Event Lineage focus. Counts, display names, responsibility transitions, and related paths are bounded projections, not an HR ledger or a causal graph. +The projection reports three explicit evidence-fusion statistics over the same +authorized event set: `event_count` counts all project posts, +`connected_post_count` counts posts incident to at least one forward +`post_lineage_edge`, and `lineage_count` counts weakly connected components +containing those posts. Isolated project posts remain in `event_count` but do +not become a lineage. These are LineageWeave evidence-DAG statistics, not TEPP +topic identities; TEPP topic birth, split, merge, dormancy, and reactivation +stay on the separate fail-closed TEPP contract. + The UI uses the existing design-token and Storybook component boundary. The Figma file above is the design source for the desktop, mobile, and evidence boundary states; no second component-specific token system is introduced. @@ -45,6 +54,8 @@ boundary states; no second component-specific token system is introduced. source evidence in one workflow. - Hidden or post-cutoff records cannot affect the index, counts, transitions, or related paths. +- A reader can distinguish all project posts from posts already connected into + one or more evidence lineages without treating an isolated post as a topic. - Semantic project mentions remain visibly inferred and do not overwrite a source project identity. - The current document-time fallback remains explicit until a durable event diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 1702da9dc..17b1fe90c 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -331,6 +331,21 @@ runtime note into a shipped/live claim. - The next stacked slice attaches this same canonical timeline and TEPP metadata to Global Ask and post-scoped Ask without re-retrieving hidden evidence. +## Project-history lineage counts (2026-08-23) + +- The shared timeline now distinguishes all authorized project posts from posts + incident to a persisted forward `post_lineage_edge`, and reports the number of + weakly connected evidence-lineage components. +- Isolated project posts remain visible but do not inflate the lineage count. + These counts describe LineageWeave's evidence-fusion DAG; they are not TEPP + topic identities and do not close the TRSL-TM topic-lineage dependency. +- The same projection supplies the dedicated Project history destination, each + post's project-evidence action, post-scoped Ask, and Global Ask, so those + surfaces cannot drift into competing count definitions. +- Current evidence is source and focused tests on the new stacked slice. Protected + merge, authenticated runtime, and the TEPP topic-identity result contract remain + open and must not be represented as protected-main behavior. + ## Ask-to-project-history integration (2026-08-21) - Protected-stack checkpoint: PR #342 is based on PR #339 head diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index ddb09aea8..caa724ded 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -9,6 +9,7 @@ reader-facing control you can click before changing product CSS. | `Evidence/CitationChip` | Click a cited title to open that source post. | `--color-chip-border`, `--radius-chip`, `CitationChip` | | `AnalysisRun/CutoffKnownBody` | Read the cutoff-known sentence, then compare it with the live body below. | `--color-accent-border`, `--space-panel-block`, `--radius-panel`, `CutoffKnownBody` | | `Analysis/LineageEntityPicker` | Choose which corp to reconstruct, then click Request a lineage reconstruction. | `--space-control-gap`, `--size-control-min`, `--radius-control`, `LineageEntityPicker` | +| `Evidence/ProjectHistoryTimeline` | Compare all project posts with connected-post and evidence-lineage counts, then open the selected source. | shared spacing, status, focus, and timeline tokens; `ProjectHistoryTimeline` | | `Chrome/PopupCloseButton` | Close the evidence panel or post popup. | `--space-close-inset`, `--font-size-close`, `PopupCloseButton` | | `Evidence/LineageDag` | Inspect a branching Event Lineage, then open a record or read its evidence trail. | `--color-primary`, `--color-accent-orange`, `LineageDag` | diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index f54e5edc5..bb6a17d10 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -1914,6 +1914,8 @@ describe("App, authenticated", () => { focus_event_id: "post-1", time_basis_code: "source_post_created_at_fallback", event_count: 1, + connected_post_count: 0, + lineage_count: 0, distinct_actor_count: 0, distinct_observed_actor_count: 0, evidence_boundary_code: "authorized_visible_source_posts", diff --git a/frontend/src/components/AskProjectHistoryLinks.test.tsx b/frontend/src/components/AskProjectHistoryLinks.test.tsx index 16da5ce7c..c5973a316 100644 --- a/frontend/src/components/AskProjectHistoryLinks.test.tsx +++ b/frontend/src/components/AskProjectHistoryLinks.test.tsx @@ -23,6 +23,8 @@ const projection: ProjectHistoryProjection = { knowledge_cutoff: "2026-08-20T12:00:00Z", evidence_boundary_code: "authorized_visible_source_posts", event_count: 1, + connected_post_count: 0, + lineage_count: 0, distinct_actor_count: 0, distinct_observed_actor_count: 0, truncated: false, diff --git a/frontend/src/components/ProjectHistoryTimeline.stories.tsx b/frontend/src/components/ProjectHistoryTimeline.stories.tsx index 49d4e8675..8f33fc14a 100644 --- a/frontend/src/components/ProjectHistoryTimeline.stories.tsx +++ b/frontend/src/components/ProjectHistoryTimeline.stories.tsx @@ -60,6 +60,8 @@ const projection: ProjectHistoryProjection = { knowledge_cutoff: "2026-08-20T00:00:00Z", evidence_boundary_code: "authorized_visible_source_posts", event_count: 5, + connected_post_count: 5, + lineage_count: 1, distinct_actor_count: 3, distinct_observed_actor_count: 2, truncated: false, diff --git a/frontend/src/components/ProjectHistoryTimeline.tepp.test.tsx b/frontend/src/components/ProjectHistoryTimeline.tepp.test.tsx index e33ad1ff8..0a9fdaef3 100644 --- a/frontend/src/components/ProjectHistoryTimeline.tepp.test.tsx +++ b/frontend/src/components/ProjectHistoryTimeline.tepp.test.tsx @@ -14,6 +14,8 @@ const projection = { knowledge_cutoff: "2026-08-20T12:00:00Z", evidence_boundary_code: "authorized_visible_source_posts", event_count: 1, + connected_post_count: 0, + lineage_count: 0, distinct_actor_count: 0, distinct_observed_actor_count: 0, truncated: false, diff --git a/frontend/src/components/ProjectHistoryTimeline.test.tsx b/frontend/src/components/ProjectHistoryTimeline.test.tsx index 8b53f654b..fa55cdd13 100644 --- a/frontend/src/components/ProjectHistoryTimeline.test.tsx +++ b/frontend/src/components/ProjectHistoryTimeline.test.tsx @@ -14,6 +14,8 @@ const projection: ProjectHistoryProjection = { knowledge_cutoff: "2026-08-20T00:00:00+00:00", evidence_boundary_code: "authorized_visible_source_posts", event_count: 3, + connected_post_count: 3, + lineage_count: 1, distinct_actor_count: 2, distinct_observed_actor_count: 1, truncated: false, @@ -142,6 +144,7 @@ describe("ProjectHistoryTimeline", () => { expect(screen.getByText(/evidence gap/i, { selector: "dd" })).toBeInTheDocument(); expect(screen.getByText(/related history, not causality/i)).toBeInTheDocument(); expect(screen.getByText(/permission, visibility, publication, and cutoff gates/i)).toBeInTheDocument(); + expect(screen.getByText(/3 project posts · 3 connected · lineage count 1/i)).toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: /open source record: VOC received/i })); expect(onOpenPost).toHaveBeenCalledWith("post-voc"); diff --git a/frontend/src/components/ProjectHistoryTimeline.tsx b/frontend/src/components/ProjectHistoryTimeline.tsx index 12c7a7854..940028103 100644 --- a/frontend/src/components/ProjectHistoryTimeline.tsx +++ b/frontend/src/components/ProjectHistoryTimeline.tsx @@ -103,6 +103,8 @@ export function ProjectHistoryTimeline({

{projectHistoryText(locale, "summaryCounts", { events: projection.event_count, + connected: projection.connected_post_count, + lineages: projection.lineage_count, actors: actorCount, })}

diff --git a/frontend/src/projectHistory.ts b/frontend/src/projectHistory.ts index c976baeb3..e4b4f76eb 100644 --- a/frontend/src/projectHistory.ts +++ b/frontend/src/projectHistory.ts @@ -109,6 +109,8 @@ export interface ProjectHistoryProjection { knowledge_cutoff?: string; evidence_boundary_code?: "authorized_visible_source_posts" | string; event_count: number; + connected_post_count: number; + lineage_count: number; distinct_actor_count?: number; distinct_observed_actor_count: number; truncated: boolean; @@ -253,7 +255,7 @@ const EN: Record = { openProject: "Open project: {name}", evidenceBoundary: "Only source posts that pass the current permission, visibility, publication, and cutoff gates are included.", heading: "Project event timeline", - summaryCounts: "{events} events · {actors} actors in evidence", + summaryCounts: "{events} project posts · {connected} connected · lineage count {lineages} · {actors} actors in evidence", sourcePostTime: "Dates use source-post creation time because a separate event clock is not recorded.", documentTime: "Dates use the document time recorded by the source.", truncated: "This bounded timeline is truncated. The selected event remains included.", @@ -304,7 +306,7 @@ const MESSAGES: Record> = { openProject: "프로젝트 열기: {name}", evidenceBoundary: "현재 권한·공개 범위·게시 상태·기준 시각을 통과한 원천 게시물만 포함합니다.", heading: "프로젝트 이벤트 타임라인", - summaryCounts: "이벤트 {events}건 · 근거에 등장한 담당자 {actors}명", + summaryCounts: "프로젝트 글 {events}개 · 연결된 글 {connected}개 · Lineage {lineages}개 · 근거 담당자 {actors}명", sourcePostTime: "별도 사건 시각이 없어 날짜는 원천 게시물 생성 시각을 사용합니다.", documentTime: "날짜는 기록된 문서 시각을 사용합니다.", truncated: "이 제한된 타임라인은 일부만 표시합니다. 선택한 이벤트는 계속 포함됩니다.", @@ -352,7 +354,7 @@ const MESSAGES: Record> = { openProject: "打开项目:{name}", evidenceBoundary: "仅包含通过当前权限、可见性、发布状态和截止时间检查的源帖子。", heading: "项目事件时间线", - summaryCounts: "{events} 个事件 · 依据中出现 {actors} 名责任人", + summaryCounts: "{events} 条项目记录 · {connected} 条已连接 · {lineages} 条 Lineage · 依据中出现 {actors} 名责任人", sourcePostTime: "未记录独立事件时钟,因此日期采用源帖子创建时间。", documentTime: "日期采用记录的文档时间。", truncated: "此有界时间线已截断,但所选事件仍保留。", @@ -400,7 +402,7 @@ const MESSAGES: Record> = { openProject: "プロジェクトを開く: {name}", evidenceBoundary: "現在の権限・可視性・公開状態・基準時刻を通過した原資料だけを含みます。", heading: "プロジェクトイベントのタイムライン", - summaryCounts: "イベント {events}件 · 根拠内の担当者 {actors}名", + summaryCounts: "プロジェクト投稿 {events}件 · 接続済み {connected}件 · Lineage {lineages}件 · 根拠内の担当者 {actors}名", sourcePostTime: "独立したイベント時刻がないため、原資料の作成時刻を使用します。", documentTime: "日付は記録された文書時刻を使用します。", truncated: "この上限付きタイムラインは省略されていますが、選択イベントは保持されます。", @@ -448,7 +450,7 @@ const MESSAGES: Record> = { openProject: "Mở dự án: {name}", evidenceBoundary: "Chỉ bao gồm bài nguồn vượt qua quyền, khả năng hiển thị, trạng thái xuất bản và mốc thời gian hiện tại.", heading: "Dòng thời gian sự kiện dự án", - summaryCounts: "{events} sự kiện · {actors} người xuất hiện trong bằng chứng", + summaryCounts: "{events} bài dự án · {connected} bài đã kết nối · {lineages} lineage · {actors} người trong bằng chứng", sourcePostTime: "Không có đồng hồ sự kiện riêng, nên dùng thời gian tạo bài nguồn.", documentTime: "Ngày sử dụng thời gian tài liệu được ghi nhận.", truncated: "Dòng thời gian có giới hạn này đã bị rút gọn nhưng vẫn giữ sự kiện đang chọn.", diff --git a/lineageweave/project_history.py b/lineageweave/project_history.py index 8391cc47c..c485b585d 100644 --- a/lineageweave/project_history.py +++ b/lineageweave/project_history.py @@ -278,6 +278,35 @@ def _prior_paths( return result +def _lineage_counts( + ordered_event_ids: Sequence[str], edge_rows: Sequence[Mapping[str, Any]] +) -> tuple[int, int]: + """Count posts and components connected by distinct forward edges.""" + + event_index = {event_id: index for index, event_id in enumerate(ordered_event_ids)} + edges = { + (str(row["parent_post_id"]), str(row["child_post_id"])) + for row in edge_rows + if str(row["parent_post_id"]) in event_index + and str(row["child_post_id"]) in event_index + and event_index[str(row["parent_post_id"])] < event_index[str(row["child_post_id"])] + } + adjacency: dict[str, set[str]] = {} + for parent, child in edges: + adjacency.setdefault(parent, set()).add(child) + adjacency.setdefault(child, set()).add(parent) + remaining = set(adjacency) + lineage_count = 0 + while remaining: + lineage_count += 1 + stack = [remaining.pop()] + while stack: + neighbors = remaining.intersection(adjacency[stack.pop()]) + remaining.difference_update(neighbors) + stack.extend(neighbors) + return len(adjacency), lineage_count + + def build_project_history_projection( *, project_key: str, @@ -425,6 +454,7 @@ def build_project_history_projection( maximum_depth=maximum_depth, maximum_paths_per_event=maximum_paths_per_event, ) + connected_post_count, lineage_count = _lineage_counts(ordered_ids, edge_rows) events: list[dict[str, Any]] = [] previous_actor_keys: Sequence[str] | None = None @@ -484,6 +514,8 @@ def build_project_history_projection( "focus_event_id": effective_focus, "time_basis_code": PROJECT_HISTORY_TIME_BASIS, "event_count": len(events), + "connected_post_count": connected_post_count, + "lineage_count": lineage_count, "distinct_actor_count": len(distinct_actor_keys), "distinct_observed_actor_count": len(distinct_observed_actor_keys), "truncated": bool(truncated), diff --git a/tests/test_project_history.py b/tests/test_project_history.py index fdf17cd4e..693bc6544 100644 --- a/tests/test_project_history.py +++ b/tests/test_project_history.py @@ -160,6 +160,56 @@ def test_project_history_rejects_nonfinite_lineage_scores(invalid_score: float) ) +def test_project_history_counts_connected_posts_and_distinct_lineages() -> None: + """Only forward edges form connected-post and lineage-component counts.""" + + events = [] + for ordinal in range(1, 7): + event = _event_row(f"00000000-0000-0000-0000-{ordinal:012d}") + event["created_at"] = datetime(2022, 3, 10 + ordinal, 9, tzinfo=timezone.utc) + events.append(event) + edges = [ + { + "parent_post_id": events[0]["post_id"], + "child_post_id": events[1]["post_id"], + "fused_score": 0.9, + }, + { + "parent_post_id": events[1]["post_id"], + "child_post_id": events[2]["post_id"], + "fused_score": 0.8, + }, + { + "parent_post_id": events[3]["post_id"], + "child_post_id": events[4]["post_id"], + "fused_score": 0.7, + }, + { + "parent_post_id": events[1]["post_id"], + "child_post_id": events[2]["post_id"], + "fused_score": 0.8, + }, + { + "parent_post_id": events[5]["post_id"], + "child_post_id": events[0]["post_id"], + "fused_score": 0.6, + }, + ] + + projection = build_project_history_projection( + project_key="P-100", + focus_event_id=None, + event_rows=events, + match_rows=[], + role_rows=[], + edge_rows=edges, + ) + + assert projection["event_count"] == 6 + assert projection["connected_post_count"] == 5 + assert projection["lineage_count"] == 2 + + def test_matching_observed_project_code_keeps_its_distinct_display_name() -> None: """A matching code may carry a human display name that is not itself the key.""" From b35c555d328a90aed39d86c98cce17b1b36c6b53 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 21:08:54 +0900 Subject: [PATCH 2/3] docs: record project history delivery evidence --- docs/product-technical-gap-baseline.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 17b1fe90c..25dafa931 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -342,9 +342,11 @@ runtime note into a shipped/live claim. - The same projection supplies the dedicated Project history destination, each post's project-evidence action, post-scoped Ask, and Global Ask, so those surfaces cannot drift into competing count definitions. -- Current evidence is source and focused tests on the new stacked slice. Protected - merge, authenticated runtime, and the TEPP topic-identity result contract remain - open and must not be represented as protected-main behavior. +- Stacked delivery evidence is PR #487 on PR #258. Local evidence is the full + 993-test backend suite, focused Project History UI tests, the complete App test + file, lint, production build, Storybook build, and rendered-story inspection. + Protected merge, authenticated runtime, and the TEPP topic-identity result + contract remain open and must not be represented as protected-main behavior. ## Ask-to-project-history integration (2026-08-21) From c044e88c8097e295aaf632b51a45bd281f87d955 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 21:14:39 +0900 Subject: [PATCH 3/3] fix: label bounded project history counts --- CHANGELOG.d/2.23.2-project-history-lineage-counts.md | 7 ++++--- .../0128-authorized-project-history-buyer-surface.md | 6 +++++- docs/product-technical-gap-baseline.md | 6 +++--- .../src/components/ProjectHistoryTimeline.test.tsx | 2 +- frontend/src/projectHistory.ts | 10 +++++----- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.d/2.23.2-project-history-lineage-counts.md b/CHANGELOG.d/2.23.2-project-history-lineage-counts.md index 47e62ac4a..4935c269e 100644 --- a/CHANGELOG.d/2.23.2-project-history-lineage-counts.md +++ b/CHANGELOG.d/2.23.2-project-history-lineage-counts.md @@ -1,5 +1,6 @@ ### Added -- Project history now shows how many authorized project posts participate in - persisted forward lineage edges and how many distinct evidence-lineage groups - those posts form. The same counts appear from post and Ask entry points. +- Project history now shows how many displayed authorized project posts + participate in persisted forward lineage edges and how many distinct + evidence-lineage groups those posts form. The same bounded counts appear from + post and Ask entry points. diff --git a/docs/adr/0128-authorized-project-history-buyer-surface.md b/docs/adr/0128-authorized-project-history-buyer-surface.md index 7df6bcabc..3581668f2 100644 --- a/docs/adr/0128-authorized-project-history-buyer-surface.md +++ b/docs/adr/0128-authorized-project-history-buyer-surface.md @@ -36,7 +36,7 @@ responsibility transitions, and related paths are bounded projections, not an HR ledger or a causal graph. The projection reports three explicit evidence-fusion statistics over the same -authorized event set: `event_count` counts all project posts, +bounded authorized event set: `event_count` counts displayed project posts, `connected_post_count` counts posts incident to at least one forward `post_lineage_edge`, and `lineage_count` counts weakly connected components containing those posts. Isolated project posts remain in `event_count` but do @@ -44,6 +44,10 @@ not become a lineage. These are LineageWeave evidence-DAG statistics, not TEPP topic identities; TEPP topic birth, split, merge, dormancy, and reactivation stay on the separate fail-closed TEPP contract. +The reader-facing label always says that the post count is the shown set. When +`truncated` is true, none of these statistics claims to cover project evidence +outside the bounded response. + The UI uses the existing design-token and Storybook component boundary. The Figma file above is the design source for the desktop, mobile, and evidence boundary states; no second component-specific token system is introduced. diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 25dafa931..6cbc26312 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -333,9 +333,9 @@ runtime note into a shipped/live claim. ## Project-history lineage counts (2026-08-23) -- The shared timeline now distinguishes all authorized project posts from posts - incident to a persisted forward `post_lineage_edge`, and reports the number of - weakly connected evidence-lineage components. +- The shared timeline now distinguishes displayed authorized project posts from + posts incident to a persisted forward `post_lineage_edge`, and reports the + number of weakly connected evidence-lineage components in that bounded set. - Isolated project posts remain visible but do not inflate the lineage count. These counts describe LineageWeave's evidence-fusion DAG; they are not TEPP topic identities and do not close the TRSL-TM topic-lineage dependency. diff --git a/frontend/src/components/ProjectHistoryTimeline.test.tsx b/frontend/src/components/ProjectHistoryTimeline.test.tsx index fa55cdd13..3ec742bb5 100644 --- a/frontend/src/components/ProjectHistoryTimeline.test.tsx +++ b/frontend/src/components/ProjectHistoryTimeline.test.tsx @@ -144,7 +144,7 @@ describe("ProjectHistoryTimeline", () => { expect(screen.getByText(/evidence gap/i, { selector: "dd" })).toBeInTheDocument(); expect(screen.getByText(/related history, not causality/i)).toBeInTheDocument(); expect(screen.getByText(/permission, visibility, publication, and cutoff gates/i)).toBeInTheDocument(); - expect(screen.getByText(/3 project posts · 3 connected · lineage count 1/i)).toBeInTheDocument(); + expect(screen.getByText(/3 shown project posts · 3 connected · lineage count 1/i)).toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: /open source record: VOC received/i })); expect(onOpenPost).toHaveBeenCalledWith("post-voc"); diff --git a/frontend/src/projectHistory.ts b/frontend/src/projectHistory.ts index e4b4f76eb..1219baf4b 100644 --- a/frontend/src/projectHistory.ts +++ b/frontend/src/projectHistory.ts @@ -255,7 +255,7 @@ const EN: Record = { openProject: "Open project: {name}", evidenceBoundary: "Only source posts that pass the current permission, visibility, publication, and cutoff gates are included.", heading: "Project event timeline", - summaryCounts: "{events} project posts · {connected} connected · lineage count {lineages} · {actors} actors in evidence", + summaryCounts: "{events} shown project posts · {connected} connected · lineage count {lineages} · {actors} actors in evidence", sourcePostTime: "Dates use source-post creation time because a separate event clock is not recorded.", documentTime: "Dates use the document time recorded by the source.", truncated: "This bounded timeline is truncated. The selected event remains included.", @@ -306,7 +306,7 @@ const MESSAGES: Record> = { openProject: "프로젝트 열기: {name}", evidenceBoundary: "현재 권한·공개 범위·게시 상태·기준 시각을 통과한 원천 게시물만 포함합니다.", heading: "프로젝트 이벤트 타임라인", - summaryCounts: "프로젝트 글 {events}개 · 연결된 글 {connected}개 · Lineage {lineages}개 · 근거 담당자 {actors}명", + summaryCounts: "표시 중 프로젝트 글 {events}개 · 연결된 글 {connected}개 · Lineage {lineages}개 · 근거 담당자 {actors}명", sourcePostTime: "별도 사건 시각이 없어 날짜는 원천 게시물 생성 시각을 사용합니다.", documentTime: "날짜는 기록된 문서 시각을 사용합니다.", truncated: "이 제한된 타임라인은 일부만 표시합니다. 선택한 이벤트는 계속 포함됩니다.", @@ -354,7 +354,7 @@ const MESSAGES: Record> = { openProject: "打开项目:{name}", evidenceBoundary: "仅包含通过当前权限、可见性、发布状态和截止时间检查的源帖子。", heading: "项目事件时间线", - summaryCounts: "{events} 条项目记录 · {connected} 条已连接 · {lineages} 条 Lineage · 依据中出现 {actors} 名责任人", + summaryCounts: "当前显示 {events} 条项目记录 · {connected} 条已连接 · {lineages} 条 Lineage · 依据中出现 {actors} 名责任人", sourcePostTime: "未记录独立事件时钟,因此日期采用源帖子创建时间。", documentTime: "日期采用记录的文档时间。", truncated: "此有界时间线已截断,但所选事件仍保留。", @@ -402,7 +402,7 @@ const MESSAGES: Record> = { openProject: "プロジェクトを開く: {name}", evidenceBoundary: "現在の権限・可視性・公開状態・基準時刻を通過した原資料だけを含みます。", heading: "プロジェクトイベントのタイムライン", - summaryCounts: "プロジェクト投稿 {events}件 · 接続済み {connected}件 · Lineage {lineages}件 · 根拠内の担当者 {actors}名", + summaryCounts: "表示中のプロジェクト投稿 {events}件 · 接続済み {connected}件 · Lineage {lineages}件 · 根拠内の担当者 {actors}名", sourcePostTime: "独立したイベント時刻がないため、原資料の作成時刻を使用します。", documentTime: "日付は記録された文書時刻を使用します。", truncated: "この上限付きタイムラインは省略されていますが、選択イベントは保持されます。", @@ -450,7 +450,7 @@ const MESSAGES: Record> = { openProject: "Mở dự án: {name}", evidenceBoundary: "Chỉ bao gồm bài nguồn vượt qua quyền, khả năng hiển thị, trạng thái xuất bản và mốc thời gian hiện tại.", heading: "Dòng thời gian sự kiện dự án", - summaryCounts: "{events} bài dự án · {connected} bài đã kết nối · {lineages} lineage · {actors} người trong bằng chứng", + summaryCounts: "Đang hiển thị {events} bài dự án · {connected} bài đã kết nối · {lineages} lineage · {actors} người trong bằng chứng", sourcePostTime: "Không có đồng hồ sự kiện riêng, nên dùng thời gian tạo bài nguồn.", documentTime: "Ngày sử dụng thời gian tài liệu được ghi nhận.", truncated: "Dòng thời gian có giới hạn này đã bị rút gọn nhưng vẫn giữ sự kiện đang chọn.",