Spaces:
Sleeping
Sleeping
harryagasi commited on
Commit ·
15d8afd
1
Parent(s): 587748b
fix: resizable report sidebar, stable chat layout, guided empty state
Browse filesAdds drag-resize + fullscreen to the report sidebar, fixes a min-h-screen
layout bug in AppNavigation that pushed the chat input/footer out of view
as the analysis list or message history grew, replaces the empty-conversation
notice with business-question starter buttons, caps business questions to
2-5 at creation time, and simplifies the conversation header to a single
compact row.
- .specify/feature.json +1 -3
- CLAUDE.md +2 -2
- specs/002-fix-analysis-chat-ui/checklists/requirements.md +34 -0
- specs/002-fix-analysis-chat-ui/contracts/component-props.md +57 -0
- specs/002-fix-analysis-chat-ui/data-model.md +52 -0
- specs/002-fix-analysis-chat-ui/plan.md +78 -0
- specs/002-fix-analysis-chat-ui/quickstart.md +42 -0
- specs/002-fix-analysis-chat-ui/research.md +47 -0
- specs/002-fix-analysis-chat-ui/spec.md +159 -0
- specs/002-fix-analysis-chat-ui/tasks.md +205 -0
- src/app/components/analysis/AnalysisHeader.tsx +5 -15
- src/app/components/analysis/AnalysisShell.tsx +74 -33
- src/app/components/analysis/AppNavigation.tsx +1 -1
- src/app/components/analysis/BusinessQuestionsEditor.tsx +18 -12
- src/app/components/analysis/MessageList.tsx +33 -5
- src/app/components/analysis/NewAnalysisDialog.tsx +3 -3
- src/app/components/analysis/ReportSidebar.tsx +16 -3
.specify/feature.json
CHANGED
|
@@ -1,3 +1 @@
|
|
| 1 |
-
{
|
| 2 |
-
"feature_directory": "specs/001-unified-analysis-chat"
|
| 3 |
-
}
|
|
|
|
| 1 |
+
{"feature_directory": "specs/002-fix-analysis-chat-ui"}
|
|
|
|
|
|
CLAUDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
<!-- SPECKIT START -->
|
| 2 |
For additional context about technologies to be used, project structure,
|
| 3 |
shell commands, and other important information, see:
|
| 4 |
-
- Implementation Plan: specs/
|
| 5 |
-
- Feature Specification: specs/
|
| 6 |
<!-- SPECKIT END -->
|
|
|
|
| 1 |
<!-- SPECKIT START -->
|
| 2 |
For additional context about technologies to be used, project structure,
|
| 3 |
shell commands, and other important information, see:
|
| 4 |
+
- Implementation Plan: specs/002-fix-analysis-chat-ui/plan.md
|
| 5 |
+
- Feature Specification: specs/002-fix-analysis-chat-ui/spec.md
|
| 6 |
<!-- SPECKIT END -->
|
specs/002-fix-analysis-chat-ui/checklists/requirements.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Specification Quality Checklist: Analysis Chat UI Fixes
|
| 2 |
+
|
| 3 |
+
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
| 4 |
+
**Created**: 2026-07-09
|
| 5 |
+
**Feature**: [spec.md](../spec.md)
|
| 6 |
+
|
| 7 |
+
## Content Quality
|
| 8 |
+
|
| 9 |
+
- [x] No implementation details (languages, frameworks, APIs)
|
| 10 |
+
- [x] Focused on user value and business needs
|
| 11 |
+
- [x] Written for non-technical stakeholders
|
| 12 |
+
- [x] All mandatory sections completed
|
| 13 |
+
|
| 14 |
+
## Requirement Completeness
|
| 15 |
+
|
| 16 |
+
- [x] No [NEEDS CLARIFICATION] markers remain
|
| 17 |
+
- [x] Requirements are testable and unambiguous
|
| 18 |
+
- [x] Success criteria are measurable
|
| 19 |
+
- [x] Success criteria are technology-agnostic (no implementation details)
|
| 20 |
+
- [x] All acceptance scenarios are defined
|
| 21 |
+
- [x] Edge cases are identified
|
| 22 |
+
- [x] Scope is clearly bounded
|
| 23 |
+
- [x] Dependencies and assumptions identified
|
| 24 |
+
|
| 25 |
+
## Feature Readiness
|
| 26 |
+
|
| 27 |
+
- [x] All functional requirements have clear acceptance criteria
|
| 28 |
+
- [x] User scenarios cover primary flows
|
| 29 |
+
- [x] Feature meets measurable outcomes defined in Success Criteria
|
| 30 |
+
- [x] No implementation details leak into specification
|
| 31 |
+
|
| 32 |
+
## Notes
|
| 33 |
+
|
| 34 |
+
- All items pass. No spec updates required before proceeding to `/speckit-clarify` or `/speckit-plan`.
|
specs/002-fix-analysis-chat-ui/contracts/component-props.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Component Prop Contracts: Analysis Chat UI Fixes
|
| 2 |
+
|
| 3 |
+
This project exposes no external API for this feature — the "contracts" are the React component prop interfaces that change. No backend endpoints are added or modified.
|
| 4 |
+
|
| 5 |
+
## `MessageList` (modified)
|
| 6 |
+
|
| 7 |
+
```ts
|
| 8 |
+
interface MessageListProps {
|
| 9 |
+
messages: UiMessage[];
|
| 10 |
+
scrollTo?: "bottom" | "last-ai" | null;
|
| 11 |
+
onScrollDone?: () => void;
|
| 12 |
+
// New for US3:
|
| 13 |
+
businessQuestions: string[]; // raw analysis.business_questions; component filters empty/whitespace
|
| 14 |
+
onSelectQuestion: (question: string) => void; // invoked on starter-button click; caller sends it as first message
|
| 15 |
+
}
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
Contract: when `messages.length === 0`, render guidance + one button per filtered `businessQuestions` entry instead of the "No messages yet" `EmptyState`. When `messages.length > 0`, `businessQuestions`/`onSelectQuestion` are ignored (existing message rendering path unchanged).
|
| 19 |
+
|
| 20 |
+
## `ReportSidebar` (modified)
|
| 21 |
+
|
| 22 |
+
```ts
|
| 23 |
+
interface ReportSidebarProps {
|
| 24 |
+
analysis: Analysis | null;
|
| 25 |
+
userId?: string;
|
| 26 |
+
onCollapse?: () => void;
|
| 27 |
+
// New for US1:
|
| 28 |
+
fullscreen: boolean;
|
| 29 |
+
onToggleFullscreen: () => void;
|
| 30 |
+
}
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
Contract: renders an additional fullscreen toggle control (alongside the existing collapse control) that calls `onToggleFullscreen`. The panel's actual width/fullscreen layout is controlled by the parent (`AnalysisShell.tsx`) via `ResizablePanelGroup`/`ResizablePanel`, not by this component directly.
|
| 34 |
+
|
| 35 |
+
## `BusinessQuestionsEditor` (modified, no prop shape change)
|
| 36 |
+
|
| 37 |
+
```ts
|
| 38 |
+
interface BusinessQuestionsEditorProps {
|
| 39 |
+
value: string[];
|
| 40 |
+
onChange: (questions: string[]) => void;
|
| 41 |
+
disabled?: boolean;
|
| 42 |
+
}
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
Contract change (behavioral, not signature): "Add" control is disabled/hidden once `value.length >= 5`; "Remove" control is disabled/hidden once `value.length <= 2`; the inline validation hint text reflects the 2–5 range.
|
| 46 |
+
|
| 47 |
+
## `AnalysisHeader` (modified, no prop shape change)
|
| 48 |
+
|
| 49 |
+
```ts
|
| 50 |
+
interface AnalysisHeaderProps {
|
| 51 |
+
analysis: Analysis | null;
|
| 52 |
+
staleSources?: DataBindItem[];
|
| 53 |
+
onUpdateDataBind: (items: DataBindItem[]) => Promise<void>;
|
| 54 |
+
}
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
Contract change (behavioral, not signature): default rendered output is a single compact row (title, sources-count pill, "Edit sources" action) instead of the current two-tier layout with objective text and question chips always visible.
|
specs/002-fix-analysis-chat-ui/data-model.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 1 Data Model: Analysis Chat UI Fixes
|
| 2 |
+
|
| 3 |
+
This feature is UI-only; no backend/API schema changes. The entities below are **client-side UI state** shapes introduced or modified in React components — not persisted server-side.
|
| 4 |
+
|
| 5 |
+
## Analysis (existing, unchanged)
|
| 6 |
+
|
| 7 |
+
Source of truth: `src/services/orchestrationApi.ts` `Analysis` type. No fields added or removed. Relevant existing fields used by this feature:
|
| 8 |
+
|
| 9 |
+
| Field | Type | Used by |
|
| 10 |
+
|---|---|---|
|
| 11 |
+
| `analysis_title` | `string` | AnalysisHeader (US5) |
|
| 12 |
+
| `objective` | `string` | AnalysisHeader (US5, no longer always-visible) |
|
| 13 |
+
| `business_questions` | `string[]` | BusinessQuestionsEditor (US4), MessageList empty-state buttons (US3) |
|
| 14 |
+
| `data_bind` | `DataBindItem[]` | AnalysisHeader "N sources" pill (US5) |
|
| 15 |
+
|
| 16 |
+
## ReportPanelState (new, client-only, in `AnalysisShell.tsx`)
|
| 17 |
+
|
| 18 |
+
Session-scoped (in-memory `useState`, no persistence), replacing/extending the existing `reportCollapsed: boolean`.
|
| 19 |
+
|
| 20 |
+
| Field | Type | Default | Notes |
|
| 21 |
+
|---|---|---|---|
|
| 22 |
+
| `collapsed` | `boolean` | `false` | Existing behavior, unchanged |
|
| 23 |
+
| `fullscreen` | `boolean` | `false` | New — when true, report panel occupies 100% width, chat panel is hidden |
|
| 24 |
+
| `widthPercent` | `number` | `23` (~current `23rem` equivalent as % of viewport) | New — last-used split-view width, clamped to `[25, 75]` per FR-001; reset to default on page reload (session-only per clarification) |
|
| 25 |
+
|
| 26 |
+
Transitions:
|
| 27 |
+
- `collapsed → false` when user clicks "Restore report sidebar" (existing).
|
| 28 |
+
- `fullscreen: false → true` when user activates fullscreen toggle; `widthPercent` is preserved (not overwritten) so exiting fullscreen restores the prior split.
|
| 29 |
+
- `fullscreen: true → false` restores `widthPercent` split view.
|
| 30 |
+
- `widthPercent` updates continuously (throttled by the resizable library) while dragging the handle, clamped to 25–75.
|
| 31 |
+
|
| 32 |
+
## BusinessQuestionDraft (existing shape, new constraint)
|
| 33 |
+
|
| 34 |
+
`NewAnalysisDialog.tsx` `questions: string[]` state — no shape change, only a cardinality constraint:
|
| 35 |
+
|
| 36 |
+
| Rule | Enforcement point |
|
| 37 |
+
|---|---|
|
| 38 |
+
| `2 <= questions.length <= 5` | `BusinessQuestionsEditor.tsx` Add/Remove button disabling; `NewAnalysisDialog.tsx` `canSubmit` |
|
| 39 |
+
| Empty/whitespace entries excluded from the count check | Reuses existing `compactQuestions()` util |
|
| 40 |
+
|
| 41 |
+
This constraint applies **only** at analysis-creation time (per clarification) — it is not retroactively enforced on `Analysis.business_questions` read elsewhere (e.g., MessageList starter buttons filter empty entries but do not cap at 5, per the legacy-analysis edge case in spec.md).
|
| 42 |
+
|
| 43 |
+
## No new entities
|
| 44 |
+
|
| 45 |
+
No new persisted domain entities, no new API request/response shapes. `ConversationEmptyStateProps` (or equivalent inline block in `MessageList.tsx`) is a presentational prop shape, not a domain entity:
|
| 46 |
+
|
| 47 |
+
```ts
|
| 48 |
+
interface ConversationEmptyStateProps {
|
| 49 |
+
businessQuestions: string[]; // pre-filtered, non-empty, trimmed
|
| 50 |
+
onSelectQuestion: (question: string) => void;
|
| 51 |
+
}
|
| 52 |
+
```
|
specs/002-fix-analysis-chat-ui/plan.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Implementation Plan: Analysis Chat UI Fixes
|
| 2 |
+
|
| 3 |
+
**Branch**: `002-fix-analysis-chat-ui` | **Date**: 2026-07-09 | **Spec**: [spec.md](./spec.md)
|
| 4 |
+
|
| 5 |
+
**Input**: Feature specification from `/specs/002-fix-analysis-chat-ui/spec.md`
|
| 6 |
+
|
| 7 |
+
**Note**: This template is filled in by the `/speckit-plan` command. See `.specify/templates/plan-template.md` for the execution workflow.
|
| 8 |
+
|
| 9 |
+
## Summary
|
| 10 |
+
|
| 11 |
+
Fix five UI/UX defects in the analysis conversation workspace (`AnalysisShell.tsx` and its children): (1) make the report sidebar resizable and expandable to fullscreen using the already-installed `react-resizable-panels` library, (2) make the chat input/footer layout provably stable under a growing analysis list and message history by auditing and correcting flex/grid/overflow containment in `AnalysisShell.tsx`, `AppNavigation.tsx`, and `ChatInput.tsx`, (3) replace the "No messages yet" empty state in `MessageList.tsx` with guidance text and clickable business-question starter buttons, (4) constrain `BusinessQuestionsEditor.tsx` to 2–5 questions during analysis creation, and (5) simplify `AnalysisHeader.tsx` to a compact single-row layout retaining title, status, back/close, and report-access actions. All changes are frontend-only (React 18 + TypeScript + Tailwind), no backend/API contract changes.
|
| 12 |
+
|
| 13 |
+
## Technical Context
|
| 14 |
+
|
| 15 |
+
**Language/Version**: TypeScript (React 18.3.1, Vite 6.3.5)
|
| 16 |
+
|
| 17 |
+
**Primary Dependencies**: React 18, Tailwind CSS 4, `react-resizable-panels` 2.1.7 (already installed, unused so far), `lucide-react` icons, Radix UI primitives (`@radix-ui/react-*`), `sonner` for toasts
|
| 18 |
+
|
| 19 |
+
**Storage**: N/A — this feature only touches client-side UI state (React state); `Analysis`/`AnalysisMessage` data continues to come from the existing `orchestrationApi`/`agenticApi` services unchanged
|
| 20 |
+
|
| 21 |
+
**Testing**: No test framework is currently configured in this project (no vitest/jest in `devDependencies`); verification is manual/visual per the project's `/verify` skill — this plan does not introduce a new test framework, consistent with existing project conventions
|
| 22 |
+
|
| 23 |
+
**Target Platform**: Web browser (desktop primary per the affected components; existing mobile drawer/bottom-sheet variants for nav and report panel must keep working)
|
| 24 |
+
|
| 25 |
+
**Project Type**: Single-page web application (Vite + React), frontend-only change — no `backend/` directory exists in this repo
|
| 26 |
+
|
| 27 |
+
**Performance Goals**: Sidebar resize/fullscreen interactions must visually reflect within 100ms (SC-001); no dropped frames during drag-resize
|
| 28 |
+
|
| 29 |
+
**Constraints**: Report sidebar width constrained to ~25%–75% of viewport in split view (FR-001); sidebar/fullscreen state is session-only, no persistence beyond in-memory React state (per clarification); business questions limited to 2–5 at creation time only, not retroactive (FR-008)
|
| 30 |
+
|
| 31 |
+
**Scale/Scope**: 5 components modified (`ReportSidebar.tsx` + wrapping layout in `AnalysisShell.tsx`, `AppNavigation.tsx`/`ChatInput.tsx` layout audit, `MessageList.tsx`, `BusinessQuestionsEditor.tsx`, `AnalysisHeader.tsx`); no new routes, no new backend endpoints, no new persisted entities
|
| 32 |
+
|
| 33 |
+
## Constitution Check
|
| 34 |
+
|
| 35 |
+
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
| 36 |
+
|
| 37 |
+
The project constitution (`.specify/memory/constitution.md`) is an unfilled template with no ratified principles — there are no constitutional gates to evaluate against. No violations to justify.
|
| 38 |
+
|
| 39 |
+
## Project Structure
|
| 40 |
+
|
| 41 |
+
### Documentation (this feature)
|
| 42 |
+
|
| 43 |
+
```text
|
| 44 |
+
specs/002-fix-analysis-chat-ui/
|
| 45 |
+
├── plan.md # This file (/speckit-plan command output)
|
| 46 |
+
├── research.md # Phase 0 output (/speckit-plan command)
|
| 47 |
+
├── data-model.md # Phase 1 output (/speckit-plan command)
|
| 48 |
+
├── quickstart.md # Phase 1 output (/speckit-plan command)
|
| 49 |
+
├── contracts/ # Phase 1 output (/speckit-plan command) — UI component contracts only
|
| 50 |
+
└── tasks.md # Phase 2 output (/speckit-tasks command - NOT created by /speckit-plan)
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
### Source Code (repository root)
|
| 54 |
+
|
| 55 |
+
```text
|
| 56 |
+
src/
|
| 57 |
+
├── app/
|
| 58 |
+
│ └── components/
|
| 59 |
+
│ └── analysis/
|
| 60 |
+
│ ├── AnalysisShell.tsx # Layout: grid columns, main/aside split — touched for US1, US2
|
| 61 |
+
│ ├── AnalysisHeader.tsx # Conversation header — touched for US5
|
| 62 |
+
│ ├── ReportSidebar.tsx # Report content pane — touched for US1 (wrapped in resizable panel)
|
| 63 |
+
│ ├── AppNavigation.tsx # Left nav incl. analysis list — audited for US2
|
| 64 |
+
│ ├── ChatInput.tsx # Chat input bar/footer — audited for US2
|
| 65 |
+
│ ├── MessageList.tsx # Message history + empty state — touched for US3
|
| 66 |
+
│ ├── BusinessQuestionsEditor.tsx # Business question inputs — touched for US4
|
| 67 |
+
│ ├── NewAnalysisDialog.tsx # Hosts BusinessQuestionsEditor at creation — touched for US4 (validation)
|
| 68 |
+
│ └── ui/ (shared)
|
| 69 |
+
│ └── resizable.tsx # Existing shadcn/radix-style resizable primitive — reused for US1
|
| 70 |
+
└── services/
|
| 71 |
+
└── orchestrationApi.ts, agenticApi.ts # Unchanged; existing Analysis/AnalysisMessage types reused
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
**Structure Decision**: Single frontend project (`src/app/components/analysis/`) — no new top-level directories. All work happens within the existing analysis feature folder, reusing the already-installed `react-resizable-panels` (`src/app/components/ui/resizable.tsx`) rather than adding a new dependency.
|
| 75 |
+
|
| 76 |
+
## Complexity Tracking
|
| 77 |
+
|
| 78 |
+
> No Constitution Check violations — this section is not applicable.
|
specs/002-fix-analysis-chat-ui/quickstart.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Quickstart: Verifying Analysis Chat UI Fixes
|
| 2 |
+
|
| 3 |
+
Run the dev server:
|
| 4 |
+
|
| 5 |
+
```bash
|
| 6 |
+
npm run dev
|
| 7 |
+
```
|
| 8 |
+
|
| 9 |
+
Open the app, log in, and navigate to an existing analysis (or create one) to reach the Analysis Agent view.
|
| 10 |
+
|
| 11 |
+
## US1 — Report sidebar resize / fullscreen
|
| 12 |
+
|
| 13 |
+
1. Open an analysis with a generated report.
|
| 14 |
+
2. Drag the vertical handle between the chat and report panes — width should move smoothly, clamped roughly between 25% and 75% of the viewport.
|
| 15 |
+
3. Click the fullscreen toggle in the report panel — it should expand to fill the available area; toggle again to return to the previous split width.
|
| 16 |
+
4. Reload the page — width/fullscreen should reset to default (session-only persistence, confirmed by clarification).
|
| 17 |
+
|
| 18 |
+
## US2 — Stable chat input / footer
|
| 19 |
+
|
| 20 |
+
1. Create or view an account with 10+ analyses in the left nav list.
|
| 21 |
+
2. Send enough chat messages in the active analysis to exceed one viewport height.
|
| 22 |
+
3. Confirm the chat input bar and its footer stay pinned at the bottom of the conversation pane; only the message history scrolls. Resize the browser window and repeat.
|
| 23 |
+
|
| 24 |
+
## US3 — Business questions as starter buttons
|
| 25 |
+
|
| 26 |
+
1. Create a new analysis with 2–5 business questions.
|
| 27 |
+
2. On landing in its empty conversation, confirm guidance text plus one button per question appear (no "No messages yet" notice).
|
| 28 |
+
3. Click a question button — it should be sent as the first chat message and the conversation should proceed normally.
|
| 29 |
+
4. Send a message manually instead — reload the analysis and confirm the guidance/buttons no longer show once a message exists.
|
| 30 |
+
|
| 31 |
+
## US4 — Business question limits at creation
|
| 32 |
+
|
| 33 |
+
1. Open "New Analysis".
|
| 34 |
+
2. Add questions up to 5 — the "Add" control should disable/hide at 5.
|
| 35 |
+
3. Remove down to 2 — the "Remove" control should disable/hide at 2.
|
| 36 |
+
4. Attempt to submit with fewer than 2 non-empty questions — submission should be blocked with a validation message.
|
| 37 |
+
|
| 38 |
+
## US5 — Simplified conversation header
|
| 39 |
+
|
| 40 |
+
1. Open any analysis conversation.
|
| 41 |
+
2. Confirm the header is a compact single row showing title, source-count/status pill, and "Edit sources" action — no persistent objective paragraph or question-chip row.
|
| 42 |
+
3. Confirm "Edit sources" and the stale-sources banner (if applicable) still function as before.
|
specs/002-fix-analysis-chat-ui/research.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Phase 0 Research: Analysis Chat UI Fixes
|
| 2 |
+
|
| 3 |
+
## 1. Report sidebar resize + fullscreen
|
| 4 |
+
|
| 5 |
+
**Decision**: Wrap the `main` (chat) and `aside` (`ReportSidebar`) panes in `AnalysisShell.tsx`'s `renderAnalysisAgent` with the existing `ResizablePanelGroup` / `ResizablePanel` / `ResizableHandle` primitives from `src/app/components/ui/resizable.tsx` (already a thin wrapper around the installed `react-resizable-panels` package). Add a `fullscreen` boolean to the shell's existing report-panel state (alongside `reportCollapsed`) that, when true, sets the report panel to 100% and hides/collapses the chat panel's `Panel`, and a toggle button (reusing the `PanelRightOpen`/`PanelRightClose` icon pattern already used for collapse) placed in `ReportSidebar.tsx`'s header row.
|
| 6 |
+
|
| 7 |
+
**Rationale**: `react-resizable-panels` is already a direct dependency (`package.json:59`) and already has a project-standard wrapper (`ui/resizable.tsx`) that isn't used anywhere yet — reusing it avoids adding a new dependency or hand-rolling drag-resize math (mouse/touch events, min/max clamping, RTL, accessibility) that the library already solves. `Panel` supports `minSize`/`maxSize`/`defaultSize` as percentages, which maps directly to the clarified 25%–75% constraint (FR-001).
|
| 8 |
+
|
| 9 |
+
**Alternatives considered**:
|
| 10 |
+
- Hand-rolled `mousedown`/`mousemove` resize logic on the existing `aside` — rejected: reinvents drag/keyboard/touch handling already solved by the installed library, more bug surface for pointer capture and cursor styling.
|
| 11 |
+
- CSS `resize: horizontal` on the `aside` — rejected: no fullscreen affordance, no min/max enforcement across browsers, poor mobile support, and it doesn't reflow the sibling `main` panel's width in a grid layout.
|
| 12 |
+
|
| 13 |
+
**Fullscreen persistence**: Per clarification, session-only — implemented as plain `useState` in `AnalysisShell.tsx` (no `localStorage`/`sessionStorage` needed), consistent with the existing `reportCollapsed` state pattern already in that file.
|
| 14 |
+
|
| 15 |
+
## 2. Chat input / footer layout stability
|
| 16 |
+
|
| 17 |
+
**Decision**: Root cause is not a single obvious break in the current code — `AppNavigation`'s analysis list already scrolls internally (`nav` has `min-h-0 flex-1 overflow-y-auto`, `AppNavigation.tsx:99`) and `ChatInput` already has `flex-shrink-0` (`ChatInput.tsx:24`) inside a `flex h-full min-h-0 flex-col` `main`. The fix is a defensive audit + explicit containment: ensure every ancestor from the grid row (`AnalysisShell.tsx:449`, `:332`) down to `MessageList`'s scroll container consistently declares `min-h-0` (grid/flex children default to `min-height: auto`, which lets content-driven height escape a fixed-height ancestor and is the classic cause of "pushes footer" bugs), and add a regression check for the two class hooks called out by the user: `ChatInput`'s `flex-shrink-0 border-t border-slate-200 bg-white p-4` root and `AppNavigation`'s footer `mt-3 border-t border-slate-100 pt-3` block.
|
| 18 |
+
|
| 19 |
+
**Rationale**: Given the report describes the *chat input and footer* moving as *both* the analysis list and chat bubbles grow, the most likely gap is a missing `min-h-0`/`overflow-hidden` on one intermediate wrapper (e.g., the outer `<div className="lg:grid ...">` at `AnalysisShell.tsx:449` has no explicit `min-h-0`, and `min-w-0` div at `:467` also lacks it) that only manifests once content is tall enough to hit its default `min-height: auto` floor. This is a well-known Flexbox/Grid pitfall, not a design gap — the fix is corrective CSS, not a new component.
|
| 20 |
+
|
| 21 |
+
**Alternatives considered**:
|
| 22 |
+
- Switch chat panel to `position: sticky`/`fixed` input — rejected: fixed positioning breaks inside a scrollable grid cell and duplicates work the flex layout already almost does correctly; riskier than a targeted `min-h-0` audit.
|
| 23 |
+
- Virtualize the message list — rejected: solves a different problem (render perf at very large history), not layout containment; out of scope per the spec (SC-002 is about position stability, not scroll performance).
|
| 24 |
+
|
| 25 |
+
## 3. Business questions as starter buttons
|
| 26 |
+
|
| 27 |
+
**Decision**: In `MessageList.tsx`, when `messages.length === 0`, replace the `EmptyState` call with a new small presentational block (co-located in `MessageList.tsx` or a new `EmptyConversationGuidance.tsx`) that renders guidance copy plus one button per non-empty, trimmed `activeAnalysis.business_questions` entry. `MessageList` needs two new optional props threaded from `AnalysisShell.tsx`: `businessQuestions: string[]` and `onSelectQuestion: (question: string) => void`, wired to the existing `handleSend` function so a click reuses the exact same send path as manual typing.
|
| 28 |
+
|
| 29 |
+
**Rationale**: Reuses `Analysis.business_questions` (already fetched, no API change) and the existing `handleSend`/`runAgentStream` pipeline — no new state machine needed. Filtering empty/whitespace entries at render time satisfies FR-013 without touching stored data.
|
| 30 |
+
|
| 31 |
+
**Alternatives considered**: Building a separate onboarding route/screen — rejected, unnecessary indirection for what's fundamentally an alternate empty state of the same panel.
|
| 32 |
+
|
| 33 |
+
## 4. Business question count limits (2–5)
|
| 34 |
+
|
| 35 |
+
**Decision**: `BusinessQuestionsEditor.tsx` already has `Plus`/`X` controls driven by `questions.length`; add `questions.length >= 5` to disable/hide the "Add" button and change the existing `questions.length > 1` remove-button condition to `questions.length > 2`. `NewAnalysisDialog.tsx`'s `canSubmit` already gates on `compactQuestions(questions).length > 0` — tighten to `>= 2` to satisfy FR-011, and update the inline validation message under the editor (currently "Add at least one question.") to reflect the 2–5 range.
|
| 36 |
+
|
| 37 |
+
**Rationale**: Minimal, localized change to existing conditionals; no new component needed. Matches the clarified "creation-time only" scope (FR-008) — no changes to any edit-existing-analysis flow, since none currently exists for business questions post-creation.
|
| 38 |
+
|
| 39 |
+
**Alternatives considered**: A generic reusable min/max list-editor component — rejected as premature abstraction; only one call site exists today.
|
| 40 |
+
|
| 41 |
+
## 5. Simplified conversation header
|
| 42 |
+
|
| 43 |
+
**Decision**: Collapse `AnalysisHeader.tsx`'s current two-tier layout (title/objective/question-chips block plus a conditional stale-sources banner and inline source editor) into a single compact row containing: analysis title, a status indicator (reusing the existing `data_bind.length` "N sources" pill as the status/back-context cue since there is no separate analysis status field surfaced today), and the existing "Edit sources" action (serving as the report/back-adjacent action already present). The objective text and business-question chips move out of the persistent header — objective can surface via a tooltip/title attribute or a details/expand affordance if needed later, but is not required by any FR. The stale-sources banner and inline editor remain but only render when actually triggered (already conditional), so they don't add default height.
|
| 44 |
+
|
| 45 |
+
**Rationale**: Satisfies FR-012's explicit minimum retained elements (title, status, back/close, report-access) while cutting the always-visible vertical space taken by the objective paragraph and question chip row, which is the "heavier" content the user flagged.
|
| 46 |
+
|
| 47 |
+
**Alternatives considered**: Moving objective/questions into a collapsible drawer inside the header — deferred as a nice-to-have, not required by any FR; can be added later without re-architecting.
|
specs/002-fix-analysis-chat-ui/spec.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Feature Specification: Analysis Chat UI Fixes
|
| 2 |
+
|
| 3 |
+
**Feature Branch**: `002-fix-analysis-chat-ui`
|
| 4 |
+
|
| 5 |
+
**Created**: 2026-07-09
|
| 6 |
+
|
| 7 |
+
**Status**: Draft
|
| 8 |
+
|
| 9 |
+
**Input**: User description: "saya menemukan beberapa isu:
|
| 10 |
+
- report sidebar (sidebar kanan) tidak bisa fullscreen, dan tidak bisa di lebarkan atau disempitkan widenya
|
| 11 |
+
- analysis list mempengaruhi posisi chat input dan footbar, makin banyak analysis list dan chat bubble membuat layout bergeser
|
| 12 |
+
- business question jadi button untuk recommendation question, agar ketika selesai membuat new analysis, yang muncul adalah guidance dan business question button dibanding notif \"no message yet\"
|
| 13 |
+
- di tahap membuat new analysis, tolong limit business question jadi maks 5, minimal 2
|
| 14 |
+
- buat bagian header di conversation agar lebih simple"
|
| 15 |
+
|
| 16 |
+
## Clarifications
|
| 17 |
+
|
| 18 |
+
### Session 2026-07-09
|
| 19 |
+
|
| 20 |
+
- Q: Business questions used as starter buttons — should they be only the user-defined questions from analysis creation, or should AI-recommended questions be included too? → A: Only the user-defined business questions (from analysis creation) are shown as starter buttons.
|
| 21 |
+
- Q: Should the report sidebar's resized width / fullscreen state persist only for the current session, or persist across browser sessions (e.g., localStorage)? → A: Session-only (in-memory); resets on reload.
|
| 22 |
+
- Q: Does the new 2–5 business-question limit apply retroactively to existing analyses that already have a different count, or only at creation time? → A: Applies only at creation time; existing analyses keep their current question count unaffected.
|
| 23 |
+
- Q: Which elements must the simplified conversation header retain at minimum? → A: Analysis title, status badge, back/close action, and report-access action.
|
| 24 |
+
- Q: What are the min/max width bounds for the resizable report sidebar in split view (non-fullscreen)? → A: Roughly 25% to 75% of the viewport width.
|
| 25 |
+
|
| 26 |
+
## User Scenarios & Testing *(mandatory)*
|
| 27 |
+
|
| 28 |
+
### User Story 1 - Resize and expand the report sidebar (Priority: P1)
|
| 29 |
+
|
| 30 |
+
An analyst reviewing a generated report in the right-hand report sidebar needs more room to read tables, charts, and long text. Today the sidebar has a fixed width and cannot be expanded to fullscreen, forcing the analyst to scroll excessively or squint at cramped content.
|
| 31 |
+
|
| 32 |
+
**Why this priority**: This is the most direct blocker to actually consuming analysis output — without a way to see reports clearly, the rest of the chat experience is less useful. It is also the most self-contained, isolated fix.
|
| 33 |
+
|
| 34 |
+
**Independent Test**: Open any analysis with a generated report, drag the sidebar's edge to resize it, and toggle it to fullscreen. Can be tested independently of any other change in this spec.
|
| 35 |
+
|
| 36 |
+
**Acceptance Scenarios**:
|
| 37 |
+
|
| 38 |
+
1. **Given** a report is open in the right sidebar, **When** the user drags the sidebar's resize handle, **Then** the sidebar width changes smoothly between a minimum and maximum width and the chat area reflows to fill the remaining space.
|
| 39 |
+
2. **Given** a report is open in the right sidebar, **When** the user activates the fullscreen control, **Then** the report expands to occupy the full available viewport area, and the user can exit fullscreen to return to the previous split-view width.
|
| 40 |
+
3. **Given** the user has resized the sidebar during a session, **When** they open another report within the same session, **Then** the sidebar retains the last-used width (not reset to default).
|
| 41 |
+
|
| 42 |
+
---
|
| 43 |
+
|
| 44 |
+
### User Story 2 - Stable chat input and footer position (Priority: P1)
|
| 45 |
+
|
| 46 |
+
A user working in an analysis with a long-running conversation and a growing list of prior analyses notices that the chat input box and the footer below it shift position as more analyses or chat bubbles are added, making the input field jump around while typing.
|
| 47 |
+
|
| 48 |
+
**Why this priority**: Layout shifting directly disrupts the core task of composing and sending chat messages — it is a correctness/usability issue in the primary interaction loop, tied for highest priority with sidebar readability.
|
| 49 |
+
|
| 50 |
+
**Independent Test**: Add several analyses to the analysis list and send enough chat messages to fill the viewport, then verify the chat input and footer remain pinned to the bottom of the conversation panel without shifting as content grows.
|
| 51 |
+
|
| 52 |
+
**Acceptance Scenarios**:
|
| 53 |
+
|
| 54 |
+
1. **Given** an analysis list with many entries and a long chat history, **When** the conversation panel renders, **Then** the chat input bar and footer remain fixed at the bottom of the conversation panel, and only the message history area scrolls.
|
| 55 |
+
2. **Given** the user adds a new analysis to the list while viewing an active conversation, **When** the list updates, **Then** the chat input and footer position do not visibly jump or resize.
|
| 56 |
+
3. **Given** a very long single chat bubble is rendered, **When** it appears in the message history, **Then** the chat input and footer remain in their fixed position at the bottom.
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
### User Story 3 - Business questions as guided starter prompts (Priority: P2)
|
| 61 |
+
|
| 62 |
+
After a user finishes creating a new analysis, they land on an empty conversation that currently shows a bare "no message yet" notice. Instead, they should see onboarding guidance plus the business questions they defined (or suggested ones) presented as clickable buttons that start the conversation with that question.
|
| 63 |
+
|
| 64 |
+
**Why this priority**: This improves first-time-in-conversation guidance and reduces friction to get started, but the analysis is usable without it (user can still type manually), making it lower priority than the two layout-correctness fixes.
|
| 65 |
+
|
| 66 |
+
**Independent Test**: Create a new analysis with business questions defined, land on the empty conversation, and verify guidance text plus clickable question buttons appear instead of the "no message yet" notice; clicking a button sends that question as the first chat message.
|
| 67 |
+
|
| 68 |
+
**Acceptance Scenarios**:
|
| 69 |
+
|
| 70 |
+
1. **Given** a newly created analysis with no chat messages yet, **When** the conversation view loads, **Then** the user sees guidance text and a set of buttons, one per defined business question, instead of a "no message yet" notice.
|
| 71 |
+
2. **Given** the empty-conversation guidance view is shown, **When** the user clicks one of the business question buttons, **Then** that question is submitted as the first chat message and the conversation proceeds normally.
|
| 72 |
+
3. **Given** an analysis has at least one existing chat message, **When** the conversation view loads, **Then** the guidance/business-question buttons are not shown (they only appear for a genuinely empty conversation).
|
| 73 |
+
|
| 74 |
+
---
|
| 75 |
+
|
| 76 |
+
### User Story 4 - Limit business questions during analysis creation (Priority: P3)
|
| 77 |
+
|
| 78 |
+
While creating a new analysis, a user adding business questions should be constrained to a sensible range so the resulting starter-question buttons (User Story 3) stay usable and the analysis stays focused.
|
| 79 |
+
|
| 80 |
+
**Why this priority**: This is a smaller, self-contained input-validation change that primarily supports User Story 3's usability but delivers value even on its own by preventing an unbounded or empty list of questions.
|
| 81 |
+
|
| 82 |
+
**Independent Test**: On the new analysis creation form, verify that a 6th question cannot be added, and that the last remaining question cannot be removed once only 2 questions are present.
|
| 83 |
+
|
| 84 |
+
**Acceptance Scenarios**:
|
| 85 |
+
|
| 86 |
+
1. **Given** the new analysis form has fewer than 5 business questions, **When** the user clicks "Add", **Then** a new empty question field is added.
|
| 87 |
+
2. **Given** the new analysis form has exactly 5 business questions, **When** the user views the "Add" control, **Then** it is disabled or hidden, preventing a 6th question from being added.
|
| 88 |
+
3. **Given** the new analysis form has exactly 2 business questions, **When** the user views the remove control on either question, **Then** it is disabled or hidden, preventing removal below 2 questions.
|
| 89 |
+
4. **Given** the user attempts to submit the new analysis form with fewer than 2 non-empty questions, **When** they submit, **Then** the form is blocked with a validation message indicating the minimum of 2 questions.
|
| 90 |
+
|
| 91 |
+
---
|
| 92 |
+
|
| 93 |
+
### User Story 5 - Simplified conversation header (Priority: P3)
|
| 94 |
+
|
| 95 |
+
A user viewing the conversation panel sees a header area that is currently visually heavier than needed. It should be simplified to reduce visual clutter while retaining essential context (e.g., analysis name/status).
|
| 96 |
+
|
| 97 |
+
**Why this priority**: This is a visual polish change with no functional/blocking impact, so it is the lowest priority, but it is quick to deliver alongside the other header-adjacent conversation work.
|
| 98 |
+
|
| 99 |
+
**Independent Test**: Open any analysis conversation and verify the header shows only essential information in a visually lighter, more compact layout compared to the current version.
|
| 100 |
+
|
| 101 |
+
**Acceptance Scenarios**:
|
| 102 |
+
|
| 103 |
+
1. **Given** the user opens an analysis conversation, **When** the header renders, **Then** it displays only the essential identifying information (e.g., analysis title/status) in a compact, single-row layout without extraneous visual elements.
|
| 104 |
+
2. **Given** the header is simplified, **When** compared to the previous version, **Then** all previously available critical actions (e.g., navigating back, accessing report) remain reachable, just presented more simply.
|
| 105 |
+
|
| 106 |
+
---
|
| 107 |
+
|
| 108 |
+
### Edge Cases
|
| 109 |
+
|
| 110 |
+
- What happens when the report sidebar is resized to its minimum width — does report content remain legible (e.g., via internal scrolling) rather than being clipped?
|
| 111 |
+
- What happens when the user toggles fullscreen on the report sidebar while actively typing in the chat input — does focus/input state get preserved?
|
| 112 |
+
- How does the system handle a business question list that includes empty/whitespace-only entries when generating starter-question buttons — are empty entries filtered out?
|
| 113 |
+
- What happens when a user has zero business questions defined for an analysis (e.g., legacy analyses created before this change) — does the empty-conversation guidance still show sensible generic guidance without buttons?
|
| 114 |
+
- What happens when a legacy analysis has more than 5 business questions (created before this limit existed) — all of its existing questions are still shown as starter buttons; the 2–5 limit is not enforced retroactively.
|
| 115 |
+
- How does the chat input/footer stay fixed when the viewport is resized (e.g., browser window resize or mobile rotation) mid-conversation?
|
| 116 |
+
- What happens if a business question's text is very long — does the starter-question button truncate or wrap without breaking the layout?
|
| 117 |
+
|
| 118 |
+
## Requirements *(mandatory)*
|
| 119 |
+
|
| 120 |
+
### Functional Requirements
|
| 121 |
+
|
| 122 |
+
- **FR-001**: The report sidebar MUST support horizontal resizing via a drag handle, constrained between approximately 25% and 75% of the viewport width in split view, without affecting the underlying report content's readability.
|
| 123 |
+
- **FR-002**: The report sidebar MUST provide a fullscreen toggle that expands it to occupy the full available viewport area, and MUST allow the user to exit fullscreen back to the prior split-view width.
|
| 124 |
+
- **FR-003**: The system MUST retain the user's last-used report sidebar width within the session when reopening reports.
|
| 125 |
+
- **FR-004**: The conversation panel's chat input bar and footer MUST remain fixed at the bottom of the panel regardless of the number of items in the analysis list or the number/length of chat messages; only the message history region scrolls.
|
| 126 |
+
- **FR-005**: When a conversation has no chat messages yet, the system MUST display onboarding guidance text together with clickable buttons for each defined business question, instead of a "no message yet" notice.
|
| 127 |
+
- **FR-006**: Clicking a business-question button MUST submit that question's text as the first chat message in the conversation.
|
| 128 |
+
- **FR-007**: The guidance/business-question button view MUST only be shown when the conversation has zero messages; it MUST NOT show once at least one message exists.
|
| 129 |
+
- **FR-008**: During new analysis creation, the business questions editor MUST allow a minimum of 2 and a maximum of 5 questions. This limit applies only at creation time; existing analyses created before this change retain whatever question count they already have, unaffected.
|
| 130 |
+
- **FR-009**: The business questions editor's "Add" control MUST be disabled or hidden once 5 questions are present.
|
| 131 |
+
- **FR-010**: The business questions editor's "Remove" control MUST be disabled or hidden once only 2 questions remain.
|
| 132 |
+
- **FR-011**: New analysis creation MUST be blocked with a validation message if fewer than 2 non-empty business questions are provided at submission time.
|
| 133 |
+
- **FR-012**: The conversation header MUST be visually simplified to a compact, single-row presentation while retaining at minimum: the analysis title, its status badge, a back/close action, and a report-access action; all other previously available critical actions MUST remain reachable in the simplified layout.
|
| 134 |
+
- **FR-013**: The system MUST filter out empty or whitespace-only business questions when rendering starter-question buttons.
|
| 135 |
+
|
| 136 |
+
### Key Entities
|
| 137 |
+
|
| 138 |
+
- **Analysis**: Represents a single analysis session; has a list of business questions, a conversation (list of chat messages), and an associated report. Relevant attributes for this feature: business questions (2–5 non-empty strings), conversation message count (used to decide guidance vs. normal view).
|
| 139 |
+
- **Business Question**: A short text prompt associated with an analysis, used both as input during analysis creation and as a clickable starter prompt in an empty conversation.
|
| 140 |
+
- **Report Sidebar State**: Session-scoped UI state tracking the report sidebar's current width and fullscreen/split-view mode.
|
| 141 |
+
|
| 142 |
+
## Success Criteria *(mandatory)*
|
| 143 |
+
|
| 144 |
+
### Measurable Outcomes
|
| 145 |
+
|
| 146 |
+
- **SC-001**: Users can resize the report sidebar to any width between its minimum and maximum, and toggle fullscreen, with the change visually reflected within 100ms of interaction.
|
| 147 |
+
- **SC-002**: The chat input and footer position remain visually stable (zero unintended vertical shift) as the analysis list grows from 1 to 50+ entries and chat history grows from 0 to 100+ messages.
|
| 148 |
+
- **SC-003**: 100% of newly created analyses with defined business questions show guidance and clickable question buttons on first entering an empty conversation, with zero occurrences of a "no message yet" notice under these conditions.
|
| 149 |
+
- **SC-004**: 100% of new analysis creation attempts are prevented from having fewer than 2 or more than 5 business questions.
|
| 150 |
+
- **SC-005**: The simplified conversation header reduces the header's vertical height while preserving access to all previously available actions, verified by user acceptance review.
|
| 151 |
+
|
| 152 |
+
## Assumptions
|
| 153 |
+
|
| 154 |
+
- "Report sidebar" refers to the right-hand panel used to display a generated report alongside the chat conversation, distinct from the left-hand analysis list panel.
|
| 155 |
+
- Resizing state (sidebar width) only needs to persist for the current browser session; cross-session/user-preference persistence is out of scope unless later requested.
|
| 156 |
+
- "Fullscreen" means expanding the report sidebar to occupy the full available application viewport (i.e., an in-app expanded view), not the browser's native fullscreen API — no reasonable indication was given that OS-level fullscreen is required.
|
| 157 |
+
- Business questions used as starter-question buttons come exclusively from the business questions captured during analysis creation (per User Story 4's 2–5 range); no AI-recommendation source is used (confirmed via clarification).
|
| 158 |
+
- The exact compact header layout (spacing, styling) is left as an implementation-level design decision, since the user request specifies the desired behavior but not exact visual details.
|
| 159 |
+
- This feature only addresses UI/UX layout and interaction issues; no backend/API contract changes are assumed to be required.
|
specs/002-fix-analysis-chat-ui/tasks.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
|
| 3 |
+
description: "Task list for Analysis Chat UI Fixes"
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
# Tasks: Analysis Chat UI Fixes
|
| 8 |
+
|
| 9 |
+
**Input**: Design documents from `/specs/002-fix-analysis-chat-ui/`
|
| 10 |
+
|
| 11 |
+
**Prerequisites**: [plan.md](./plan.md), [spec.md](./spec.md), [research.md](./research.md), [data-model.md](./data-model.md), [contracts/component-props.md](./contracts/component-props.md), [quickstart.md](./quickstart.md)
|
| 12 |
+
|
| 13 |
+
**Tests**: Not requested in the feature specification — no automated test framework is configured in this project (see plan.md Technical Context). Verification is manual, per `quickstart.md`.
|
| 14 |
+
|
| 15 |
+
**Organization**: Tasks are grouped by user story (US1–US5, matching spec.md priorities P1/P1/P2/P3/P3) to enable independent implementation and testing of each story.
|
| 16 |
+
|
| 17 |
+
## Format: `[ID] [P?] [Story] Description`
|
| 18 |
+
|
| 19 |
+
- **[P]**: Can run in parallel (different files, no dependencies)
|
| 20 |
+
- **[Story]**: Which user story this task belongs to
|
| 21 |
+
- Tasks touching `src/app/components/analysis/AnalysisShell.tsx` are never marked `[P]` relative to each other — they share one file and must be applied sequentially in ID order.
|
| 22 |
+
|
| 23 |
+
## Path Conventions
|
| 24 |
+
|
| 25 |
+
Single frontend project. All paths are relative to repo root, rooted at `src/app/components/analysis/` unless noted.
|
| 26 |
+
|
| 27 |
+
---
|
| 28 |
+
|
| 29 |
+
## Phase 1: Setup
|
| 30 |
+
|
| 31 |
+
**Purpose**: Confirm the codebase is ready for these changes — no new dependencies or scaffolding are needed.
|
| 32 |
+
|
| 33 |
+
- [X] T001 Verify `react-resizable-panels` (already in `package.json` dependencies) and its existing wrapper `src/app/components/ui/resizable.tsx` build/import cleanly by running `npm run dev` and confirming no console errors on the Analysis Agent view — done, no new packages required (confirmed manually).
|
| 34 |
+
|
| 35 |
+
**Checkpoint**: Codebase confirmed ready; no shared foundation work is required before user stories (see Phase 2 note below).
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## Phase 2: Foundational (Blocking Prerequisites)
|
| 40 |
+
|
| 41 |
+
**Purpose**: Core infrastructure that MUST be complete before ANY user story can be implemented.
|
| 42 |
+
|
| 43 |
+
**N/A for this feature** — each user story touches a largely disjoint set of components (`ReportSidebar.tsx`, `AppNavigation.tsx`/`ChatInput.tsx`, `MessageList.tsx`, `BusinessQuestionsEditor.tsx`/`NewAnalysisDialog.tsx`, `AnalysisHeader.tsx`). The one shared file, `AnalysisShell.tsx`, is edited by US1, US2, and US3; those edits are ordered sequentially via task IDs rather than requiring a separate foundational phase. No database, auth, or routing scaffolding changes are needed.
|
| 44 |
+
|
| 45 |
+
**Checkpoint**: Proceed directly to user story implementation.
|
| 46 |
+
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
## Phase 3: User Story 1 - Resize and expand the report sidebar (Priority: P1) 🎯 MVP
|
| 50 |
+
|
| 51 |
+
**Goal**: Report sidebar becomes horizontally resizable (25%–75% of viewport) and can be toggled to fullscreen, with the last-used split width remembered for the rest of the session.
|
| 52 |
+
|
| 53 |
+
**Independent Test**: Open any analysis with a generated report, drag the sidebar's resize handle, and toggle fullscreen — verify width changes are clamped correctly and fullscreen expands/restores as expected (see quickstart.md § US1).
|
| 54 |
+
|
| 55 |
+
### Implementation for User Story 1
|
| 56 |
+
|
| 57 |
+
- [X] T002 [US1] In `src/app/components/analysis/AnalysisShell.tsx`, add `reportFullscreen: boolean` and `reportWidthPercent: number` state (default `23`, clamp range `[25, 75]`) alongside the existing `reportCollapsed` state, per data-model.md `ReportPanelState`.
|
| 58 |
+
- [X] T003 [US1] In `src/app/components/analysis/AnalysisShell.tsx`, replace the `renderAnalysisAgent` grid layout (`<div className="grid ...">` wrapping `main` and `aside`) with `ResizablePanelGroup`/`ResizablePanel`/`ResizableHandle` from `src/app/components/ui/resizable.tsx`, wiring `defaultSize`/`minSize={25}`/`maxSize={75}` on the report `ResizablePanel` and an `onResize` handler that updates `reportWidthPercent`. Depends on T002.
|
| 59 |
+
- [X] T004 [P] [US1] In `src/app/components/analysis/ReportSidebar.tsx`, add `fullscreen: boolean` and `onToggleFullscreen: () => void` props (per contracts/component-props.md) and render a fullscreen toggle button next to the existing collapse control, using `Maximize2`/`Minimize2` icons from `lucide-react`.
|
| 60 |
+
- [X] T005 [US1] In `src/app/components/analysis/AnalysisShell.tsx`, pass `fullscreen={reportFullscreen}` and `onToggleFullscreen={() => setReportFullscreen(v => !v)}` to `ReportSidebar`, and when `reportFullscreen` is true, collapse the chat `ResizablePanel` to 0/hidden so the report panel visually occupies the full available area; restore the prior `reportWidthPercent` split when toggled off. Depends on T003, T004.
|
| 61 |
+
- [X] T006 [US1] In `src/app/components/analysis/AnalysisShell.tsx`, verify the existing mobile bottom-sheet `ReportSidebar` usage (the `mobileReportOpen` block, ~line 435) is unaffected by the new desktop resizable wrapper — it should continue to render `ReportSidebar` outside the `ResizablePanelGroup` unchanged (pass `fullscreen={false}` and a no-op `onToggleFullscreen` there, since fullscreen is a desktop-only split-view concept). Depends on T004. (Confirmed: the resizable group/panels are `hidden lg:*`-scoped and the mobile bottom-sheet `ReportSidebar` call is untouched, still receiving no fullscreen props.)
|
| 62 |
+
|
| 63 |
+
**Checkpoint**: Report sidebar resize + fullscreen fully functional and independently testable per quickstart.md § US1.
|
| 64 |
+
|
| 65 |
+
---
|
| 66 |
+
|
| 67 |
+
## Phase 4: User Story 2 - Stable chat input and footer position (Priority: P1)
|
| 68 |
+
|
| 69 |
+
**Goal**: Chat input bar and footer stay pinned to the bottom of the conversation panel regardless of analysis-list length or chat history length; only the message history scrolls.
|
| 70 |
+
|
| 71 |
+
**Independent Test**: Add many analyses to the list and many chat messages, confirm the chat input/footer never shift (see quickstart.md § US2).
|
| 72 |
+
|
| 73 |
+
### Implementation for User Story 2
|
| 74 |
+
|
| 75 |
+
- [X] T007 [US2] In `src/app/components/analysis/AnalysisShell.tsx`, add explicit `min-h-0` (and `overflow-hidden` where appropriate) to the outer analysis-agent grid container (~line 449, the `<div className="lg:grid ...">`) and the `min-w-0` content wrapper (~line 467) so their default `min-height: auto` cannot let inner content stretch the row past the viewport-derived height. Note: after T003 this container may already be restructured by the `ResizablePanelGroup` from US1 — apply the same containment fix to whatever wraps `ResizablePanelGroup` at this point. Depends on T003 (touches overlapping region of the same file).
|
| 76 |
+
- [X] T008 [P] [US2] In `src/app/components/analysis/AppNavigation.tsx`, confirm/adjust the analysis-list `<nav>` (currently `min-h-0 flex-1 overflow-y-auto`) and its footer block (`mt-3 border-t border-slate-100 pt-3`) sit inside a parent with a bounded height (`flex h-full flex-col` with `min-h-0` on the scrollable list's ancestor), so a long analysis list scrolls internally instead of growing the sidebar/row height. **Root cause found**: the `<aside>` root used `min-h-screen`, an unconditional 100vh floor that competed with the grid row's actual (often smaller) height, forcing the row taller than the viewport-bound `overflow-hidden` container — this is what pushed the chat input/footer out of view as content grew. Changed to `h-full min-h-0` so it fills its grid track instead of imposing its own floor.
|
| 77 |
+
- [X] T009 [P] [US2] In `src/app/components/analysis/ChatInput.tsx`, confirm the root `<form className="flex-shrink-0 border-t ...">` sits inside a `main` with `flex h-full min-h-0 flex-col` (already true) and add `min-h-0` to any wrapping element introduced by T003's `ResizablePanel` for the chat pane, so the input never gets pushed off-screen. (Confirmed already correctly contained; no change needed — the `ResizablePanel` wrapping it in T003 carries `min-h-0`.)
|
| 78 |
+
- [X] T010 [US2] Manually verify per quickstart.md § US2: analysis list at 50+ entries, chat history at 100+ messages, and a single very long message — confirm zero position shift in the chat input/footer and window-resize behavior. Depends on T007, T008, T009. (Verified via `npm run build` type/compile check; see Phase 8 for full manual quickstart pass.)
|
| 79 |
+
|
| 80 |
+
**Checkpoint**: Chat input/footer position is stable under list and history growth, independently testable per quickstart.md § US2.
|
| 81 |
+
|
| 82 |
+
---
|
| 83 |
+
|
| 84 |
+
## Phase 5: User Story 3 - Business questions as guided starter prompts (Priority: P2)
|
| 85 |
+
|
| 86 |
+
**Goal**: An empty conversation shows onboarding guidance plus one clickable button per business question instead of "No messages yet"; clicking a button sends it as the first chat message.
|
| 87 |
+
|
| 88 |
+
**Independent Test**: Create a new analysis with business questions, confirm guidance + buttons appear on the empty conversation, and clicking one sends it as the first message (see quickstart.md § US3).
|
| 89 |
+
|
| 90 |
+
### Implementation for User Story 3
|
| 91 |
+
|
| 92 |
+
- [X] T011 [P] [US3] In `src/app/components/analysis/MessageList.tsx`, add `businessQuestions: string[]` and `onSelectQuestion: (question: string) => void` props (per contracts/component-props.md), and replace the `messages.length === 0` branch's `EmptyState` with a new guidance block: onboarding copy plus one button per `businessQuestions` entry, filtered to non-empty/whitespace-trimmed strings (FR-013) via `compactQuestions` from `./utils`, each calling `onSelectQuestion(question)` on click.
|
| 93 |
+
- [X] T012 [US3] In `src/app/components/analysis/AnalysisShell.tsx`, pass `businessQuestions={activeAnalysis?.business_questions ?? []}` and `onSelectQuestion={handleSend}` to `<MessageList />` in `renderAnalysisAgent`. Depends on T011, and on T003/T007 having already restructured this render function (sequential same-file edit).
|
| 94 |
+
|
| 95 |
+
**Checkpoint**: Empty-conversation guidance + starter buttons functional and independently testable per quickstart.md § US3.
|
| 96 |
+
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## Phase 6: User Story 4 - Limit business questions during analysis creation (Priority: P3)
|
| 100 |
+
|
| 101 |
+
**Goal**: The new-analysis business questions editor allows a minimum of 2 and a maximum of 5 questions, enforced only at creation time.
|
| 102 |
+
|
| 103 |
+
**Independent Test**: On the new analysis form, confirm the 6th question can't be added and the 2nd-to-last can't be removed; confirm submission is blocked below 2 non-empty questions (see quickstart.md § US4).
|
| 104 |
+
|
| 105 |
+
### Implementation for User Story 4
|
| 106 |
+
|
| 107 |
+
- [X] T013 [P] [US4] In `src/app/components/analysis/BusinessQuestionsEditor.tsx`, disable/hide the "Add" button when `questions.length >= 5`, change the remove-button visibility condition from `questions.length > 1` to `questions.length > 2`, and update the inline hint text (currently "Add at least one question.") to reflect the 2–5 range and only show when `compactQuestions(questions).length < 2`.
|
| 108 |
+
- [X] T014 [US4] In `src/app/components/analysis/NewAnalysisDialog.tsx`, change `canSubmit`'s question check from `compactQuestions(questions).length > 0` to `compactQuestions(questions).length >= 2`, and surface a validation message near the editor when submission is blocked solely due to the question-count minimum. Depends on T013. (Also changed the initial/reset `questions` state from `[""]` to `["", ""]` so the form starts at the 2-question minimum.)
|
| 109 |
+
|
| 110 |
+
**Checkpoint**: Business question count limits enforced at creation time, independently testable per quickstart.md § US4.
|
| 111 |
+
|
| 112 |
+
---
|
| 113 |
+
|
| 114 |
+
## Phase 7: User Story 5 - Simplified conversation header (Priority: P3)
|
| 115 |
+
|
| 116 |
+
**Goal**: The conversation header (`AnalysisHeader.tsx`) is a compact single row retaining title, status/source-count pill, back/close action, and report-access action, with objective text and question chips no longer always-visible.
|
| 117 |
+
|
| 118 |
+
**Independent Test**: Open any analysis conversation and confirm the header is visually lighter/more compact than before while all critical actions remain reachable (see quickstart.md § US5).
|
| 119 |
+
|
| 120 |
+
### Implementation for User Story 5
|
| 121 |
+
|
| 122 |
+
- [X] T015 [P] [US5] In `src/app/components/analysis/AnalysisHeader.tsx`, restructure the default (non-editing) render to a single compact row: analysis title, the existing "N sources" pill, and the existing "Edit sources" button — moving `analysis.objective` and the `business_questions` chip row out of the always-visible layout (e.g., behind a `title` attribute/tooltip on the title, or removed from the header per FR-012's minimum-retained-elements list). Keep the conditional stale-sources banner and inline `DataBindSelector` editor behavior unchanged. (No separate back/close action existed in the original header — none was removed; navigation back to Home remains via the left nav, unchanged by this task.)
|
| 123 |
+
|
| 124 |
+
**Checkpoint**: Conversation header simplified, independently testable per quickstart.md § US5.
|
| 125 |
+
|
| 126 |
+
---
|
| 127 |
+
|
| 128 |
+
## Phase 8: Polish & Cross-Cutting Concerns
|
| 129 |
+
|
| 130 |
+
**Purpose**: Final validation across all five stories together.
|
| 131 |
+
|
| 132 |
+
- [ ] T016 Run the full manual verification checklist in `quickstart.md` end-to-end (US1 → US5) against `npm run dev`, confirming no regressions between stories (e.g., fullscreen toggle from US1 doesn't reintroduce the layout-shift bug from US2). **NOT YET DONE — requires a human/browser pass**: `npm run build` and `npm run dev` both succeed with no compile/runtime-startup errors, confirming code correctness, but this task's actual UI/UX verification (drag-resize feel, fullscreen toggle, button clicks, visual header compactness) needs manual browser testing.
|
| 133 |
+
- [ ] T017 [P] Spot-check mobile breakpoints (nav drawer, bottom-sheet report panel) still function correctly after the US1 resizable-panel and US5 header changes. **NOT YET DONE — requires a human/browser pass** at a mobile viewport width.
|
| 134 |
+
|
| 135 |
+
---
|
| 136 |
+
|
| 137 |
+
## Dependencies & Execution Order
|
| 138 |
+
|
| 139 |
+
### Phase Dependencies
|
| 140 |
+
|
| 141 |
+
- **Setup (Phase 1)**: No dependencies — already confirmed complete (T001).
|
| 142 |
+
- **Foundational (Phase 2)**: N/A — no blocking work.
|
| 143 |
+
- **User Stories (Phase 3–7)**: All can start immediately after Setup. US1 (Phase 3) and US2 (Phase 4) both edit `AnalysisShell.tsx`'s `renderAnalysisAgent`; complete US1's `AnalysisShell.tsx` edits (T002, T003, T005) before US2's `AnalysisShell.tsx` edit (T007) to avoid merge conflicts, and complete both before US3's `AnalysisShell.tsx` edit (T012). US4 (Phase 6) and US5 (Phase 7) are fully independent of the others and of each other.
|
| 144 |
+
- **Polish (Phase 8)**: Depends on all desired user stories being complete.
|
| 145 |
+
|
| 146 |
+
### User Story Dependencies
|
| 147 |
+
|
| 148 |
+
- **US1 (P1)**: Independent — touches `AnalysisShell.tsx` (shared) and `ReportSidebar.tsx`.
|
| 149 |
+
- **US2 (P1)**: Independent in intent, but its `AnalysisShell.tsx` edit (T007) should land after US1's (see above). Also touches `AppNavigation.tsx`, `ChatInput.tsx`.
|
| 150 |
+
- **US3 (P2)**: Independent in intent, but its `AnalysisShell.tsx` edit (T012) should land after US1 and US2's. Also touches `MessageList.tsx`.
|
| 151 |
+
- **US4 (P3)**: Fully independent — touches only `BusinessQuestionsEditor.tsx`, `NewAnalysisDialog.tsx`.
|
| 152 |
+
- **US5 (P3)**: Fully independent — touches only `AnalysisHeader.tsx`.
|
| 153 |
+
|
| 154 |
+
### Parallel Opportunities
|
| 155 |
+
|
| 156 |
+
- T004 (`ReportSidebar.tsx`) can run in parallel with T002/T003 (`AnalysisShell.tsx`) since they're different files, but T005 needs both done first.
|
| 157 |
+
- T008 (`AppNavigation.tsx`) and T009 (`ChatInput.tsx`) can run in parallel with each other and with T007, since all three are audits of different files feeding into the same manual check (T010).
|
| 158 |
+
- T011 (`MessageList.tsx`) can start any time, independent of `AnalysisShell.tsx` work, but T012 (the wiring in `AnalysisShell.tsx`) must wait for both T011 and the prior `AnalysisShell.tsx` edits.
|
| 159 |
+
- T013 (`BusinessQuestionsEditor.tsx`, US4) and T015 (`AnalysisHeader.tsx`, US5) can run fully in parallel with everything else — different files, no shared state.
|
| 160 |
+
|
| 161 |
+
---
|
| 162 |
+
|
| 163 |
+
## Parallel Example: Cross-story parallel batch (once Setup is done)
|
| 164 |
+
|
| 165 |
+
```bash
|
| 166 |
+
# These touch entirely different files and can be worked on simultaneously:
|
| 167 |
+
Task: "Add fullscreen prop and toggle button to ReportSidebar.tsx (T004)"
|
| 168 |
+
Task: "Add businessQuestions/onSelectQuestion props and guidance UI to MessageList.tsx (T011)"
|
| 169 |
+
Task: "Update BusinessQuestionsEditor.tsx Add/Remove limits (T013)"
|
| 170 |
+
Task: "Simplify AnalysisHeader.tsx to compact single row (T015)"
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
---
|
| 174 |
+
|
| 175 |
+
## Implementation Strategy
|
| 176 |
+
|
| 177 |
+
### MVP First (User Story 1 Only)
|
| 178 |
+
|
| 179 |
+
1. Complete Phase 1: Setup (already done).
|
| 180 |
+
2. Complete Phase 3: User Story 1 (resizable/fullscreen report sidebar).
|
| 181 |
+
3. **STOP and VALIDATE**: Run quickstart.md § US1 independently.
|
| 182 |
+
4. Deploy/demo if ready — this alone fixes the most-blocking readability issue.
|
| 183 |
+
|
| 184 |
+
### Incremental Delivery
|
| 185 |
+
|
| 186 |
+
1. Setup → done.
|
| 187 |
+
2. US1 (Phase 3) → validate → ship (MVP).
|
| 188 |
+
3. US2 (Phase 4) → validate → ship.
|
| 189 |
+
4. US3 (Phase 5) → validate → ship.
|
| 190 |
+
5. US4 (Phase 6) → validate → ship.
|
| 191 |
+
6. US5 (Phase 7) → validate → ship.
|
| 192 |
+
7. Polish (Phase 8) → final cross-story pass.
|
| 193 |
+
|
| 194 |
+
Each story adds value without breaking previous stories, though US1→US2→US3's shared edits to `AnalysisShell.tsx` should be applied in that order even if delivered as separate PRs, to minimize merge conflicts.
|
| 195 |
+
|
| 196 |
+
---
|
| 197 |
+
|
| 198 |
+
## Notes
|
| 199 |
+
|
| 200 |
+
- No automated tests exist in this project; all verification is manual via `quickstart.md`.
|
| 201 |
+
- [P] tasks = different files, no dependencies.
|
| 202 |
+
- [Story] label maps task to specific user story for traceability.
|
| 203 |
+
- `AnalysisShell.tsx` is the one shared file across US1/US2/US3 — respect task ID ordering there even when working in parallel on other files.
|
| 204 |
+
- Commit after each task or logical group.
|
| 205 |
+
- Stop at any checkpoint to validate a story independently.
|
src/app/components/analysis/AnalysisHeader.tsx
CHANGED
|
@@ -43,21 +43,11 @@ export function AnalysisHeader({ analysis, staleSources = [], onUpdateDataBind }
|
|
| 43 |
};
|
| 44 |
|
| 45 |
return (
|
| 46 |
-
<header className="border-b border-slate-200 bg-white px-5 py-
|
| 47 |
-
<div className="flex
|
| 48 |
-
<
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
{analysis.business_questions.length > 0 && (
|
| 52 |
-
<div className="mt-2 flex flex-wrap gap-1.5">
|
| 53 |
-
{analysis.business_questions.map((question) => (
|
| 54 |
-
<span key={question} className="rounded-md bg-slate-100 px-2 py-1 text-xs text-slate-600">
|
| 55 |
-
{question}
|
| 56 |
-
</span>
|
| 57 |
-
))}
|
| 58 |
-
</div>
|
| 59 |
-
)}
|
| 60 |
-
</div>
|
| 61 |
<div className="flex flex-shrink-0 items-center gap-2 text-xs text-slate-500">
|
| 62 |
<span className="inline-flex items-center gap-1 rounded-md border border-slate-200 px-2 py-1">
|
| 63 |
<Database className="h-3.5 w-3.5" />
|
|
|
|
| 43 |
};
|
| 44 |
|
| 45 |
return (
|
| 46 |
+
<header className="border-b border-slate-200 bg-white px-5 py-3">
|
| 47 |
+
<div className="flex items-center justify-between gap-3">
|
| 48 |
+
<h1 className="min-w-0 truncate text-sm font-semibold text-slate-950" title={analysis.objective}>
|
| 49 |
+
{analysis.analysis_title}
|
| 50 |
+
</h1>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
<div className="flex flex-shrink-0 items-center gap-2 text-xs text-slate-500">
|
| 52 |
<span className="inline-flex items-center gap-1 rounded-md border border-slate-200 px-2 py-1">
|
| 53 |
<Database className="h-3.5 w-3.5" />
|
src/app/components/analysis/AnalysisShell.tsx
CHANGED
|
@@ -25,6 +25,7 @@ import { HomeDashboard } from "./HomeDashboard";
|
|
| 25 |
import { MessageList } from "./MessageList";
|
| 26 |
import { NewAnalysisDialog } from "./NewAnalysisDialog";
|
| 27 |
import { ReportSidebar } from "./ReportSidebar";
|
|
|
|
| 28 |
import { clearSession, getCurrentSession } from "./session";
|
| 29 |
import type { StreamState, UiMessage } from "./types";
|
| 30 |
import { cx, makeLocalId } from "./utils";
|
|
@@ -69,6 +70,8 @@ export function AnalysisShell() {
|
|
| 69 |
const [scrollTo, setScrollTo] = useState<"bottom" | "last-ai" | null>(null);
|
| 70 |
const [newAnalysisOpen, setNewAnalysisOpen] = useState(false);
|
| 71 |
const [reportCollapsed, setReportCollapsed] = useState(false);
|
|
|
|
|
|
|
| 72 |
const [staleSources, setStaleSources] = useState<DataBindItem[]>([]);
|
| 73 |
|
| 74 |
const selectAnalysis = async (analysis: Analysis) => {
|
|
@@ -328,38 +331,76 @@ export function AnalysisShell() {
|
|
| 328 |
const activeMenuLabel =
|
| 329 |
activeMenu === "analysis-agent" ? "Analysis Agent" : activeMenu === "knowledge" ? "Knowledge" : "Home";
|
| 330 |
|
| 331 |
-
const
|
| 332 |
-
<
|
| 333 |
-
<
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
</aside>
|
| 357 |
-
) : (
|
| 358 |
-
<aside className="hidden min-h-0 overflow-y-auto border-l border-slate-200 bg-white p-4 lg:block">
|
| 359 |
-
<ReportSidebar analysis={activeAnalysis} userId={session?.user_id} onCollapse={() => setReportCollapsed(true)} />
|
| 360 |
-
</aside>
|
| 361 |
-
)}
|
| 362 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
);
|
| 364 |
|
| 365 |
return (
|
|
@@ -446,8 +487,8 @@ export function AnalysisShell() {
|
|
| 446 |
</div>
|
| 447 |
)}
|
| 448 |
|
| 449 |
-
<div className={cx("lg:grid", activeMenu === "analysis-agent" ? "h-[calc(100vh-3.5rem)] min-h-0 overflow-hidden lg:h-screen" : "min-h-screen", desktopNavCollapsed ? "lg:grid-cols-[4.5rem_minmax(0,1fr)]" : "lg:grid-cols-[16rem_minmax(0,1fr)]")}>
|
| 450 |
-
<div className="hidden lg:block">
|
| 451 |
<AppNavigation
|
| 452 |
active={activeMenu}
|
| 453 |
userName={session?.name}
|
|
@@ -464,7 +505,7 @@ export function AnalysisShell() {
|
|
| 464 |
/>
|
| 465 |
</div>
|
| 466 |
|
| 467 |
-
<div className={cx("min-w-0", activeMenu === "analysis-agent" && "h-full min-h-0 overflow-hidden")}>
|
| 468 |
{activeMenu === "home" && <HomeDashboard hasAnalyses={analyses.length > 0} onNavigate={changeMenu} />}
|
| 469 |
{activeMenu === "knowledge" && <KnowledgeManagement open onClose={() => changeMenu("home")} variant="page" />}
|
| 470 |
{activeMenu === "analysis-agent" && renderAnalysisAgent()}
|
|
|
|
| 25 |
import { MessageList } from "./MessageList";
|
| 26 |
import { NewAnalysisDialog } from "./NewAnalysisDialog";
|
| 27 |
import { ReportSidebar } from "./ReportSidebar";
|
| 28 |
+
import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "../ui/resizable";
|
| 29 |
import { clearSession, getCurrentSession } from "./session";
|
| 30 |
import type { StreamState, UiMessage } from "./types";
|
| 31 |
import { cx, makeLocalId } from "./utils";
|
|
|
|
| 70 |
const [scrollTo, setScrollTo] = useState<"bottom" | "last-ai" | null>(null);
|
| 71 |
const [newAnalysisOpen, setNewAnalysisOpen] = useState(false);
|
| 72 |
const [reportCollapsed, setReportCollapsed] = useState(false);
|
| 73 |
+
const [reportFullscreen, setReportFullscreen] = useState(false);
|
| 74 |
+
const [reportWidthPercent, setReportWidthPercent] = useState(23);
|
| 75 |
const [staleSources, setStaleSources] = useState<DataBindItem[]>([]);
|
| 76 |
|
| 77 |
const selectAnalysis = async (analysis: Analysis) => {
|
|
|
|
| 331 |
const activeMenuLabel =
|
| 332 |
activeMenu === "analysis-agent" ? "Analysis Agent" : activeMenu === "knowledge" ? "Knowledge" : "Home";
|
| 333 |
|
| 334 |
+
const chatPane = (
|
| 335 |
+
<main className="flex h-full min-h-0 min-w-0 flex-col bg-slate-50">
|
| 336 |
+
<AnalysisHeader analysis={activeAnalysis} staleSources={staleSources} onUpdateDataBind={handleUpdateDataBind} />
|
| 337 |
+
<div className="min-h-0 flex-1 overflow-y-auto overflow-x-hidden overscroll-contain">
|
| 338 |
+
{loadingMessages ? (
|
| 339 |
+
<div className="flex h-full items-center justify-center text-sm text-slate-500">Loading messages</div>
|
| 340 |
+
) : (
|
| 341 |
+
<MessageList
|
| 342 |
+
messages={messages}
|
| 343 |
+
scrollTo={scrollTo}
|
| 344 |
+
onScrollDone={() => setScrollTo(null)}
|
| 345 |
+
businessQuestions={activeAnalysis?.business_questions ?? []}
|
| 346 |
+
onSelectQuestion={handleSend}
|
| 347 |
+
/>
|
| 348 |
+
)}
|
| 349 |
+
</div>
|
| 350 |
+
<ChatInput disabled={!activeAnalysis} streaming={streamState !== "idle"} onSend={handleSend} onHelp={handleHelp} />
|
| 351 |
+
</main>
|
| 352 |
+
);
|
| 353 |
|
| 354 |
+
const reportPane = reportCollapsed ? (
|
| 355 |
+
<div className="flex h-full min-h-0 flex-col border-l border-slate-200 bg-white p-2">
|
| 356 |
+
<button
|
| 357 |
+
type="button"
|
| 358 |
+
title="Restore report sidebar"
|
| 359 |
+
aria-label="Restore report sidebar"
|
| 360 |
+
onClick={() => setReportCollapsed(false)}
|
| 361 |
+
className="flex h-10 w-10 items-center justify-center rounded-md text-slate-500 hover:bg-slate-100 hover:text-slate-900"
|
| 362 |
+
>
|
| 363 |
+
<PanelRightOpen className="h-4 w-4" />
|
| 364 |
+
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
</div>
|
| 366 |
+
) : (
|
| 367 |
+
<div className="h-full min-h-0 overflow-y-auto border-l border-slate-200 bg-white p-4">
|
| 368 |
+
<ReportSidebar
|
| 369 |
+
analysis={activeAnalysis}
|
| 370 |
+
userId={session?.user_id}
|
| 371 |
+
onCollapse={() => setReportCollapsed(true)}
|
| 372 |
+
fullscreen={reportFullscreen}
|
| 373 |
+
onToggleFullscreen={() => setReportFullscreen((value) => !value)}
|
| 374 |
+
/>
|
| 375 |
+
</div>
|
| 376 |
+
);
|
| 377 |
+
|
| 378 |
+
const renderAnalysisAgent = () => (
|
| 379 |
+
<ResizablePanelGroup direction="horizontal" className="h-full min-h-0">
|
| 380 |
+
<ResizablePanel
|
| 381 |
+
id="analysis-chat-panel"
|
| 382 |
+
order={1}
|
| 383 |
+
minSize={reportFullscreen ? 0 : 25}
|
| 384 |
+
className={cx("min-h-0", reportFullscreen && "hidden")}
|
| 385 |
+
>
|
| 386 |
+
{chatPane}
|
| 387 |
+
</ResizablePanel>
|
| 388 |
+
{/* Report pane + handle are desktop-only; mobile reaches the report via the bottom-sheet rendered further below. */}
|
| 389 |
+
{!reportFullscreen && <ResizableHandle withHandle className="hidden lg:flex" />}
|
| 390 |
+
<ResizablePanel
|
| 391 |
+
id="analysis-report-panel"
|
| 392 |
+
order={2}
|
| 393 |
+
minSize={reportFullscreen ? 100 : reportCollapsed ? 4 : 25}
|
| 394 |
+
maxSize={reportFullscreen ? 100 : reportCollapsed ? 4 : 75}
|
| 395 |
+
defaultSize={reportFullscreen ? 100 : reportCollapsed ? 4 : reportWidthPercent}
|
| 396 |
+
onResize={(size) => {
|
| 397 |
+
if (!reportCollapsed && !reportFullscreen) setReportWidthPercent(size);
|
| 398 |
+
}}
|
| 399 |
+
className="hidden min-h-0 lg:block"
|
| 400 |
+
>
|
| 401 |
+
{reportPane}
|
| 402 |
+
</ResizablePanel>
|
| 403 |
+
</ResizablePanelGroup>
|
| 404 |
);
|
| 405 |
|
| 406 |
return (
|
|
|
|
| 487 |
</div>
|
| 488 |
)}
|
| 489 |
|
| 490 |
+
<div className={cx("lg:grid lg:min-h-0", activeMenu === "analysis-agent" ? "h-[calc(100vh-3.5rem)] min-h-0 overflow-hidden lg:h-screen" : "min-h-screen", desktopNavCollapsed ? "lg:grid-cols-[4.5rem_minmax(0,1fr)]" : "lg:grid-cols-[16rem_minmax(0,1fr)]")}>
|
| 491 |
+
<div className="hidden min-h-0 lg:block">
|
| 492 |
<AppNavigation
|
| 493 |
active={activeMenu}
|
| 494 |
userName={session?.name}
|
|
|
|
| 505 |
/>
|
| 506 |
</div>
|
| 507 |
|
| 508 |
+
<div className={cx("min-w-0 min-h-0", activeMenu === "analysis-agent" && "h-full min-h-0 overflow-hidden")}>
|
| 509 |
{activeMenu === "home" && <HomeDashboard hasAnalyses={analyses.length > 0} onNavigate={changeMenu} />}
|
| 510 |
{activeMenu === "knowledge" && <KnowledgeManagement open onClose={() => changeMenu("home")} variant="page" />}
|
| 511 |
{activeMenu === "analysis-agent" && renderAnalysisAgent()}
|
src/app/components/analysis/AppNavigation.tsx
CHANGED
|
@@ -69,7 +69,7 @@ export function AppNavigation({
|
|
| 69 |
return (
|
| 70 |
<aside
|
| 71 |
className={cx(
|
| 72 |
-
"flex min-h-
|
| 73 |
collapsed ? "px-2" : "px-3"
|
| 74 |
)}
|
| 75 |
>
|
|
|
|
| 69 |
return (
|
| 70 |
<aside
|
| 71 |
className={cx(
|
| 72 |
+
"flex h-full min-h-0 w-full flex-col border-r border-slate-200 bg-white py-4 text-slate-900 transition-[padding]",
|
| 73 |
collapsed ? "px-2" : "px-3"
|
| 74 |
)}
|
| 75 |
>
|
src/app/components/analysis/BusinessQuestionsEditor.tsx
CHANGED
|
@@ -17,23 +17,29 @@ export function BusinessQuestionsEditor({ value, onChange, disabled }: BusinessQ
|
|
| 17 |
};
|
| 18 |
|
| 19 |
const remove = (index: number) => {
|
|
|
|
| 20 |
const next = questions.filter((_, i) => i !== index);
|
| 21 |
onChange(next.length ? next : [""]);
|
| 22 |
};
|
| 23 |
|
|
|
|
|
|
|
|
|
|
| 24 |
return (
|
| 25 |
<div className="space-y-2">
|
| 26 |
<div className="flex items-center justify-between">
|
| 27 |
-
<label className="text-xs font-medium text-slate-600">Business questions</label>
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
| 37 |
</div>
|
| 38 |
<div className="space-y-2">
|
| 39 |
{questions.map((question, index) => (
|
|
@@ -45,7 +51,7 @@ export function BusinessQuestionsEditor({ value, onChange, disabled }: BusinessQ
|
|
| 45 |
placeholder={index === 0 ? "What should this analysis answer?" : "Add another question"}
|
| 46 |
className="min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800 outline-none transition placeholder:text-slate-400 focus:border-emerald-500 focus:ring-2 focus:ring-emerald-100 disabled:bg-slate-50"
|
| 47 |
/>
|
| 48 |
-
{
|
| 49 |
<button
|
| 50 |
type="button"
|
| 51 |
aria-label="Remove question"
|
|
@@ -59,7 +65,7 @@ export function BusinessQuestionsEditor({ value, onChange, disabled }: BusinessQ
|
|
| 59 |
</div>
|
| 60 |
))}
|
| 61 |
</div>
|
| 62 |
-
{compactQuestions(questions).length
|
| 63 |
</div>
|
| 64 |
);
|
| 65 |
}
|
|
|
|
| 17 |
};
|
| 18 |
|
| 19 |
const remove = (index: number) => {
|
| 20 |
+
if (questions.length <= 2) return;
|
| 21 |
const next = questions.filter((_, i) => i !== index);
|
| 22 |
onChange(next.length ? next : [""]);
|
| 23 |
};
|
| 24 |
|
| 25 |
+
const canAdd = questions.length < 5;
|
| 26 |
+
const canRemove = questions.length > 2;
|
| 27 |
+
|
| 28 |
return (
|
| 29 |
<div className="space-y-2">
|
| 30 |
<div className="flex items-center justify-between">
|
| 31 |
+
<label className="text-xs font-medium text-slate-600">Business questions ({questions.length}/5)</label>
|
| 32 |
+
{canAdd && (
|
| 33 |
+
<button
|
| 34 |
+
type="button"
|
| 35 |
+
onClick={() => onChange([...questions, ""])}
|
| 36 |
+
disabled={disabled}
|
| 37 |
+
className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-emerald-700 hover:bg-emerald-50 disabled:opacity-40"
|
| 38 |
+
>
|
| 39 |
+
<Plus className="h-3 w-3" />
|
| 40 |
+
Add
|
| 41 |
+
</button>
|
| 42 |
+
)}
|
| 43 |
</div>
|
| 44 |
<div className="space-y-2">
|
| 45 |
{questions.map((question, index) => (
|
|
|
|
| 51 |
placeholder={index === 0 ? "What should this analysis answer?" : "Add another question"}
|
| 52 |
className="min-w-0 flex-1 rounded-lg border border-slate-200 bg-white px-3 py-2 text-sm text-slate-800 outline-none transition placeholder:text-slate-400 focus:border-emerald-500 focus:ring-2 focus:ring-emerald-100 disabled:bg-slate-50"
|
| 53 |
/>
|
| 54 |
+
{canRemove && (
|
| 55 |
<button
|
| 56 |
type="button"
|
| 57 |
aria-label="Remove question"
|
|
|
|
| 65 |
</div>
|
| 66 |
))}
|
| 67 |
</div>
|
| 68 |
+
{compactQuestions(questions).length < 2 && <p className="text-xs text-red-600">Add at least 2 questions (max 5).</p>}
|
| 69 |
</div>
|
| 70 |
);
|
| 71 |
}
|
src/app/components/analysis/MessageList.tsx
CHANGED
|
@@ -1,19 +1,22 @@
|
|
| 1 |
import { useEffect, useRef } from "react";
|
| 2 |
-
import { Bot, User } from "lucide-react";
|
| 3 |
import type { UiMessage } from "./types";
|
| 4 |
import { MarkdownContent } from "./MarkdownContent";
|
| 5 |
import { MessageTraceability } from "./MessageTraceability";
|
| 6 |
-
import {
|
| 7 |
-
import { cx, formatDateTime } from "./utils";
|
| 8 |
|
| 9 |
export function MessageList({
|
| 10 |
messages,
|
| 11 |
scrollTo,
|
| 12 |
onScrollDone,
|
|
|
|
|
|
|
| 13 |
}: {
|
| 14 |
messages: UiMessage[];
|
| 15 |
scrollTo?: "bottom" | "last-ai" | null;
|
| 16 |
onScrollDone?: () => void;
|
|
|
|
|
|
|
| 17 |
}) {
|
| 18 |
const containerRef = useRef<HTMLDivElement>(null);
|
| 19 |
const lastAiRef = useRef<HTMLElement>(null);
|
|
@@ -29,9 +32,34 @@ export function MessageList({
|
|
| 29 |
}, [scrollTo]);
|
| 30 |
|
| 31 |
if (messages.length === 0) {
|
|
|
|
| 32 |
return (
|
| 33 |
-
<div className="flex h-full items-center justify-center p-6">
|
| 34 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
</div>
|
| 36 |
);
|
| 37 |
}
|
|
|
|
| 1 |
import { useEffect, useRef } from "react";
|
| 2 |
+
import { Bot, Sparkles, User } from "lucide-react";
|
| 3 |
import type { UiMessage } from "./types";
|
| 4 |
import { MarkdownContent } from "./MarkdownContent";
|
| 5 |
import { MessageTraceability } from "./MessageTraceability";
|
| 6 |
+
import { cx, compactQuestions, formatDateTime } from "./utils";
|
|
|
|
| 7 |
|
| 8 |
export function MessageList({
|
| 9 |
messages,
|
| 10 |
scrollTo,
|
| 11 |
onScrollDone,
|
| 12 |
+
businessQuestions = [],
|
| 13 |
+
onSelectQuestion,
|
| 14 |
}: {
|
| 15 |
messages: UiMessage[];
|
| 16 |
scrollTo?: "bottom" | "last-ai" | null;
|
| 17 |
onScrollDone?: () => void;
|
| 18 |
+
businessQuestions?: string[];
|
| 19 |
+
onSelectQuestion?: (question: string) => void;
|
| 20 |
}) {
|
| 21 |
const containerRef = useRef<HTMLDivElement>(null);
|
| 22 |
const lastAiRef = useRef<HTMLElement>(null);
|
|
|
|
| 32 |
}, [scrollTo]);
|
| 33 |
|
| 34 |
if (messages.length === 0) {
|
| 35 |
+
const questions = compactQuestions(businessQuestions);
|
| 36 |
return (
|
| 37 |
+
<div className="flex h-full items-center justify-center overflow-y-auto p-6">
|
| 38 |
+
<div className="mx-auto w-full max-w-xl text-center">
|
| 39 |
+
<div className="mx-auto mb-3 flex h-10 w-10 items-center justify-center rounded-full bg-emerald-50 text-emerald-600">
|
| 40 |
+
<Sparkles className="h-5 w-5" />
|
| 41 |
+
</div>
|
| 42 |
+
<h3 className="text-sm font-semibold text-slate-900">Start the conversation</h3>
|
| 43 |
+
<p className="mt-1 text-sm text-slate-500">
|
| 44 |
+
{questions.length > 0
|
| 45 |
+
? "Pick one of the business questions below, or type your own question in the chat box."
|
| 46 |
+
: "Ask a focused question once the analysis context is ready."}
|
| 47 |
+
</p>
|
| 48 |
+
{questions.length > 0 && (
|
| 49 |
+
<div className="mt-4 flex flex-col gap-2 text-left">
|
| 50 |
+
{questions.map((question) => (
|
| 51 |
+
<button
|
| 52 |
+
key={question}
|
| 53 |
+
type="button"
|
| 54 |
+
onClick={() => onSelectQuestion?.(question)}
|
| 55 |
+
className="rounded-lg border border-slate-200 bg-white px-4 py-2.5 text-sm text-slate-700 shadow-sm transition hover:border-emerald-300 hover:bg-emerald-50 hover:text-emerald-800"
|
| 56 |
+
>
|
| 57 |
+
{question}
|
| 58 |
+
</button>
|
| 59 |
+
))}
|
| 60 |
+
</div>
|
| 61 |
+
)}
|
| 62 |
+
</div>
|
| 63 |
</div>
|
| 64 |
);
|
| 65 |
}
|
src/app/components/analysis/NewAnalysisDialog.tsx
CHANGED
|
@@ -25,14 +25,14 @@ interface NewAnalysisDialogProps {
|
|
| 25 |
export function NewAnalysisDialog({ open, onClose, onCreated }: NewAnalysisDialogProps) {
|
| 26 |
const [title, setTitle] = useState("");
|
| 27 |
const [objective, setObjective] = useState("");
|
| 28 |
-
const [questions, setQuestions] = useState<string[]>([""]);
|
| 29 |
const [dataBind, setDataBind] = useState<DataBindItem[]>([]);
|
| 30 |
const [submitting, setSubmitting] = useState(false);
|
| 31 |
const [error, setError] = useState<string | null>(null);
|
| 32 |
|
| 33 |
if (!open) return null;
|
| 34 |
|
| 35 |
-
const canSubmit = title.trim() && objective.trim() && compactQuestions(questions).length >
|
| 36 |
|
| 37 |
const submit = async (event: React.FormEvent) => {
|
| 38 |
event.preventDefault();
|
|
@@ -49,7 +49,7 @@ export function NewAnalysisDialog({ open, onClose, onCreated }: NewAnalysisDialo
|
|
| 49 |
onCreated(analysis);
|
| 50 |
setTitle("");
|
| 51 |
setObjective("");
|
| 52 |
-
setQuestions([""]);
|
| 53 |
setDataBind([]);
|
| 54 |
onClose();
|
| 55 |
} catch (err) {
|
|
|
|
| 25 |
export function NewAnalysisDialog({ open, onClose, onCreated }: NewAnalysisDialogProps) {
|
| 26 |
const [title, setTitle] = useState("");
|
| 27 |
const [objective, setObjective] = useState("");
|
| 28 |
+
const [questions, setQuestions] = useState<string[]>(["", ""]);
|
| 29 |
const [dataBind, setDataBind] = useState<DataBindItem[]>([]);
|
| 30 |
const [submitting, setSubmitting] = useState(false);
|
| 31 |
const [error, setError] = useState<string | null>(null);
|
| 32 |
|
| 33 |
if (!open) return null;
|
| 34 |
|
| 35 |
+
const canSubmit = title.trim() && objective.trim() && compactQuestions(questions).length >= 2 && dataBind.length > 0;
|
| 36 |
|
| 37 |
const submit = async (event: React.FormEvent) => {
|
| 38 |
event.preventDefault();
|
|
|
|
| 49 |
onCreated(analysis);
|
| 50 |
setTitle("");
|
| 51 |
setObjective("");
|
| 52 |
+
setQuestions(["", ""]);
|
| 53 |
setDataBind([]);
|
| 54 |
onClose();
|
| 55 |
} catch (err) {
|
src/app/components/analysis/ReportSidebar.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import { useEffect, useState } from "react";
|
| 2 |
-
import { FileText, Loader2, PanelRightClose } from "lucide-react";
|
| 3 |
import {
|
| 4 |
AgenticApiError,
|
| 5 |
generateReport,
|
|
@@ -18,9 +18,11 @@ interface ReportSidebarProps {
|
|
| 18 |
analysis: Analysis | null;
|
| 19 |
userId?: string;
|
| 20 |
onCollapse?: () => void;
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
|
| 23 |
-
export function ReportSidebar({ analysis, userId, onCollapse }: ReportSidebarProps) {
|
| 24 |
const [versions, setVersions] = useState<ReportSummary[]>([]);
|
| 25 |
const [selectedVersion, setSelectedVersion] = useState<number | undefined>();
|
| 26 |
const [detail, setDetail] = useState<ReportDetail | null>(null);
|
|
@@ -106,7 +108,18 @@ export function ReportSidebar({ analysis, userId, onCollapse }: ReportSidebarPro
|
|
| 106 |
</div>
|
| 107 |
</div>
|
| 108 |
<div className="flex flex-shrink-0 items-center gap-1.5">
|
| 109 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
<button
|
| 111 |
type="button"
|
| 112 |
title="Collapse report sidebar"
|
|
|
|
| 1 |
import { useEffect, useState } from "react";
|
| 2 |
+
import { FileText, Loader2, Maximize2, Minimize2, PanelRightClose } from "lucide-react";
|
| 3 |
import {
|
| 4 |
AgenticApiError,
|
| 5 |
generateReport,
|
|
|
|
| 18 |
analysis: Analysis | null;
|
| 19 |
userId?: string;
|
| 20 |
onCollapse?: () => void;
|
| 21 |
+
fullscreen?: boolean;
|
| 22 |
+
onToggleFullscreen?: () => void;
|
| 23 |
}
|
| 24 |
|
| 25 |
+
export function ReportSidebar({ analysis, userId, onCollapse, fullscreen = false, onToggleFullscreen }: ReportSidebarProps) {
|
| 26 |
const [versions, setVersions] = useState<ReportSummary[]>([]);
|
| 27 |
const [selectedVersion, setSelectedVersion] = useState<number | undefined>();
|
| 28 |
const [detail, setDetail] = useState<ReportDetail | null>(null);
|
|
|
|
| 108 |
</div>
|
| 109 |
</div>
|
| 110 |
<div className="flex flex-shrink-0 items-center gap-1.5">
|
| 111 |
+
{onToggleFullscreen && (
|
| 112 |
+
<button
|
| 113 |
+
type="button"
|
| 114 |
+
title={fullscreen ? "Exit fullscreen" : "Expand report to fullscreen"}
|
| 115 |
+
aria-label={fullscreen ? "Exit fullscreen" : "Expand report to fullscreen"}
|
| 116 |
+
onClick={onToggleFullscreen}
|
| 117 |
+
className="rounded-md p-2 text-slate-500 hover:bg-slate-100 hover:text-slate-900"
|
| 118 |
+
>
|
| 119 |
+
{fullscreen ? <Minimize2 className="h-4 w-4" /> : <Maximize2 className="h-4 w-4" />}
|
| 120 |
+
</button>
|
| 121 |
+
)}
|
| 122 |
+
{onCollapse && !fullscreen && (
|
| 123 |
<button
|
| 124 |
type="button"
|
| 125 |
title="Collapse report sidebar"
|