diff --git a/BUILD_REPORT.md b/BUILD_REPORT.md
index 6896c60..ea5f49d 100644
--- a/BUILD_REPORT.md
+++ b/BUILD_REPORT.md
@@ -2,89 +2,87 @@
## sprint objective
-Implement Sprint 6F by extending the AliceBot web shell so approved approvals can be executed from `/approvals` and their resulting execution state can be reviewed from `/approvals` and `/tasks` using only the shipped approval-execution and tool-execution read endpoints.
+Implement Sprint 6G by turning `/chat` into a dual-mode operator conversation surface with:
+
+- assistant response mode backed by `POST /v0/responses`
+- governed request mode retained through `POST /v0/approvals/requests`
+
+The sprint stays inside the shipped backend seams and keeps the two behaviors visibly separate.
## completed work
-- extended `apps/web/lib/api.ts` with typed execution support for:
- - `POST /v0/approvals/{approval_id}/execute`
- - `GET /v0/tool-executions`
- - `GET /v0/tool-executions/{execution_id}`
-- added fixture-backed execution records in `apps/web/lib/fixtures.ts` so fixture mode now covers:
- - approved but not executed
- - executed task and execution review
-- updated `apps/web/app/approvals/page.tsx` to:
- - discover linked execution records for the selected approval
- - surface explicit live unavailable state when execution review cannot be loaded
- - keep fixture fallback explicit when live API configuration is absent
-- updated `apps/web/app/tasks/page.tsx` to:
- - read latest execution detail from `task.latest_execution_id`
- - fall back to fixture execution detail only when a matching fixture exists
- - surface explicit unavailable messaging when a live execution read fails without fixture coverage
-- extended `apps/web/components/approval-actions.tsx` to:
- - keep approve/reject for pending approvals
- - show execute for eligible approved approvals
- - show bounded loading, success, failure, and read-only states
-- extended `apps/web/components/approval-detail.tsx` and `apps/web/components/task-summary.tsx` with the new bounded `apps/web/components/execution-summary.tsx`
-- updated `apps/web/components/task-step-list.tsx` to make execution linkage and blocked reasons clearer inside the existing step timeline
-- refined `apps/web/app/globals.css` for the scoped surfaces with stronger containment, calmer grouping, better wrapping behavior, and more stable responsive stacking
-- added or updated narrow frontend coverage in:
+- updated `apps/web/app/chat/page.tsx` to:
+ - make assistant mode the default `/chat` state
+ - add an explicit mode toggle between assistant chat and governed request submission
+ - seed fixture history only when live API configuration is absent
+ - keep the side rail mode-specific so supporting guidance stays relevant instead of noisy
+- added `apps/web/components/mode-toggle.tsx` as a stable two-state switch with clear labeling and active-state emphasis
+- added `apps/web/components/response-composer.tsx` to:
+ - submit normal assistant questions through `POST /v0/responses`
+ - keep thread identity explicit
+ - provide explicit fixture preview fallback when live API configuration is absent
+- added `apps/web/components/response-history.tsx` to show bounded assistant history with:
+ - operator prompt
+ - assistant reply
+ - model metadata
+ - compile and response trace summaries
+ - direct links into `/traces`
+- refined `apps/web/components/request-composer.tsx` so governed mode reads as an intentional approval-gated workflow instead of a chat-like surface
+- extended `apps/web/lib/api.ts` with typed assistant-response submission support for `POST /v0/responses`
+- extended `apps/web/lib/fixtures.ts` with assistant response fixtures, fixture trace coverage, and deterministic preview entries
+- refined `apps/web/app/globals.css` for the scoped `/chat` surface with:
+ - stronger hierarchy
+ - calmer spacing
+ - bounded history panels
+ - more deliberate prompt/reply grouping
+ - safer wrapping for long ids, trace references, and body text
+ - cleaner mobile stacking for the mode switch and chat workspace
+- added narrow frontend coverage in:
- `apps/web/lib/api.test.ts`
- - `apps/web/components/approval-actions.test.tsx`
- - `apps/web/components/execution-summary.test.tsx`
+ - `apps/web/app/chat/page.test.tsx`
+ - `apps/web/components/response-composer.test.tsx`
+ - `apps/web/components/response-history.test.tsx`
## incomplete work
- no scoped sprint deliverables remain incomplete in code
- intentionally not added:
- backend changes
- - new routes
- - execution mutation beyond the shipped approval execute seam
- - execution filtering, search, or pagination
- - broader task workflow redesign outside `/approvals` and `/tasks`
+ - thread browsing or thread creation UI
+ - auth changes
+ - new routes outside `/chat`
+ - hidden tool routing or autonomous action behavior
-## files changed
+## exact /chat files and components updated
-- `apps/web/app/approvals/page.tsx`
-- `apps/web/app/tasks/page.tsx`
+- `apps/web/app/chat/page.tsx`
+- `apps/web/app/chat/page.test.tsx`
- `apps/web/app/globals.css`
-- `apps/web/components/approval-actions.tsx`
-- `apps/web/components/approval-detail.tsx`
-- `apps/web/components/task-summary.tsx`
-- `apps/web/components/task-step-list.tsx`
+- `apps/web/components/request-composer.tsx`
+- `apps/web/components/response-composer.tsx`
+- `apps/web/components/response-history.tsx`
+- `apps/web/components/mode-toggle.tsx`
- `apps/web/components/status-badge.tsx`
-- `apps/web/components/execution-summary.tsx`
- `apps/web/lib/api.ts`
- `apps/web/lib/fixtures.ts`
- `apps/web/lib/api.test.ts`
-- `apps/web/components/approval-actions.test.tsx`
-- `apps/web/components/execution-summary.test.tsx`
+- `apps/web/components/response-composer.test.tsx`
+- `apps/web/components/response-history.test.tsx`
- `BUILD_REPORT.md`
## route backing mode
-- `/approvals` is:
- - live-API-backed for approval list/detail and linked execution review when API configuration is present
+- assistant mode in `/chat` is:
+ - live-API-backed when API configuration is present
- fixture-backed when API configuration is absent
- - explicitly unavailable for linked execution review when live execution reads fail
-- `/tasks` is:
- - live-API-backed for task detail, step detail, and latest execution review when API configuration is present
+- governed request mode in `/chat` is:
+ - live-API-backed when API configuration is present
- fixture-backed when API configuration is absent
- - mixed only when a live task falls back to fixture execution detail
## backend endpoints consumed
-- `POST /v0/approvals/{approval_id}/execute`
-- `GET /v0/tool-executions`
-- `GET /v0/tool-executions/{execution_id}`
-- existing carried-forward reads already used by the shell:
- - `GET /v0/approvals`
- - `GET /v0/approvals/{approval_id}`
- - `POST /v0/approvals/{approval_id}/approve`
- - `POST /v0/approvals/{approval_id}/reject`
- - `GET /v0/tasks`
- - `GET /v0/tasks/{task_id}`
- - `GET /v0/tasks/{task_id}/steps`
+- `POST /v0/responses`
+- `POST /v0/approvals/requests`
## exact commands run
@@ -96,19 +94,22 @@ Implement Sprint 6F by extending the AliceBot web shell so approved approvals ca
- lint result: PASS
- test result: PASS
- - `4` test files passed
- - `20` tests passed
+ - `7` test files passed
+ - `28` tests passed
- build result: PASS
## desktop and mobile visual verification notes
- no browser-driven visual QA pass was executed in this turn
- desktop note:
- - code inspection indicates `/approvals` and `/tasks` now use stronger internal grouping for action handling and execution review
- - ids, badges, and payload snapshots have explicit wrapping and overflow handling inside bounded cards
+ - assistant mode now presents the composer and bounded response history as two coordinated panels instead of one long undifferentiated form
+ - the mode switch is visible near the page header and reads as a stable route-level decision rather than an inline afterthought
+ - response prompt, reply, ids, and trace summaries all use explicit containment styles with overflow wrapping
+ - live-configured `/chat` now starts empty in both modes instead of showing synthetic fixture history
- mobile note:
- - the shared shell still collapses the split layouts to one column below the existing breakpoint
- - execution review, action bars, and buttons now stack into full-width rows to preserve containment on narrow screens
+ - the mode switch collapses to one column below the existing breakpoint
+ - the assistant workspace collapses from a two-panel layout to one column so the composer remains primary and the history panel follows cleanly
+ - buttons continue to expand to full width on narrow screens to avoid cramped action rows
## blockers/issues
@@ -117,14 +118,15 @@ Implement Sprint 6F by extending the AliceBot web shell so approved approvals ca
## recommended next step
-Run a browser-based QA pass against a live configured backend to validate:
-- the execute transition from approved to executed or blocked
-- the exact empty/unavailable messaging in live failure cases
-- the density of output snapshots on long real-world payloads
+Run a browser-based QA pass against both assistant mode and governed mode to validate:
+
+- real long-form assistant replies in the bounded history panel
+- mode-switch readability and perceived hierarchy on tablet widths
+- trace-link destinations against a live configured backend
## intentionally deferred after this sprint
+- thread browsing, thread create flows, or any broader conversation management UI
+- backend changes beyond the shipped `/v0/responses` and `/v0/approvals/requests` seams
- any Gmail, Calendar, auth, runner, or broader workflow expansion
-- any execution list filters, sorting controls, or search UI
-- any task-step mutation UI beyond existing backend reads
-- any redesign outside the scoped `/approvals` and `/tasks` review surfaces
+- redesign of unrelated routes outside the scoped `/chat` surface
diff --git a/REVIEW_REPORT.md b/REVIEW_REPORT.md
index 04d781d..0d67e0a 100644
--- a/REVIEW_REPORT.md
+++ b/REVIEW_REPORT.md
@@ -6,20 +6,20 @@ PASS
## criteria met
-- The sprint stayed a UI sprint and did not widen backend scope. The implementation remains confined to the web shell and uses only the shipped approval/task/execution seams.
-- The UI can trigger `POST /v0/approvals/{approval_id}/execute` for eligible approved approvals through `apps/web/lib/api.ts` and `apps/web/components/approval-actions.tsx`.
-- The UI can show resulting execution state using existing execution and task reads in `apps/web/app/approvals/page.tsx`, `apps/web/app/tasks/page.tsx`, `apps/web/components/approval-detail.tsx`, `apps/web/components/task-summary.tsx`, `apps/web/components/task-step-list.tsx`, and `apps/web/components/execution-summary.tsx`.
-- `/approvals` and `/tasks` make execution state understandable without widening backend scope. Loading, success, blocked/failure, empty, and unavailable states are all explicitly surfaced.
-- When API configuration is absent, execution controls degrade to explicit fixture/read-only behavior rather than broken interaction.
-- The sprint stayed within the listed in-scope screens, components, and files.
-- `DESIGN_SYSTEM.md` was followed materially. The execution controls and review surfaces remain bounded and consistent with the existing operator-shell tone.
-- `BUILD_REPORT.md` is aligned with the implemented sprint scope and now reflects the current verification totals.
+- The sprint stayed a UI sprint and did not widen backend scope. The implementation remains confined to the web shell and uses only the shipped seams `POST /v0/responses` and `POST /v0/approvals/requests`.
+- `/chat` supports assistant response mode via `POST /v0/responses` through `apps/web/lib/api.ts` and `apps/web/components/response-composer.tsx`.
+- `/chat` retains governed request mode via the existing approval-request seam through `apps/web/components/request-composer.tsx`.
+- The mode switch is explicit and understandable. `apps/web/components/mode-toggle.tsx` keeps assistant and governed modes visibly separate.
+- Assistant replies and trace summaries are visible in bounded history panels via `apps/web/components/response-history.tsx`.
+- Fixture fallback is now explicit and correctly scoped to the no-config path. Live-configured `/chat` starts empty in both modes instead of showing seeded synthetic history. This is enforced in `apps/web/app/chat/page.tsx` and covered by `apps/web/app/chat/page.test.tsx`.
+- The sprint stayed within the exact in-scope files and components listed in the sprint packet.
+- The UI continues to follow `DESIGN_SYSTEM.md` materially. The `/chat` surface remains restrained, bounded, and readable on the inspected responsive layouts.
+- `BUILD_REPORT.md` now matches the implemented route-backing behavior and current verification totals.
- Verification passed in `apps/web`:
- `npm run lint`
- `npm test`
- `npm run build`
- - current totals: `4` test files, `20` tests
-- `next build` did not leave tracked churn in `apps/web/tsconfig.json` or `apps/web/next-env.d.ts`.
+ - current totals: `7` test files, `28` tests
## criteria missed
@@ -27,15 +27,15 @@ PASS
## quality issues
-- No blocking quality issues found in the current Sprint 6F implementation.
+- No blocking quality issues found in the current Sprint 6G implementation.
## regression risks
-- Residual risk is limited to live-data wording and density because the visual notes are still based on code inspection rather than a browser QA pass against a configured backend. That does not block sprint acceptance.
+- Residual risk is limited to browser-level presentation because no live browser QA pass was executed in this review cycle. That does not block sprint acceptance.
## docs issues
-- No blocking docs issues remain for Sprint 6F.
+- No blocking docs issues remain for Sprint 6G.
## should anything be added to RULES.md?
@@ -47,5 +47,5 @@ PASS
## recommended next action
-- Sprint 6F can be considered review-passed.
-- Next follow-up should be a browser-based QA pass against a live configured backend to validate the approved-to-executed or blocked transition and the exact operator-facing wording in live failure cases.
+- Sprint 6G can be considered review-passed.
+- Next follow-up should be a browser-based QA pass against a live configured backend to validate long-form assistant replies, mode-switch hierarchy on tablet widths, and trace-link destinations.
diff --git a/apps/web/app/chat/page.test.tsx b/apps/web/app/chat/page.test.tsx
new file mode 100644
index 0000000..2fcf424
--- /dev/null
+++ b/apps/web/app/chat/page.test.tsx
@@ -0,0 +1,88 @@
+import React from "react";
+import { cleanup, render, screen } from "@testing-library/react";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+import ChatPage from "./page";
+
+const { getApiConfigMock, hasLiveApiConfigMock } = vi.hoisted(() => ({
+ getApiConfigMock: vi.fn(),
+ hasLiveApiConfigMock: vi.fn(),
+}));
+
+vi.mock("next/link", () => ({
+ default: ({
+ href,
+ children,
+ className,
+ "aria-current": ariaCurrent,
+ }: {
+ href: string;
+ children: React.ReactNode;
+ className?: string;
+ "aria-current"?: string;
+ }) => (
+
+ {children}
+
+ ),
+}));
+
+vi.mock("../../lib/api", async () => {
+ const actual = await vi.importActual("../../lib/api");
+ return {
+ ...actual,
+ getApiConfig: getApiConfigMock,
+ hasLiveApiConfig: hasLiveApiConfigMock,
+ };
+});
+
+describe("ChatPage", () => {
+ beforeEach(() => {
+ getApiConfigMock.mockReset();
+ hasLiveApiConfigMock.mockReset();
+ });
+
+ afterEach(() => {
+ cleanup();
+ });
+
+ it("does not seed fixture assistant history when live API configuration is present", async () => {
+ getApiConfigMock.mockReturnValue({
+ apiBaseUrl: "https://api.example.com",
+ userId: "user-1",
+ defaultThreadId: "thread-1",
+ defaultToolId: "tool-1",
+ });
+ hasLiveApiConfigMock.mockReturnValue(true);
+
+ render(await ChatPage({ searchParams: Promise.resolve({}) }));
+
+ expect(screen.getByText("Live submission enabled")).toBeInTheDocument();
+ expect(screen.getByText("No assistant replies yet")).toBeInTheDocument();
+ expect(screen.queryByText("Fixture response preview")).not.toBeInTheDocument();
+ expect(screen.queryByText(/What do I need to know about the last Vitamin D request/i)).not.toBeInTheDocument();
+ });
+
+ it("does not seed fixture governed-request history when live API configuration is present", async () => {
+ getApiConfigMock.mockReturnValue({
+ apiBaseUrl: "https://api.example.com",
+ userId: "user-1",
+ defaultThreadId: "thread-1",
+ defaultToolId: "tool-1",
+ });
+ hasLiveApiConfigMock.mockReturnValue(true);
+
+ render(
+ await ChatPage({
+ searchParams: Promise.resolve({
+ mode: "request",
+ }),
+ }),
+ );
+
+ expect(screen.getByText("Live submission enabled")).toBeInTheDocument();
+ expect(screen.getByText("No governed requests yet")).toBeInTheDocument();
+ expect(screen.queryByText("Fixture preview")).not.toBeInTheDocument();
+ expect(screen.queryByText(/place_order \/ supplements/i)).not.toBeInTheDocument();
+ });
+});
diff --git a/apps/web/app/chat/page.tsx b/apps/web/app/chat/page.tsx
index b81b950..0f59d38 100644
--- a/apps/web/app/chat/page.tsx
+++ b/apps/web/app/chat/page.tsx
@@ -1,67 +1,139 @@
+import { ModeToggle, type ChatMode } from "../../components/mode-toggle";
import { PageHeader } from "../../components/page-header";
import { RequestComposer } from "../../components/request-composer";
+import { ResponseComposer } from "../../components/response-composer";
import { SectionCard } from "../../components/section-card";
import { getApiConfig, hasLiveApiConfig } from "../../lib/api";
-import { requestHistoryFixtures } from "../../lib/fixtures";
+import { requestHistoryFixtures, responseHistoryFixtures } from "../../lib/fixtures";
-export default function ChatPage() {
+type ChatPageProps = {
+ searchParams?: Promise>;
+};
+
+function normalizeMode(value: string | string[] | undefined): ChatMode {
+ if (Array.isArray(value)) {
+ return normalizeMode(value[0]);
+ }
+
+ return value === "request" ? "request" : "assistant";
+}
+
+export default async function ChatPage({ searchParams }: ChatPageProps) {
+ const resolvedSearchParams = searchParams ? await searchParams : undefined;
+ const mode = normalizeMode(resolvedSearchParams?.mode);
const apiConfig = getApiConfig();
const liveModeReady = hasLiveApiConfig(apiConfig);
+ const initialResponseEntries = liveModeReady ? [] : responseHistoryFixtures;
+ const initialRequestEntries = liveModeReady ? [] : requestHistoryFixtures;
return (
{liveModeReady ? "Live submission enabled" : "Fixture preview mode"}
- Approval-request seam only
+ Responses and approvals stay explicit
}
/>
+
+
-
+ {mode === "assistant" ? (
+
+ ) : (
+
+ )}
-
-
-
Requests are submitted directly to `POST /v0/approvals/requests` using shipped payload fields only.
-
The operator supplies thread and tool identifiers explicitly instead of relying on hidden web-side routing.
-
Every resulting summary keeps decision, approval linkage, task status, and trace references visible.
-
-
+ {mode === "assistant" ? (
+ <>
+
+
+
Questions are submitted directly to `POST /v0/responses` with only the shipped user, thread, and message fields.
+
The operator still provides thread identity explicitly instead of relying on hidden routing or auto-selected context.
+
Each reply keeps compile and response trace summaries attached so explainability remains one click away.
+
+
+
+
+
+
+
Assistant mode
+
Answer questions, summarize state, and explain prior work without submitting an approval request.
+
+
+
Governed mode
+
Submit action-oriented payloads that can create approval and task records through the shipped request seam.
+
+
+
Fallback
+
Fixture previews stay explicit when live API configuration is absent instead of failing silently.
+
+
+
Trace review
+
Compile and response trace IDs remain linked from each reply so the operator can inspect why the answer was produced.
+
+
+
+ >
+ ) : (
+ <>
+
+
+
Requests are submitted directly to `POST /v0/approvals/requests` using shipped payload fields only.
+
The operator supplies thread and tool identifiers explicitly instead of relying on hidden web-side routing.
+
Every resulting summary keeps decision, approval linkage, task status, and trace references visible.
{liveModeReady ? "Live operator mode" : "Fixture operator mode"}
-
- Requests stay explicitly governed and recent routing plus request traces remain attached to each submission.
-
+ Requests stay explicitly governed and the resulting approval, task, and trace links remain attached.
@@ -191,10 +189,11 @@ export function RequestComposer({
-
-
+
+
Governed request
+
Approval-gated action submission
- Submit the shipped approval-request payload directly. This surface is request-oriented, not a freeform chat transcript.
+ Submit the shipped approval-request payload directly. This mode is purpose-built for consequential actions, not freeform conversation.
@@ -293,6 +292,7 @@ export function RequestComposer({
+
Recent activity
Recent governed request summaries
Latest submissions stay grouped with decision, approval linkage, task state, and traces.