diff --git a/AGENTS.md b/AGENTS.md index 1728f9e61..cff6469d3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,7 +34,7 @@ reimplementing them: tree assembly (`reconstruct.py`'s `_walk`/`thread_messages` calls). - [RankWeave](https://github.com/ContextualWisdomLab/RankWeave) for multi-channel score fusion (`weighted_convex_fuse` in - `reconstruct.py`) and the buyer-facing Rankings port + `reconstruct.py`) and the reader-facing Rankings port (`rankweave_client.py`) -- never invent a fused score or a theta. - [TEPP](https://github.com/ContextualWisdomLab/TEPP)'s published wire contract for calibrated measurement (`tepp_client.py`) -- never @@ -127,7 +127,7 @@ contextual-orchestrator owns model discovery and selection. retaining the original asset and provenance. Recognize image DOM/visual regions before OCR, descriptions, Keyman extraction, or embeddings. Store region-level evidence; never show an internal LLM instruction such as - `This post is an image` to a buyer. + `This post is an image` to a reader. ## Source parsing and semantic units @@ -146,10 +146,10 @@ contextual-orchestrator owns model discovery and selection. - Remove presentation-only visual line alignment inside a paragraph (for example continuation lines manually aligned after `-`, `*`, `1.`, or `.`) from derived semantic text, while retaining the source body and meaningful - list/heading nesting. A buyer-facing post view must render semantic + list/heading nesting. A reader-facing post view must render semantic paragraphs, not the authoring application's spacing workaround. - Image descriptions, OCR text, and region evidence are analysis artifacts, - not buyer-facing prompt instructions. Buyer UI shows the source content and + not reader-facing prompt instructions. The UI shows the source content and useful captions/evidence only, with provenance where appropriate. ## Pluggable channels: never fake a missing signal diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index d0280ff97..d4ff2e159 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -64,7 +64,7 @@ flowchart LR | `chunking.py` | Splits a document into meaning-identifiable units (paragraph, sentence, DOM, conversation-turn) plus embedded-image extraction, in document order | | `embedding_client.py` | Pluggable text-embedding channel (`Null` default, `OpenAiCompatible` real impl) + `chunked_max_similarity` | | `adjudication_client.py` | Pluggable LLM-judgment channel (`Null` default, `ContextualOrchestrator` real impl) | -| `image_content.py` | Pluggable vision channel: OCR + object recognition/tagging for embedded images (`Null` default, `OpenAiCompatibleVisionClient` real impl). The product popup (`frontend/src/PostBody.tsx`) renders each `data:image` payload in document order so the buyer sees the picture, not the base64 string; GET does not call the vision client. | +| `image_content.py` | Pluggable vision channel: OCR + object recognition/tagging for embedded images (`Null` default, `OpenAiCompatibleVisionClient` real impl). The product popup (`frontend/src/PostBody.tsx`) renders each `data:image` payload in document order so the reader sees the picture, not the base64 string; GET does not call the vision client. | | `tepp_client.py` | TEPP's published `AnalysisRunRequest` wire contract, pluggable transport | | `rankweave_client.py` | Fail-closed RankWeave ranking port (`weighted_reciprocal_rank_fuse` in-process; never invent a fused score or a theta) | | `reconstruct.py` | The pipeline: group → candidate window → score → fuse → thread | @@ -326,7 +326,7 @@ Keymen are affiliated with (`lineageweave/affiliate_tree.py`, loaded by set of those leaves, not the whole company directory -- a sibling the post never mentions is omitted. People on the tree are buttons that reuse `GET /api/keymen/{person_id}/related` so the popup Keyman walk -starts from the affiliation the buyer clicked. A resolved organization +starts from the affiliation the reader clicked. A resolved organization is the same walk via `GET /api/corporate-entities/{id}/related`. An affiliation that did not resolve to a `corporate_entity` row stays as its own root (`resolved=false`); that is the same never-guess-a-parent rule @@ -467,7 +467,7 @@ truly have no dated open tickets. ## Phase 6-M2: authorized analysis-run evidence (read projection) -Issue #79's first buyer-visible Milestone 2 slice is a source-redacting +Issue #79's first reader-visible Milestone 2 slice is a source-redacting read of the #89 registry. `GET /api/analysis-runs` and `GET /api/analysis-runs/{id}` require `post_read` and apply the scope in SQL: the requester always sees their own run; a corporate-entity or @@ -475,7 +475,7 @@ process-unit scope is visible only to affiliated accounts; a thread-group scope is visible only when the account can already see a post in that group; `all_visible` is requester-only. Hidden runs 404. Detail also lists ABAC-visible post titles in the run's scope whose `created_at` is at or before `knowledge_cutoff` -(ADR 0016) so a buyer can open a post the run was allowed to know +(ADR 0016) so a reader can open a post the run was allowed to know without seeing later live rows or hidden bodies. Detail also returns revision and configuration digest prefixes. `POST /api/analysis-runs` records a Pending lineage run on a new @@ -925,7 +925,7 @@ code. Wired into both `keyman_ingestion.py`'s affiliation loop and ## Standards-complete W3C PROV-O provenance layer ADR 0011 separates standards-complete provenance from the compact -buyer-facing navigation graph. `lineageweave/prov_o.py` validates +reader-facing navigation graph. `lineageweave/prov_o.py` validates and materializes all 50 normative PROV-O properties, including literal-valued times/values and qualified Influence resources. `migrations/0017_prov_o_standard_relations.sql` stores definitions, diff --git a/backend/app/analysis_run_ingestion.py b/backend/app/analysis_run_ingestion.py index f7da2969b..cb9bb29a0 100644 --- a/backend/app/analysis_run_ingestion.py +++ b/backend/app/analysis_run_ingestion.py @@ -249,7 +249,7 @@ async def fetch_outbox_deliveries( """Labeled claim/delivery events for one already-visible run. Missing outbox tables mean migration 0023 is not applied. Stream - entry ids stay off the payload -- they are not buyer evidence. + entry ids stay off the payload -- they are not reader evidence. """ try: rows = await conn.fetch( @@ -285,7 +285,7 @@ async def _serialize_runs( conn: asyncpg.Connection, rows: list[asyncpg.Record], ) -> list[dict[str, Any]]: - """Project registry rows into the authorized buyer-facing payload.""" + """Project registry rows into the authorized reader-facing payload.""" if not rows: return [] count_rows = await _counts_by_run(conn, [str(row["analysis_run_id"]) for row in rows]) @@ -346,7 +346,7 @@ async def fetch_visible_analysis_runs( """Runs the account requested or whose scope they may already walk. Once real source-import evidence is visible, the synthetic `make seed` - Demo Corp runs stop appearing here -- a buyer must not mistake that + Demo Corp runs stop appearing here -- a reader must not mistake that fabricated narrative for real evidence (ADR 0001 / ADR 0042). """ # Safe SQL: this immutable module query contains only closed schema SQL; request values remain bound below. diff --git a/backend/app/demo_scope.py b/backend/app/demo_scope.py index 6d25bee24..811a0e673 100644 --- a/backend/app/demo_scope.py +++ b/backend/app/demo_scope.py @@ -4,7 +4,7 @@ something to show. Once an account can see at least one post carrying real source-import evidence, the synthetic Demo Corp tree is no longer needed to fill an empty screen and must stop appearing next to real evidence -- a -buyer must never mistake a fabricated contact (e.g. Ada West, Priya Nair) +reader must never mistake a fabricated contact (e.g. Ada West, Priya Nair) for a real one. """ diff --git a/backend/app/entity_relationship_ingestion.py b/backend/app/entity_relationship_ingestion.py index da22a9136..815628ef0 100644 --- a/backend/app/entity_relationship_ingestion.py +++ b/backend/app/entity_relationship_ingestion.py @@ -128,11 +128,11 @@ async def fetch_relationship_network( can be a customer in one post, a competitor in another (their own product line competes with ours elsewhere), the customer of our customer in a third, or a supplier -- Customer Master's per-post - reads never rolled these up, so buyers could only see one role at + reads never rolled these up, so readers could only see one role at a time and never the entity's whole network. This groups every visible, eligible post's classifications by counterparty name, keeping every distinct relationship type observed (not just the - most frequent), so a buyer can see a name marked both Customer and + most frequent), so a reader can see a name marked both Customer and Competitor and know that reflects the real, mixed relationship rather than a classification error. diff --git a/backend/app/issue_ticket_ingestion.py b/backend/app/issue_ticket_ingestion.py index 2cb3fe01d..021a3c398 100644 --- a/backend/app/issue_ticket_ingestion.py +++ b/backend/app/issue_ticket_ingestion.py @@ -173,7 +173,7 @@ async def upsert_commitment_ticket( Re-deriving the same post must not stack duplicate calendar rows -- an existing open ticket with a commitment_summary is updated in place. - A closed ticket is left alone so the buyer can keep the historical + A closed ticket is left alone so the reader can keep the historical record and still derive a fresh open one. """ existing = await conn.fetchrow( diff --git a/backend/app/knowledge_graph.py b/backend/app/knowledge_graph.py index 71304ce92..3f84ea69c 100644 --- a/backend/app/knowledge_graph.py +++ b/backend/app/knowledge_graph.py @@ -576,7 +576,7 @@ async def fetch_person_role_history( responsibility, in posts at different times (a job change, a title change, a move between projects). ``post_summary_role`` already carries this per post; this simply orders it chronologically for - one person instead of leaving a buyer to open every post that + one person instead of leaving a reader to open every post that mentions them and compare manually. ``visible_post_ids`` must already be ABAC-filtered by the caller diff --git a/backend/app/main.py b/backend/app/main.py index fb943315f..33975b904 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1463,7 +1463,7 @@ async def read_post_content( pool: asyncpg.Pool = Depends(get_pool), valkey: redis.Redis = Depends(get_valkey), ) -> dict[str, Any]: - """Return persisted content evidence; never derive or invent buyer copy.""" + """Return persisted content evidence; never derive or invent reader copy.""" await _load_visible_post(post_id, account, pool) queue_event: tuple[str, str] | None = None async with pool.acquire() as conn: @@ -2517,7 +2517,7 @@ class ChatRequest(BaseModel): class GlobalAskRequest(BaseModel): - """JSON body for the buyer's source-grounded Global Ask Agent.""" + """JSON body for the reader's source-grounded Global Ask Agent.""" question: str @@ -2618,7 +2618,7 @@ async def ask_agent( account: CurrentAccount = Depends(get_current_account), pool: asyncpg.Pool = Depends(get_pool), ) -> dict[str, Any]: - """Answer a buyer question from authorized post and graph evidence.""" + """Answer a reader question from authorized post and graph evidence.""" question = request.question.strip() if not question: raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, "question is required") diff --git a/backend/app/post_eligibility.py b/backend/app/post_eligibility.py index 41473d9da..6af04b197 100644 --- a/backend/app/post_eligibility.py +++ b/backend/app/post_eligibility.py @@ -1,4 +1,4 @@ -"""Shared source-post eligibility SQL for buyer evidence reads.""" +"""Shared source-post eligibility SQL for reader evidence reads.""" SOURCE_CONTEXT_COLUMNS = ( "source_author_code", diff --git a/backend/app/post_summary_ingestion.py b/backend/app/post_summary_ingestion.py index 7403185ed..397db0da9 100644 --- a/backend/app/post_summary_ingestion.py +++ b/backend/app/post_summary_ingestion.py @@ -89,7 +89,7 @@ async def fetch_persisted_summary( (ADR 0019 / 0027). This function does not join ``corporate_entity`` by ``entity_name``. Person chips read ``cataloged_person_id``. A stale row is returned only when ``allow_stale`` is explicit so a caller can - preserve buyer continuity without presenting old semantics as current. + preserve reader continuity without presenting old semantics as current. """ header = await conn.fetchrow( "select korean_summary, summary_contract_version " diff --git a/backend/app/ranking_ingestion.py b/backend/app/ranking_ingestion.py index 512b71273..71cd66e57 100644 --- a/backend/app/ranking_ingestion.py +++ b/backend/app/ranking_ingestion.py @@ -18,7 +18,7 @@ async def load_visible_ranking_posts( conn: "asyncpg.Connection", can_see_post: Callable[[Mapping[str, Any]], bool], ) -> list[dict[str, Any]]: - """Read ``source_post`` rows the buyer may rank.""" + """Read ``source_post`` rows the reader may rank.""" posts = await conn.fetch( "select post_id, post_title, created_at, visibility_code, " "corporate_entity_id from source_post" diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 438b4786a..3ba67bb68 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -661,7 +661,7 @@ def test_create_analysis_run_records_pending_without_inventing_a_score( json={ "run_kind_code": "analysis_run_lineage", "corporate_entity_id": seeded_db["own_corp_id"], - "idempotency_key": "buyer-create-2026-w02", + "idempotency_key": "run-create-2026-w02", }, ) assert created.status_code == 201 @@ -682,7 +682,7 @@ def test_create_analysis_run_records_pending_without_inventing_a_score( json={ "run_kind_code": "analysis_run_lineage", "corporate_entity_id": seeded_db["own_corp_id"], - "idempotency_key": "buyer-create-2026-w02", + "idempotency_key": "run-create-2026-w02", }, ) assert replay.status_code == 201 @@ -694,7 +694,7 @@ def test_create_analysis_run_records_pending_without_inventing_a_score( json={ "run_kind_code": "analysis_run_tepp", "corporate_entity_id": seeded_db["own_corp_id"], - "idempotency_key": "buyer-create-tepp", + "idempotency_key": "run-create-tepp", }, ) assert tepp.status_code == 422 @@ -707,7 +707,7 @@ def test_create_analysis_run_records_pending_without_inventing_a_score( json={ "run_kind_code": "analysis_run_report", "corporate_entity_id": seeded_db["own_corp_id"], - "idempotency_key": "buyer-create-report", + "idempotency_key": "run-create-report", }, ) assert report.status_code == 422 @@ -720,7 +720,7 @@ def test_create_analysis_run_records_pending_without_inventing_a_score( "run_kind_code": "analysis_run_lineage", "corporate_entity_id": seeded_db["own_corp_id"], "knowledge_cutoff": "2026-01-01T00:00:00Z", - "idempotency_key": "buyer-create-2026-w02", + "idempotency_key": "run-create-2026-w02", }, ) assert conflict.status_code == 409 @@ -731,14 +731,14 @@ def test_create_analysis_run_records_pending_without_inventing_a_score( json={ "run_kind_code": "analysis_run_lineage", "corporate_entity_id": seeded_db["other_corp_id"], - "idempotency_key": "buyer-create-hidden-corp", + "idempotency_key": "run-create-hidden-corp", }, ) assert hidden.status_code == 404 unauthenticated = client.post( "/api/analysis-runs", - json={"idempotency_key": "buyer-create-unauthenticated"}, + json={"idempotency_key": "run-create-unauthenticated"}, ) assert unauthenticated.status_code == 401 @@ -781,7 +781,7 @@ def test_start_analysis_run_recovers_the_a100_fork( "run_kind_code": "analysis_run_lineage", "corporate_entity_id": seeded_db["own_corp_id"], "knowledge_cutoff": "2026-02-15T00:00:00Z", - "idempotency_key": "buyer-start-2026-w07", + "idempotency_key": "run-start-2026-w07", }, ) assert created.status_code == 201, created.text @@ -853,7 +853,7 @@ def test_start_analysis_run_recovers_the_a100_fork( "run_kind_code": "analysis_run_tepp", "corporate_entity_id": seeded_db["own_corp_id"], "knowledge_cutoff": "2026-02-15T00:00:00Z", - "idempotency_key": "buyer-start-tepp-2026-w07", + "idempotency_key": "run-start-tepp-2026-w07", }, ) assert tepp_create.status_code == 422 @@ -887,7 +887,7 @@ def test_start_analysis_run_recovers_the_a100_fork( requested_by_account_id, knowledge_cutoff, configuration_schema_version, configuration_sha256, code_revision_sha, requested_at) - values (%s, 'analysis_run_tepp', 'buyer-start-tepp-seeded', + values (%s, 'analysis_run_tepp', 'run-start-tepp-seeded', %s, '2026-02-15T00:00:00Z', 'tepp-run-v1', %s, %s, '2026-02-15T12:30:00Z') returning analysis_run_id @@ -956,7 +956,7 @@ def test_start_analysis_run_recovers_the_a100_fork( requested_by_account_id, knowledge_cutoff, configuration_schema_version, configuration_sha256, code_revision_sha, requested_at) - values (%s, 'analysis_run_report', 'buyer-start-report', + values (%s, 'analysis_run_report', 'run-start-report', %s, '2026-01-12T12:00:00Z', 'lineage-run-v1', %s, %s, '2026-01-12T12:30:00Z') returning analysis_run_id @@ -999,7 +999,7 @@ def test_start_analysis_run_recovers_the_a100_fork( requested_by_account_id, knowledge_cutoff, configuration_schema_version, configuration_sha256, code_revision_sha, requested_at) - values (%s, 'analysis_run_lineage', 'buyer-start-running', + values (%s, 'analysis_run_lineage', 'run-start-running', %s, '2026-01-12T12:00:00Z', 'lineage-run-v1', %s, %s, '2026-01-12T12:30:00Z') returning analysis_run_id @@ -1072,7 +1072,7 @@ def test_start_analysis_run_recovers_the_a100_fork( requested_by_account_id, knowledge_cutoff, configuration_schema_version, configuration_sha256, code_revision_sha, requested_at) - values (%s, 'analysis_run_lineage', 'buyer-start-outbox-resume', + values (%s, 'analysis_run_lineage', 'run-start-outbox-resume', %s, '2026-02-15T00:00:00Z', 'lineage-run-v1', %s, %s, '2026-02-15T12:30:00Z') returning analysis_run_id @@ -1568,7 +1568,7 @@ def test_persisted_summary_is_returned_without_an_llm(client, demo_analyst_token def test_stale_summary_is_returned_labeled_when_orchestrator_is_unavailable( client, demo_analyst_token, seeded_db ) -> None: - """A legacy saved summary preserves buyer continuity with an explicit label.""" + """A legacy saved summary preserves reader continuity with an explicit label.""" os.environ.pop("ORCHESTRATOR_BASE_URL", None) os.environ.pop("ORCHESTRATOR_API_KEY", None) admin_conn = psycopg2.connect(seeded_db["dsn"]) @@ -4633,7 +4633,7 @@ def test_seed_period_report_member_click_lands_on_decorated_fixture( client, demo_analyst_token, seeded_db ) -> None: """The first W02 report member must already have Event Lineage, - Keyman, and evaluation -- otherwise the buyer click opens a dummy + Keyman, and evaluation -- otherwise the reader click opens a dummy high/low band row. """ from lineageweave.fixtures import fixture_thread_cast, fixture_titles_in_iso_week diff --git a/docker-compose.yml b/docker-compose.yml index 96ec0b89a..324b3981f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -112,7 +112,7 @@ services: # Gateway credentials and URL are supplied only by env_file (${HOME}/.env). # Do not repeat them under environment:, where Compose interpolation can # overwrite env_file values with an empty host-shell value. - # The upstream default remains 64 KiB for ordinary text APIs. Buyer + # The upstream default remains 64 KiB for ordinary text APIs. Reader # image blocks are base64 data URIs, so the multimodal boundary gets an # explicit bounded 8 MiB limit rather than an unbounded request size. CONTEXTUAL_ORCHESTRATOR_MAX_BODY_BYTES: ${CONTEXTUAL_ORCHESTRATOR_MAX_BODY_BYTES:-8388608} diff --git a/docs/adr/0119-retire-buyer-terminology.md b/docs/adr/0119-retire-buyer-terminology.md new file mode 100644 index 000000000..334111286 --- /dev/null +++ b/docs/adr/0119-retire-buyer-terminology.md @@ -0,0 +1,62 @@ +# ADR 0119: Retire "Buyer" Terminology From Live Code And Docs + +**Status:** Accepted +**Date:** 2026-08-23 + +**Context:** LineageWeave is an internal B2B marketing-intelligence/knowledge-graph +tool over VOC/board-post data, not a storefront -- there is no literal +e-commerce buyer role. Earlier work (ADR 0037, ADR 0110, ADR 0114) named the +app-wide navigation bar, several CSS classes, i18n keys, code comments, and +test descriptions after "Buyer" as a generic stand-in for "whoever is using +the product." Per this project's naming convention, internal code should not +use "Buyer" unless a literal e-commerce buyer role exists; the codebase +already has an established term for the generic app user -- "reader" (see +`lineageweave/post_summary.py`, `backend/app/activity_stream.py`, +`backend/app/knowledge_graph.py`). + +**Decision:** +1. `frontend/src/components/BuyerNav.tsx` (and its test) is renamed to + `WorkspaceNav.tsx`. The exported `BuyerNav` component becomes + `WorkspaceNav`; the `BuyerDestination` type becomes `WorkspaceDestination`. +2. CSS classes `.buyer-gnb`, `.buyer-gnb-item`, `.buyer-gnb-tools` become + `.workspace-gnb`, `.workspace-gnb-item`, `.workspace-gnb-tools`; the + `.buyer-destination` / `.buyer-destination-intro` class names used in + `App.tsx` become `.workspace-destination` / `.workspace-destination-intro`. +3. The i18n key `"Buyer navigation"` becomes `"Workspace navigation"` across + all five locale blocks in `frontend/src/i18n.ts` (en/ko/zh/ja/vi). +4. Internal Python identifiers `_buyer_evidence_kind` / `_buyer_evidence_text` + in `lineageweave/post_chat.py` become `_reader_evidence_kind` / + `_reader_evidence_text`. +5. Prose in code comments, docstrings, test descriptions, `AGENTS.md`, and + `ARCHITECTURE.md` that used "buyer" as a generic reference to the person + using the product now says "reader," matching the term already + established elsewhere in this codebase. +6. Test-only opaque idempotency-key string literals prefixed `buyer-` (e.g. + `"buyer-create-2026-w02"`) become `run-`-prefixed, since they are + arbitrary unique test values with no functional dependency on the + substring. +7. Out of scope, deliberately unchanged: numbered ADRs already on `main` + that record past decisions in the terminology of their time (ADR 0037, + 0069, 0110, 0114, and others) -- these are point-in-time records, not + live naming; `CHANGELOG.md` and `CHANGELOG.d/*.md` historical entries, + for the same reason; and domain-content prose that names an actual + business role called "buyer" inside synthetic fixture text + (`lineageweave/fixtures.py`'s sales-note fixtures). One ambiguous case, + `tests/test_chunking.py`'s `"Owner | Buyer"` markdown-table fixture, is + left as-is because it reads as a plausible real business-table header + rather than a naming choice. + +**Consequences:** +- No user-visible behavior changes; this is a naming-only rename plus + translated-string updates. +- `WorkspaceNav`/`WorkspaceDestination` and `.workspace-gnb*` are now the + canonical names for the app-wide navigation bar and its destinations. +- Future internal naming should default to "reader" for the generic app + user, consistent with `post_summary.py` and `activity_stream.py`, and + should avoid "Buyer" unless a literal e-commerce buyer role is added. + +**References:** +- ADR 0037 (Buyer GNB surface -- the component this ADR renames) +- ADR 0110 (Buyer image evidence rendering) +- ADR 0114 (Stale summary buyer continuity) +- ADR 0118 (UI/UX Standard Guide Ver.3.0, most recent GNB touch point) diff --git a/docs/doctoring/DESIGN_TOKEN_REFERENCES.md b/docs/doctoring/DESIGN_TOKEN_REFERENCES.md index 717954045..7ae1afbdc 100644 --- a/docs/doctoring/DESIGN_TOKEN_REFERENCES.md +++ b/docs/doctoring/DESIGN_TOKEN_REFERENCES.md @@ -9,7 +9,7 @@ the Storybook inventory. | Source | Product implication | Implemented evidence | |---|---|---| | W3C Design Tokens Format Module 2025.10 | Name color, space, type, and radius once; consume those names from repeated objects. | `frontend/src/styles/tokens.css` defines `--color-*`, `--space-*`, `--size-control-min`, `--radius-chip`, `--radius-control`, `--radius-panel`, and `--font-*`. `CitationChip`, `PopupCloseButton`, `CutoffKnownBody`, and `LineageEntityPicker` read those names through `App.css`. | -| Storybook for React & Vite | Catalog repeated controls so a buyer can try the next click without reading `App.tsx`. | `frontend/src/components/*.stories.tsx` and `docs/storybook-inventory.md`. | +| Storybook for React & Vite | Catalog repeated controls so a reader can try the next click without reading `App.tsx`. | `frontend/src/components/*.stories.tsx` and `docs/storybook-inventory.md`. | | WCAG 2.2 | Give interactive controls programmatic names and announce an asynchronous evidence failure instead of leaving a perpetual loading state. | Component interaction tests exercise the named controls; `EvidencePanel` exposes its terminal failure with `role="alert"`. This is targeted evidence, not a claim of complete WCAG conformance. | ## APA 7th references diff --git a/docs/image-content-schema.md b/docs/image-content-schema.md index d8e490b80..7574db752 100644 --- a/docs/image-content-schema.md +++ b/docs/image-content-schema.md @@ -93,7 +93,7 @@ picture sat relative to the surrounding paragraphs." The demo popup does not yet read these tables. It splits the live `post_body` the same way `extract_base64_images` does: each `data:image/...;base64,...` payload becomes an `` at its original -character offset, and the surrounding HTML is shown as text. A buyer who +character offset, and the surrounding HTML is shown as text. A reader who opens the post sees the picture that sat between the paragraphs, not the base64 wall. Remote `src="https://..."` tags are stripped, never fetched. OCR, caption, and tag search still require the vision client on extract / diff --git a/docs/lineage-bi-research-notes.md b/docs/lineage-bi-research-notes.md index 94f99d73d..1f1c53a52 100644 --- a/docs/lineage-bi-research-notes.md +++ b/docs/lineage-bi-research-notes.md @@ -322,7 +322,7 @@ node, and an unresolved free-text affiliation is left as its own root rather than attached to the nearest name. VOC evidence is extractive, not abstractive. The post already carries a -closed `voc_type_code`; the buyer-felt gap was the missing span that +closed `voc_type_code`; the reader-felt gap was the missing span that justifies that label. `sentence_excerpts` returns the sentences that contain a classified organization name -- the ACE mention extent (Doddington et al., 2004) already used for Keyman -- and returns diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index 28c59bd48..97c3d000d 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -1,9 +1,9 @@ # Storybook inventory Open the catalog after `cd frontend && pnpm run storybook`. Each story is a -buyer-facing control you can click before changing product CSS. +reader-facing control you can click before changing product CSS. -| Story | Buyer next action | Token / module | +| Story | Reader next action | Token / module | |---|---|---| | `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` | diff --git a/frontend/src/App.css b/frontend/src/App.css index c72aab078..e95a020b0 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -142,7 +142,7 @@ } /* GNB Navigation (§2.3.1) */ -.buyer-gnb { +.workspace-gnb { display: flex; align-items: center; height: var(--gnb-height); @@ -155,7 +155,7 @@ z-index: var(--z-gnb-pulldown); } -.buyer-gnb-item { +.workspace-gnb-item { display: flex; align-items: center; height: 100%; @@ -170,15 +170,15 @@ transition: color 0.15s ease; } -.buyer-gnb-item:hover { +.workspace-gnb-item:hover { color: var(--color-text-heading); } -.buyer-gnb-item[aria-current="page"] { +.workspace-gnb-item[aria-current="page"] { color: var(--color-primary); } -.buyer-gnb-item[aria-current="page"]::after { +.workspace-gnb-item[aria-current="page"]::after { content: ""; position: absolute; bottom: 0; @@ -188,7 +188,7 @@ background-color: var(--gnb-active-indicator-color); } -.buyer-gnb-tools { +.workspace-gnb-tools { margin-left: auto; display: flex; align-items: center; @@ -820,7 +820,7 @@ .app-header { padding: 0 1rem; } - .buyer-gnb { + .workspace-gnb { padding: 0 1rem; } } @@ -828,7 +828,7 @@ @media (max-width: 768px) { /* Phone Breakpoint (<768px) */ - .buyer-gnb { + .workspace-gnb { display: none; /* Replaced by drawer on mobile */ } diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 7462abd2c..e6ecb8c3e 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -41,6 +41,9 @@ describe("App, unauthenticated", () => { state: expect.objectContaining({ returnUrl: expect.stringMatching(/^\//) }), }), ); + // Persisted as a fallback in case the OIDC state round-trip is dropped + // (see oidcReturnUrl.ts's restoreOidcReturnUrl, consumed in main.tsx). + expect(window.sessionStorage.getItem("lineageweave.oidc.returnUrl")).toMatch(/^\//); }); }); @@ -2100,7 +2103,7 @@ describe("App, authenticated", () => { ); }); - it("stops loading and gives the buyer a next action when cited evidence is unavailable", async () => { + it("stops loading and gives the reader a next action when cited evidence is unavailable", async () => { stubBackend({ evidenceUnavailable: true }); render(); @@ -2549,7 +2552,7 @@ describe("App, authenticated", () => { expect(screen.getByText("due 2026-01-09")).toBeInTheDocument(); }); - it("tells the buyer how to populate an empty calendar", async () => { + it("tells the reader how to populate an empty calendar", async () => { stubBackend({ calendarCommitments: [] }); render(); @@ -3501,11 +3504,11 @@ describe("App, authenticated", () => { ); }); - it("keeps advanced review tools out of the buyer board", async () => { + it("keeps advanced review tools out of the reader board", async () => { stubBackend(); render(); - expect(await screen.findByRole("navigation", { name: "Buyer navigation" })).toBeInTheDocument(); + expect(await screen.findByRole("navigation", { name: "Workspace navigation" })).toBeInTheDocument(); expect(screen.getByRole("button", { name: "Board" })).toHaveAttribute("aria-current", "page"); expect(screen.queryByText("Advanced review tools")).not.toBeInTheDocument(); }); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6fba0dd41..fecd2d8e1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -86,7 +86,7 @@ import { CitationChip } from "./components/CitationChip"; import { CutoffKnownBody } from "./components/CutoffKnownBody"; import { LineageEntityPicker } from "./components/LineageEntityPicker"; import { PopupCloseButton } from "./components/PopupCloseButton"; -import { BuyerNav, type BuyerDestination } from "./components/BuyerNav"; +import { WorkspaceNav, type WorkspaceDestination } from "./components/WorkspaceNav"; import { LineageDag } from "./LineageDag"; import { PostBody } from "./PostBody"; import { decodeHtmlEntities } from "./postBodyDisplay"; @@ -4293,10 +4293,10 @@ function CustomerMasterPanel({ } return ( -
+

{t("Authorized customer scope")}

{t("Customer master")}

-

{t("Customer entities available to this account.")}

+

{t("Customer entities available to this account.")}

{error ?

{error}

: null} {master === null && !error ?

{t("Loading customer master...")}

: null} {master?.corporate_entities.length === 0 ? ( @@ -4321,7 +4321,7 @@ function CustomerMasterPanel({ {master && (master.relationship_network ?? []).length > 0 ? (

{t("Relationship network")}

-

+

{t("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.")}

    @@ -4344,7 +4344,7 @@ function CustomerMasterPanel({ {master && master.source_customer_hints.length > 0 ? (

    {t("Observed customer evidence")}

    -

    +

    {t("Source identifiers are hints only; ontology and semantic evidence must resolve them before binding a customer.")}

    {master.source_customer_hints.length > HINT_RENDER_LIMIT && ( @@ -4494,10 +4494,10 @@ function AskAgentPanel({ } return ( -
    +

    {t("Evidence-grounded questions")}

    {t("Ask Agent")}

    -

    {t("Questions use authorized posts and their evidence.")}

    +

    {t("Questions use authorized posts and their evidence.")}

    {error ?

    {error}

    : null}