Skip to content
Open
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
34 changes: 34 additions & 0 deletions .ai/spec/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# OpenShift LightSpeed Console Plugin -- Specifications

These specs define the requirements, behaviors, and architecture for the OLS console plugin (lightspeed-console). They are organized into two layers:

- **[`what/`](what/README.md)** -- Behavioral rules: WHAT the plugin must do and WHY. Technology-neutral, testable assertions. Use these to understand requirements, fix bugs, or rebuild components.
- **[`how/`](how/README.md)** -- Architecture specs: HOW the current implementation is structured. Module boundaries, data flow, design patterns. Use these to navigate, modify, and extend the codebase.

## Scope

These specs cover the **lightspeed-console** TypeScript/React dynamic plugin only. The service (lightspeed-service), operator (lightspeed-operator), and RAG content pipeline (lightspeed-rag-content) are separate projects.

## Audience

AI agents (Claude). Specs optimize for precision, unambiguous rules, and machine-parseable structure.

## Quick Start

| I want to... | Read |
|--------------|------|
| Understand what this plugin does | `what/system-overview.md` |
| Fix a bug in chat or streaming | `what/chat.md` + `how/streaming.md` |
| Add a new attachment type | `what/attachments.md` + `how/components.md` |
| Understand tool approval UI | `what/tools.md` |
| Understand the plugin API | `what/plugin-api.md` |
| Navigate the codebase | `how/project-structure.md` |
| Understand state management | `how/state-management.md` |
| See what's planned | Look for `[PLANNED: OLS-XXXX]` in `what/` specs |

## Conventions

- `[PLANNED: OLS-XXXX]` markers in `what/` specs indicate existing rules about to change due to open Jira work
- "Planned Changes" sections list new capabilities not yet in code
- Internal constants are stated as behavioral rules without numeric values; `how/` specs may include specific values
- User-configurable values are referenced by their user settings key path
23 changes: 23 additions & 0 deletions .ai/spec/how/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Architecture Specifications (how/)

These specs describe HOW the OLS console plugin is structured -- module boundaries, data flow, design patterns, key abstractions, and implementation decisions. They are grounded in the current TypeScript/React codebase and should be updated when the code changes.

## Spec Index

| Spec | Description |
|------|-------------|
| [project-structure.md](project-structure.md) | Directory layout, module responsibilities, build system, dependencies, dev setup, testing |
| [state-management.md](state-management.md) | Redux store shape, Immutable.js usage, actions, reducer, selectors |
| [streaming.md](streaming.md) | SSE stream processing, event types, buffering, throttled dispatch, abort handling |
| [components.md](components.md) | Component tree, Popover/GeneralPage/Prompt hierarchy, PatternFly Chatbot integration |

## When to Read These

- **Navigating the codebase**: Start with `project-structure.md` to understand where things live.
- **Modifying a subsystem**: Read the relevant `how/` spec to understand the current architecture before making changes.
- **Adding a new attachment type or tool UI**: The `components.md` spec includes extension points.
- **Debugging streaming issues**: The `streaming.md` spec traces the exact event processing path.

## Relationship to what/ Specs

The [`what/` specs](../what/README.md) define behavioral contracts (technology-neutral). These `how/` specs describe the implementation that fulfills those contracts. When the two diverge, the `what/` spec is the source of truth for correct behavior, and the `how/` spec should be updated to reflect the current code.
195 changes: 195 additions & 0 deletions .ai/spec/how/components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Components -- Architecture

The plugin's UI is built from a component tree rooted in the `Popover`
component, which is launched as a console modal. All components use
PatternFly 6 and the PatternFly AI Chatbot library.

## Module Map

| Path | Layer | Purpose |
|---|---|---|
| `Popover.tsx` | Root | Modal container, open/close/expand, first-time UX, feedback status fetch |
| `GeneralPage.tsx` | Layout | Chat interface with header, content, and footer sections |
| `Prompt.tsx` | Input | Message input, attachment menu, stream processing |
| `ResponseTools.tsx` | Response | Tool label group, MCP App and OLS Tool UI rendering |
| `ResponseToolModal.tsx` | Modal | Tool detail viewer |
| `ToolApproval.tsx` | Response | HITL approval card |
| `MCPApp.tsx` | Response | MCP App iframe host with JSON-RPC |
| `OlsToolUIs.tsx` | Response | OLS-native tool UI extension renderer |
| `AttachmentModal.tsx` | Modal | Attachment editor (code editor) |
| `AttachmentLabel.tsx` | Inline | Attachment display label |
| `AttachEventsModal.tsx` | Modal | Event selection for attachment |
| `AttachLogModal.tsx` | Modal | Log selection for attachment |
| `AttachmentsSizeAlert.tsx` | Alert | Warning for large attachments |
| `ErrorBoundary.tsx` | Utility | React error boundary |
| `Modal.tsx` | Utility | Reusable modal wrapper |

## Data Flow

### Component tree

```
ErrorBoundary
└── Popover
├── [isOpen && !isExpanded] GeneralPage (collapsed mode)
│ ├── ChatbotHeader (title, clear/copy/expand/minimize actions)
│ ├── ChatbotContent
│ │ └── MessageBox
│ │ ├── Welcome logo + intro text
│ │ ├── AuthAlert (if not authorized)
│ │ ├── PrivacyAlert
│ │ ├── WelcomeNotice (if first-time user)
│ │ ├── ChatHistoryEntry[] (memoized, one per entry)
│ │ │ ├── [user entry] Message (role=user, text, expandable context)
│ │ │ └── [ai entry] Message (role=bot, markdown, code blocks, actions)
│ │ │ ├── [error] Alert (danger)
│ │ │ ├── [historyCompression] Alert (info/success)
│ │ │ ├── [isTruncated] Alert (warning)
│ │ │ ├── [isCancelled] Alert (info)
│ │ │ ├── [pendingApproval] ToolApproval[]
│ │ │ ├── ResponseTools
│ │ │ │ ├── MCPApp[] (for tools with uiResourceUri)
│ │ │ │ ├── OlsToolUIs (for tools with olsToolUiID)
│ │ │ │ └── LabelGroup (tool summary labels)
│ │ │ └── [feedback] UserFeedbackForm
│ │ ├── AttachmentsSizeAlert
│ │ └── ReadinessAlert
│ └── ChatbotFooter (if authorized)
│ ├── Prompt
│ │ ├── MessageBar (textarea + attach menu + send/stop button)
│ │ ├── AttachmentLabel[] (current attachments)
│ │ ├── <input type="file"> (hidden, for YAML upload)
│ │ ├── AttachmentModal (edit modal)
│ │ ├── ToolModal (tool detail modal)
│ │ ├── AttachEventsModal (event selection)
│ │ └── AttachLogModal (log selection)
│ ├── ChatbotFootnote ("Always review AI generated content")
│ ├── Contact link
│ └── NewChatModal (clear confirmation)
├── [isOpen && isExpanded] GeneralPage (fullscreen mode, same tree)
└── Button (floating OLS button, always rendered)
```

### Popover lifecycle

1. `useHideLightspeed()` -> if hidden, render nothing.
2. `useFirstTimeUser()` -> if first-time and not hidden, auto-open after 500ms.
3. Fetch `/v1/feedback/status` on mount -> if disabled, dispatch `userFeedbackDisable()`.
4. Render: `isOpen` ? `GeneralPage` + close button : tooltip + open button.
5. Close handler: if first-time user, call `markAsExperienced()`.

### GeneralPage rendering

1. `useAuth()` -> determines if footer (prompt) is shown.
2. `useFirstTimeUser()` -> determines if welcome notice is shown.
3. Chat history entries are rendered via `ChatHistoryEntry` (memoized with `React.memo`).
4. Each `ChatHistoryEntry` handles its own feedback state and tool rendering.
5. Header actions: trash (clear chat), copy, expand/collapse, minimize.

### Prompt rendering

1. `useLocationContext()` -> extracts K8s resource from URL.
2. `useK8sWatchResource()` -> watches the detected resource (unless Alert).
3. Builds attachment menu items based on detected context and resource type.
4. `MessageBar` renders textarea with attach menu and send/stop toggle.
5. `onSubmit` -> validates input, dispatches history entries, starts stream.
6. `autoSubmit` effect -> programmatically clicks send button.

## Key Abstractions

### ChatHistoryEntry memoization

`ChatHistoryEntry` uses `React.memo` to prevent re-renders when other
entries change. Each entry reads its own slice of state via `useSelector`
with index-based paths (`getIn(['chatHistory', entryIndex, ...])`).

### Popover display modes

The `GeneralPage` component receives `onExpand` OR `onCollapse` prop
(never both) to determine its display mode:

- `onExpand` present -> collapsed mode (`ChatbotDisplayMode.default`)
- `onCollapse` present -> fullscreen mode (`ChatbotDisplayMode.fullscreen`)

The Popover parent tracks `isExpanded` state and passes the appropriate
callback.

### MCPApp card states

The MCP App card has three visual states managed by local `cardState`:

- `normal`: Full card with iframe at dynamic height
- `expanded`: Full card with `ols-plugin__mcp-app--expanded` CSS class
- `minimized`: Header-only card with restore button

### Message component integration

The PatternFly `Message` component is used for both user and AI entries
with different configurations:

**User messages**: `role="user"`, user avatar, text in `extraContent.afterMainContent`
with optional expandable attachment context section.

**AI messages**: `role="bot"`, OLS logo avatar (theme-dependent), markdown
`content` prop, `actions` (copy, thumbs up/down), `sources` (reference
links), `codeBlockProps` (import action, expandable), `extraContent`
(error alerts, tool approval cards, tool summaries, compression indicators),
`userFeedbackForm`/`userFeedbackComplete` for inline feedback.

### Attachment menu construction

The attachment menu items are built in `Prompt.tsx` using a `useMemo`
that depends on: detected resource context, events availability, loading
state, troubleshooting mode, and resource kind. The menu structure is:

1. "Currently viewing" section with resource label (if resource detected)
2. "Attach" section with resource-specific options
3. File upload option (always)
4. Divider
5. Query mode toggle (Ask or Troubleshooting)

Special cases:
- `ManagedCluster` kind -> "Attach cluster info" instead of YAML options
- `Alert` kind -> "Alert" option fetching from Prometheus/Thanos
- Non-workload kinds -> no Events or Logs options

## Implementation Notes

### GeneralPage.tsx ChatHistoryEntry reads adjacent entries

`ChatHistoryEntry` at index `i` (an AI response) reads `chatHistory[i-1]`
(the preceding user entry) to access the user's query text and attachments
for the feedback submission payload. This coupling means user entries must
always precede their corresponding AI entries in the history list.

### Prompt.tsx modal co-location

`AttachmentModal`, `ToolModal`, `AttachEventsModal`, and `AttachLogModal`
are rendered inside `Prompt.tsx`. They read their open/close state from
Redux (`openAttachment`, `openTool`) or local boolean state
(`isEventsModalOpen`, `isLogModalOpen`). This means the modals are always
mounted but conditionally visible.

### Theme propagation to MCPApp iframe

Theme changes are propagated to MCP App iframes via two mechanisms:
1. On initial HTML load, a `data-theme` attribute is injected into the
`<html>` tag via string replacement.
2. On subsequent theme changes, a `ui/notifications/host-context-changed`
JSON-RPC notification is sent via `postMessage`.

The theme `useEffect` in `MCPApp.tsx` intentionally excludes
`uiResourceUri` and `serverName` from its dependency array to avoid
re-fetching the HTML content on theme change -- only the notification
is sent.

### ErrorBoundary wrapping strategy

The `ErrorBoundary` is used at two levels:
1. Root level: wraps `Popover` to prevent plugin crashes from breaking
the console.
2. Tool UI level: wraps each `OlsToolUI` component to isolate crashes
in third-party tool visualizations.

The `MCPApp` iframe is inherently isolated and does not need an error
boundary.
Loading