Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 0 additions & 139 deletions .github/workflows/ask-agent-workspace-integration.yml

This file was deleted.

6 changes: 6 additions & 0 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Product & Technical Gap Baseline

**Active Ask Agent design head:** PR #353, stacked directly on #264 exact head `39f21261052a9d2ae82c4b851a54831eaf909805`; this is proposed work until merged.

## 1. Known Parsing & Frontend Display Gaps
- **Footnote Parsing**: `post=00505695-3e61-1fd1-83c5-263f88a9e77a` fails to recognize footnotes (li/oi level errors).
- **Table Parsing**: `post=00505695-3e61-1fd1-80c6-86bb61c8ddc5` completely fails at parsing tables.
Expand Down Expand Up @@ -77,6 +79,7 @@ claims that an unmerged PR or historical runtime observation is live behavior.
| FR-11 | Post summaries expose evidence-bearing events and R&R. Requester/processor actions are nullable and may only name actors already bound to the same post summary. | ADR 0052, ADR 0102 | Commit `15e1a378` is on PR #258 and the schema exists locally; the current database has zero populated action rows, so buyer-data acceptance remains unproven |
| FR-12 | A hierarchy-enrichment timeout leaves the source-grounded summary readable and the actor unbound; it never creates a guessed catalog identity. | ADR 0101, ADR 0010, ADR 0026 | Commit `1c260f20` contains the boundary, ADR, and focused test; independent review, protected-main merge, and fresh runtime evidence remain pending |
| FR-13 | Customer Master projects authorized corporate entities as a Group → Company → Plant tree. Real organization containment uses W3C ORG while Group/Company/Plant remain separate SKOS level concepts. Missing-parent, self-parent, and cyclic edges remain visible as unresolved roots; the UI owns nested `group` elements from their parent `treeitem`, supports Arrow/Home/End and Enter/Space operation, and opens source-backed evidence outside the tree. | ADR 0124, ADR 0004, ADR 0010 | Ontology/SHACL interoperability tests, `customerMasterTree.ts`, `CustomerMasterTree.tsx`, component tests, Storybook, and code commit `21074cf80cbfab3001bf18b6e1a618f75f4bed24` |
| FR-14 | Global Ask presents a dedicated evidence workspace: semantic form submission, IME-safe keyboard behavior, explicit empty/loading/error/answer states, separated timeline and cited evidence, answer focus, responsive phone/tablet/PC layout, and the existing authorized cited-post → Event Lineage handoff. | ADR 0126, ADR 0002, ADR 0032, ADR 0090 | `AskAgentWorkspace.tsx`, focused component/token tests, Storybook state inventory, and existing App navigation regressions on #353 |

## TRD

Expand Down Expand Up @@ -153,6 +156,7 @@ sequenceDiagram
| NFR-05 | Synthetic fixtures only; runtime validation returns aggregate, non-identifying evidence | repository scan and evidence-document review |
| NFR-06 | ADR-first architectural change and paper-grounded model policy | ADR link check and review; unsupported policies remain unavailable |
| NFR-07 | Buyer hierarchy controls meet WCAG 2.2 keyboard operation and the WAI-ARIA tree ownership contract without inventing ontology facts | Ontology tests, focused hierarchy tests, full frontend test/lint/build, Storybook build, and final-head hosted verification |
| NFR-08 | Ask Agent uses shared UI-standard button/focus/color/radius tokens, clear focus differentiation, a primary content action, and 1024px/768px responsive transitions without horizontal scrolling | Focused component and CSS-contract tests, complete frontend build, Storybook state build, and UI/UX Standard Guide v3.0 review |

## Current aggregate data and runtime evidence

Expand Down Expand Up @@ -183,6 +187,7 @@ evidence for one authorized post, not a corpus-wide acceptance claim.

| Closed gap | Root cause | Closure evidence | Remaining boundary |
|---|---|---|---|
| Ask Agent had no dedicated UI component or executable state inventory; its chatbox reused a Keyman link-style action and mixed answer, timeline, citations, and evidence facts in one generic section. | The Global Ask feature accumulated inside `App.tsx` while the stack focused on authorization and cross-surface navigation, leaving presentation without its own ownership boundary. | #353 extracts a token-based evidence workspace, uses a semantic and IME-safe form, focuses completed answers, separates source-backed result regions, and adds five Storybook scenes plus focused regressions. | Streaming and persisted conversation history remain separate future product work; final-head hosted Checks and independent approval are still required. |
| Customer entities could disappear from the buyer surface when `parent_entity_id` formed a self-parent or cycle; the first tree refactor also placed child `group` content beside rather than inside its parent `treeitem`. | The old projection assembled only root-reachable nodes, overloaded evidence state with hierarchy semantics, and did not satisfy the APG ownership rule. | Code commit `21074cf80cbfab3001bf18b6e1a618f75f4bed24` promotes malformed edges to visible unresolved roots, keeps ORG containment separate from SKOS classification, makes every parent `treeitem` own its child `group`, separates evidence into an external region, and adds navigation, failure, stale-response, ontology, and Storybook regressions. | The API still exposes one parent context; authoritative acyclicity, level-transition rules, legal/operating/sales/billing contexts, and effective-dated history remain future normalized-model work. |

## Active PR audit
Expand All @@ -194,6 +199,7 @@ PR head, valid unresolved threads, qualifying independent review, and terminal h

| PR | Proposed increment | Base → head | Snapshot state |
|---|---|---|---|
| #353 | Ask Agent evidence workspace and semantic chatbox | `#264` → `v2.18.0` | Exact-head verification and independent review required |
| #301 | Global Ask knowledge cutoff | `#264 stack` → `v2.23.0` | Ready / UNSTABLE |
| #298 | bounded async lineage LLM rebuild | `#276` → `v2.22.0` | Ready / UNSTABLE |
| #287 | exact Event Lineage channel evidence | `#276` → feature | Ready / UNSTABLE |
Expand Down
1 change: 1 addition & 0 deletions docs/storybook-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ buyer-facing control you can click before changing product CSS.

| Story | Buyer next action | Token / module |
|---|---|---|
| `Ask/AskAgentWorkspace` | Review empty, loading, answered, unavailable, and phone states; open cited evidence after an answer. | UI-standard primary-button/focus tokens, responsive layout, `AskAgentWorkspace` |
| `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` |
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "2.17.0",
"version": "2.18.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
142 changes: 4 additions & 138 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"
import { useAuth } from "react-oidc-context";
import {
askPostChat,
askAgent,
BackendError,
createAnalysisRun,
startAnalysisRun,
Expand Down Expand Up @@ -48,7 +47,6 @@ import {
updateTicketStatus,
verifyPostRelations,
type ActivityEvent,
type AskAgentResponse,
type AffiliateNode,
type AnalysisRun,
type CalendarResponse,
Expand Down Expand Up @@ -91,6 +89,10 @@ import { PostBody } from "./PostBody";
import { decodeHtmlEntities } from "./postBodyDisplay";
import { FiveW1H } from "./components/FiveW1H";
import { CustomerMasterTree, CustomerRelatedPostCard } from "./components/CustomerMasterTree";
import {
AskAgentWorkspace as AskAgentPanel,
GLOBAL_ASK_SESSION_STORAGE_KEY,
} from "./components/AskAgentWorkspace";
Comment on lines +92 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📝 Info: Inline Ask panel extracted to AskAgentWorkspace with equivalent behavior

The inline AskAgentPanel and the module-scoped GLOBAL_ASK_SESSION_STORAGE_KEY, CHAT_EVIDENCE_KIND_LABELS, and chatEvidenceKindLabel were removed from App.tsx and re-homed in frontend/src/components/AskAgentWorkspace.tsx. The new component preserves the original session-recovery-on-404 flow and additionally adds request-ordinal guarding (requestOrdinal at frontend/src/components/AskAgentWorkspace.tsx:275-306) and storage-access try/catch hardening, so behavior is a superset of the old panel. The re-exported constant value matches the original (lineageweave.globalAskSessionId) and remains consumed at App.tsx for logout cleanup. All removed imports (askAgent, AskAgentResponse) have no remaining references in App.tsx.

Open in Devin Review

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

import { subgraphForPost } from "./lineageLayout";
import {
isSupportedLocale,
Expand All @@ -108,7 +110,6 @@ import {
} from "./analysisRunNavigation";
import "./App.css";

const GLOBAL_ASK_SESSION_STORAGE_KEY = "lineageweave.globalAskSessionId";

function orchestratorUnavailableMessage(err: unknown, action: string): string {
if (err instanceof BackendError && err.status === 503) {
Expand Down Expand Up @@ -783,16 +784,6 @@ function projectProvenanceLabel(provenance: string): string {
return t(PROJECT_PROVENANCE_LABELS[provenance] ?? "Recorded evidence");
}

const CHAT_EVIDENCE_KIND_LABELS: Record<string, string> = {
source_field: "Source field hint",
semantic_project: "Semantic project",
semantic_role: "Semantic role",
semantic_keyman: "Semantic Keyman",
};

function chatEvidenceKindLabel(kind: string): string {
return t(CHAT_EVIDENCE_KIND_LABELS[kind] ?? "Evidence");
}

const VERIFICATION_BADGE: Record<string, string> = {
verify_pending: "Not yet checked",
Expand Down Expand Up @@ -4549,131 +4540,6 @@ function CustomerMasterPanel({
);
}

function AskAgentPanel({
accessToken,
onOpenPost,
}: {
accessToken: string;
onOpenPost: (postId: string) => void;
}) {
const [question, setQuestion] = useState("");
const [answer, setAnswer] = useState<AskAgentResponse | null>(null);
const [error, setError] = useState<string | null>(null);
const [asking, setAsking] = useState(false);
const [sessionId, setSessionId] = useState<string | undefined>(() =>
window.sessionStorage.getItem(GLOBAL_ASK_SESSION_STORAGE_KEY) ?? undefined,
);

async function handleAsk() {
const normalized = question.trim();
if (!normalized) return;
setAsking(true);
setError(null);
setAnswer(null);
try {
let nextAnswer: AskAgentResponse;
try {
nextAnswer = await askAgent(accessToken, normalized, sessionId);
} catch (err) {
if (!(err instanceof BackendError) || err.status !== 404 || !sessionId) {
throw err;
}
setSessionId(undefined);
window.sessionStorage.removeItem(GLOBAL_ASK_SESSION_STORAGE_KEY);
nextAnswer = await askAgent(accessToken, normalized);
}
setAnswer(nextAnswer);
setSessionId(nextAnswer.session_id);
window.sessionStorage.setItem(GLOBAL_ASK_SESSION_STORAGE_KEY, nextAnswer.session_id);
} catch (err) {
setAnswer(null);
setError(orchestratorUnavailableMessage(err, t("Ask Agent")));
} finally {
setAsking(false);
}
}

return (
<section className="buyer-destination" aria-labelledby="ask-agent-heading">
<p className="section-eyebrow">{t("Evidence-grounded questions")}</p>
<h2 id="ask-agent-heading">{t("Ask Agent")}</h2>
<p className="buyer-destination-intro">{t("Questions use authorized posts and their evidence.")}</p>
{error ? <p className="error">{error}</p> : null}
<label className="ask-agent-source">
<span>{t("Ask a question")}</span>
<textarea
aria-label={t("Ask a question")}
value={question}
onChange={(event) => setQuestion(event.target.value)}
rows={4}
/>
</label>
<button className="keyman-select" onClick={() => void handleAsk()} disabled={asking || !question.trim()}>
{asking ? t("Asking...") : t("Ask")}
</button>
{answer && (
<section className="popup-section" aria-label={t("Answer")}>
<h3>{t("Answer")}</h3>
{answer.answer_text ? <p>{answer.answer_text}</p> : null}
{answer.next_action ? <p className="post-meta">{t(answer.next_action)}</p> : null}
{answer.timeline && answer.timeline.length > 0 ? (
<>
<h4>{t("Event Lineage timeline")}</h4>
<ol className="related-post-list" aria-label={t("Event Lineage timeline")}>
{answer.timeline.map((event) => (
<li key={event.post_id}>
<button
type="button"
className="post-list-item"
aria-label={`${t("Open timeline post:")} ${event.post_title}`}
onClick={() => onOpenPost(event.post_id)}
>
<strong>{event.post_title}</strong>
{event.occurred_at ? <time dateTime={event.occurred_at}>{event.occurred_at}</time> : null}
</button>
</li>
))}
</ol>
</>
) : null}
{answer.cited_posts && answer.cited_posts.length > 0 && (
<>
<p className="board-next-action" role="status" aria-label={t("Next action")}>
{t("Authorized cited posts are current. Open a cited post to read Event Lineage.")}
</p>
<h4>{t("Cited posts")}</h4>
<ul className="related-post-list">
{answer.cited_posts.map((post) => (
<li key={post.post_id}>
<button
className="post-list-item"
aria-label={`${t("Open cited post:")} ${post.post_title}`}
onClick={() => onOpenPost(post.post_id)}
>
<strong>{post.post_title}</strong>
</button>
{answer.cited_post_evidence?.find((item) => item.post_id === post.post_id)?.facts.length ? (
<ul className="post-evidence-list" aria-label={t("Evidence facts")}>
{answer.cited_post_evidence
.find((item) => item.post_id === post.post_id)
?.facts.map((fact, index) => (
<li key={`${fact.kind}:${fact.text}:${index}`}>
<span>{chatEvidenceKindLabel(fact.kind)}</span>
<span>{fact.text}</span>
</li>
))}
</ul>
) : null}
</li>
))}
</ul>
</>
)}
</section>
)}
</section>
);
}

export default function App({ showLabPanels = false }: { showLabPanels?: boolean } = {}) {
useLocale();
Expand Down
Loading
Loading