Skip to content

Exception review UI: multi-exception timeline + layout v1/v2/v3 (exploration)#878

Draft
petervachon wants to merge 5 commits into
mainfrom
TestInvoice
Draft

Exception review UI: multi-exception timeline + layout v1/v2/v3 (exploration)#878
petervachon wants to merge 5 commits into
mainfrom
TestInvoice

Conversation

@petervachon

Copy link
Copy Markdown
Collaborator

Exception review UI (exploration). Supersedes #872 (closed to re-trigger the deployment).

The invoice-review detail area is reworked around a timeline where the reviewer answers one question at a time. Selectable via the profile-menu Layout switch:

  • v1 — the original design.
  • v2 — the timeline redesign with a bordered exception index.
  • v3 — same timeline with an "Up next" strip in place of the index (for comparison).

Highlights:

  • Multi-exception stack with a single live exception and a stable index/strip of the rest; one shared record drives the queue rail, the invoices table, and the workspace so an invoice never reads differently in two places.
  • Suggested fixes propose a resolution with reasoning; the agent never resolves on its own. Invoice-level decisions (Approve/Reject/Hold) live in the header.
  • Phased resolve choreography (confirm → check → reveal) with in-place collapse, then a completion moment when all exceptions clear.
  • Resolution data propagation (e.g. Link PO-5123 updates the header PO pill + Details), chat-style scroll follow, and a header fade/blur scrim.

All agent behavior is stubbed; single-exception and cascade invoices render through the same flow.

🤖 Generated with Claude Code

petervachon and others added 5 commits July 1, 2026 11:20
Invoice review template, demo API routes, and the Slack escalation server.
Rebased onto latest main; prior prototype history (8 commits, incl. a merge)
squashed into a single commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…toggle

Add the "next" exception-review experience, toggled against "current" via a
dev switch in the profile menu (InvoiceVersionProvider + ShellProfileExtras).

The center panel reads as a timeline: collapsed agent history, the live
exception, and any checks gated behind it. Confident fixes surface in a
suggested-fix card (reasoning + apply/alternative) but never auto-resolve.
Invoice-level decisions (Approve/Reject/Hold/Flag) move to a header
split-button. The right panel drops the Activity tab and merges details.

All agent behavior is stubbed; single-exception and cascade invoices render
through the same flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…resolve

Rework the next invoice-review workspace around multiple exceptions per invoice
and one source of truth across surfaces.

- Data: exceptions[] with a canonical exception dictionary (label + tone per
  type), assignee/status on InvoiceReview, getExceptionSummary/openExceptions;
  fixtures converted (INV-84471 loop hero, INV-60118 multi-open, high-value as
  a review reason). revalidateException returns cleared/surfaced.
- Shared per-invoice runtime store (InvoiceRuntimeProvider) so the workspace,
  queue rail, and table reflect resolution live from the same record.
- Queue rail derives by assignee filter with a live +N suffix and "Ready to
  approve"; table exception cell shows lead + N (tooltip) or "Cleared".
- Center column: stable exception index (master-detail) with ordinals and a
  "Viewing" indicator; stage crossfade + staggered entrance.
- Phased resolve choreography (confirm -> check -> reveal -> commit): the
  resolved block collapses in place, nothing inserts above the live exception,
  and the next exception/surfaced items appear only after re-validation settles.
  Reduced-motion aware.
- Stage headline wraps (no ellipsis); resolved marker uses user-round-check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…scroll follow

Build out the invoice-review resolve flow and add the completion end state.

- Completion moment: at zero open exceptions the resolve log compresses into a
  peek and a terminal block appears (solid-success marker, summary built from
  resolution shortLabels, Approve invoice / Hold wired to the header handlers).
- Phased resolve choreography kept, with a cleaner Phase 1 collapse (content
  fades before the height animates) and no scope labels on the live stage.
- Resolution data propagation: a resolution dataPatch (e.g. Link PO-5123)
  updates the shared record via the runtime store; header PO pill and Details
  Purchase order reflect it.
- Chat-style scroll follow: after a resolve commits (or on selection), the
  column scrolls so the live block's top lands ~24px below the header, clamping
  to the bottom when the rest fits; user scroll cancels the follow; reduced
  motion applies instantly.
- Header scrim (fade + blur, tunable --header-scrim-h) over the center column.
- "Issue x of x" moved beside "Needs your decision" with a subtle divider.
- Split-button: one primary color with a primary-600 rule between the halves.
- Copy sweep: removed em-dashes from fixture and UI strings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rename the layout versions to v1 (was current), v2 (was next), and add v3;
migrate the stored preference. v2 keeps the bordered exception index; v3
previews an "Up next" strip in its place, selected via an exceptionListVariant
prop so the two can be compared from the Layout menu.

The strip lists only open, non-active exceptions in standing order: borderless,
one quiet line each (tone dot + headline + scope + optional New tag), no
ordinals, chips, or "Viewing". Lines pull an exception forward on click
(keyboard-focusable); it updates only at the Phase 3 commit. The index
component is kept intact behind the flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 2, 2026 20:43
const LISTENER_PORT = process.env.LISTENER_PORT || "3010";

export async function POST(request: NextRequest) {
let body = "{}";
const LISTENER_PORT = process.env.LISTENER_PORT || "3010";

export async function POST(request: NextRequest) {
let body = "{}";
Comment on lines +3 to +10
import {
createContext,
type ReactNode,
useCallback,
useContext,
useMemo,
useState,
} from "react";

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Exploration/prototype work for the apollo-vertex “Invoice Review / Exception review” UI, adding a multi-exception timeline flow (layout v2/v3) plus a demo-grade Slack roundtrip (Socket Mode listener + Next.js proxy routes) and a shell hook to inject a layout switch into the user profile menu.

Changes:

  • Added a new timeline-based exception review surface with multi-exception staging, resolving choreography, and shared per-invoice runtime state.
  • Introduced a demo-only Slack listener (isolated npm package) + demo API routes to trigger escalation, post replies, and poll shared state.
  • Added shell “profile extras” slot to inject extra menu items (used to switch v1/v2/v3 layouts), plus small supporting tweaks (i18n/lang init, colors, scripts, gitignore, lint ignores).

Reviewed changes

Copilot reviewed 29 out of 31 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
package.json Adds repo-level demo scripts to start the prototype + Slack listener together.
apps/apollo-vertex/templates/invoice-review/README.md Documents prototype status and migration expectations for the invoice-review template + demo routes.
apps/apollo-vertex/templates/invoice-review/next/SuggestedFixCard.tsx Adds a “Suggested fix” glass card UI for AI fix suggestions and resolved confirmation state.
apps/apollo-vertex/templates/invoice-review/next/invoice-runtime.tsx Adds a shared in-memory per-invoice runtime store (resolved IDs, surfaced exceptions, data patches).
apps/apollo-vertex/templates/invoice-review/next/invoice-review-data.ts Adds fixtures + types + stubbed revalidation logic for invoice review and multi-exception behavior.
apps/apollo-vertex/templates/invoice-review/next/HeaderDecision.tsx Adds a header split-button decision control (Approve + overflow actions).
apps/apollo-vertex/templates/invoice-review/next/ExceptionTimeline.tsx Implements the timeline UI, multi-exception index/strip variants, resolve choreography, and scroll-follow behavior.
apps/apollo-vertex/templates/invoice-review/invoice-version.tsx Adds v1/v2/v3 layout version selection persisted to localStorage and exposed via profile menu.
apps/apollo-vertex/slack/store.js Adds JSON-file backed shared state store for the Slack demo (atomic writes, message ingestion).
apps/apollo-vertex/slack/server.js Adds Socket Mode Slack listener (posts escalation card, handles actions, ingests replies, exposes local HTTP endpoints).
apps/apollo-vertex/slack/reset-demo.js Adds reset script to delete bot messages and reset the shared demo store.
apps/apollo-vertex/slack/README.md Documents running/configuring the standalone Slack listener demo.
apps/apollo-vertex/slack/package.json Defines the isolated Slack listener npm package and its dependencies.
apps/apollo-vertex/slack/escalation-card.js Adds the Block Kit escalation card builder used by the Slack listener.
apps/apollo-vertex/registry/shell/shell-user-profile-menu-items.tsx Renders injected “profile extras” items in the shell user profile menu.
apps/apollo-vertex/registry/shell/shell-profile-extras.tsx Adds a shell provider + hook for optional profile-menu item injection.
apps/apollo-vertex/registry/button/button.tsx Tweaks button base styles (adds cursor-pointer).
apps/apollo-vertex/registry.json Adds “insight-*” color tokens used by the timeline UI.
apps/apollo-vertex/package.json Adds app-level demo scripts to start UI + Slack listener together.
apps/apollo-vertex/locales/en.json Adds missing “invoices” translation key.
apps/apollo-vertex/lib/i18n.ts Prevents re-initializing i18n, while ensuring <html lang> stays in sync.
apps/apollo-vertex/app/invoice-review/page.tsx Adds a dedicated route to render the invoice review template fullscreen.
apps/apollo-vertex/app/api/demo-trigger/route.ts Adds server-side proxy route to trigger Slack escalation via the local listener.
apps/apollo-vertex/app/api/demo-state/route.ts Adds server route to read the shared JSON demo state for UI polling.
apps/apollo-vertex/app/api/demo-reply/route.ts Adds server-side proxy route to post replies into the Slack thread via the local listener.
apps/apollo-vertex/app/_meta.ts Hides the invoice-review route from navigation.
apps/apollo-vertex/.oxlintrc.json Excludes demo/prototype directories and demo API routes from oxlint.
apps/apollo-vertex/.gitignore Ignores Slack demo runtime state and isolated Slack listener install artifacts.
apps/apollo-vertex/.env.example Adds example env vars for the Slack demo (tokens/channel/port).

Comment on lines +17 to +20
* Per-invoice disposition control for the page header: a primary Approve
* split-button with an attached overflow (Reject / Hold / Flag). Approve is
* locked until the timeline reports the invoice is clear; the overflow stays
* enabled so a reviewer can always Reject/Hold/Flag.
Comment on lines +28 to +29
// Accepted for API compatibility; Approve is no longer gated on it.
canApprove: boolean;
Comment on lines +3 to +10
import {
createContext,
type ReactNode,
useCallback,
useContext,
useMemo,
useState,
} from "react";
Comment on lines +1 to +2
"use client";
import { InvoiceReviewTemplate } from "@/templates/invoice-review/InvoiceReviewTemplate";
Comment thread package.json
Comment on lines +23 to +24
"demo": "apps/apollo-vertex/slack/node_modules/.bin/concurrently --names APP,SLACK --prefix-colors cyan,magenta \"pnpm --filter apollo-vertex dev\" \"cd apps/apollo-vertex/slack && npm start\"",
"demo:reset": "cd apps/apollo-vertex/slack && npm run reset-demo",
Comment on lines +10 to +16
export async function POST(request: NextRequest) {
let body = "{}";
try {
body = JSON.stringify(await request.json());
} catch {
body = "{}"; // no body is fine — listener falls back to demo defaults
}
Comment on lines +10 to +16
export async function POST(request: NextRequest) {
let body = "{}";
try {
body = JSON.stringify(await request.json());
} catch {
body = "{}";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants