Invoice Process: Execption UI v3#908
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Dependency License Review
License distribution
Excluded packages
|
| message.external_id && | ||
| inv.messages.some((m) => m.external_id === message.external_id) | ||
| ) { | ||
| state.invoices[invoiceId] = inv; |
| return false; | ||
| } | ||
| inv.messages.push(message); | ||
| state.invoices[invoiceId] = inv; |
|
|
||
| function resetInvoice(invoiceId) { | ||
| const state = readState(); | ||
| state.invoices[invoiceId] = blankInvoice(); |
| 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 = "{}"; |
| } = require("./escalation-card"); | ||
| const store = require("./store"); | ||
|
|
||
| const log = (...args) => console.log("[SLACK]", ...args); |
| 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 = "{}"; |
📊 Coverage + size by packagePer-package bundle size on this PR (no JS/TS source changes detected under
"Coverage" is each package's own |
There was a problem hiding this comment.
Pull request overview
Adds an “Invoice Review” prototype flow to Apollo Vertex (v3 exception/timeline UI) and a bidirectional Slack demo integration, including local demo API routes and supporting runtime/state utilities.
Changes:
- Introduces a new invoice-review prototype template with a runtime store, timeline UI, decision dialogs, and fixture data.
- Adds a standalone Slack Socket Mode listener (isolated npm package) plus reset tooling, and Next.js API proxies to bridge the prototype ↔ listener.
- Extends the Vertex shell profile menu with an “extras” slot and adds supporting demo/dev scripts, tokens, and locale strings.
Reviewed changes
Copilot reviewed 32 out of 34 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| package.json | Adds root-level demo scripts to run the Vertex app + Slack listener together. |
| apps/apollo-vertex/templates/invoice-review/README.md | Documents prototype status and lint-exclusion/migration guidance. |
| apps/apollo-vertex/templates/invoice-review/next/SupplierEmailModal.tsx | Supplier email draft/send modal used in the routing flow. |
| apps/apollo-vertex/templates/invoice-review/next/SuggestedFixCard.tsx | Renders AI suggested fix card and route actions (incl. reason dialog). |
| apps/apollo-vertex/templates/invoice-review/next/ReasonDialog.tsx | Shared confirm dialog with reason chips + optional note. |
| apps/apollo-vertex/templates/invoice-review/next/invoice-runtime.tsx | Adds per-invoice runtime store (events, waiting, disposition, highlights). |
| apps/apollo-vertex/templates/invoice-review/next/invoice-review-data.ts | Adds canonical types, fixtures, and seam/stub functions for the prototype. |
| apps/apollo-vertex/templates/invoice-review/next/HeaderDecision.tsx | Header disposition control (approve + hold/reject overflow using ReasonDialog). |
| apps/apollo-vertex/templates/invoice-review/next/ExceptionTimeline.tsx | Large timeline UI implementation with resolve choreography + waiting/terminal states. |
| apps/apollo-vertex/templates/invoice-review/invoice-version.tsx | Dev-only layout version switch (v1/v2/v3) persisted in localStorage. |
| apps/apollo-vertex/slack/store.js | JSON-file store for demo invoice state written by Slack listener. |
| apps/apollo-vertex/slack/server.js | Slack Bolt Socket Mode listener + local HTTP trigger/proxy endpoints. |
| apps/apollo-vertex/slack/reset-demo.js | Script to delete bot messages and reset demo store between rehearsals. |
| apps/apollo-vertex/slack/README.md | Setup/run docs for the Slack listener and demo flow. |
| apps/apollo-vertex/slack/package.json | Defines isolated npm package for Slack listener dependencies/scripts. |
| apps/apollo-vertex/slack/escalation-card.js | Block Kit escalation card builder posted into Slack. |
| apps/apollo-vertex/scripts/check-type-drift.mjs | Adds a font-size “type drift” guard for the new timeline UI. |
| apps/apollo-vertex/registry/shell/shell-user-profile-menu-items.tsx | Renders injected profile-menu extras via new hook/slot. |
| apps/apollo-vertex/registry/shell/shell-profile-extras.tsx | Adds provider/hook to inject extra items into the shell profile menu. |
| apps/apollo-vertex/registry/button/button.tsx | Updates button base classes (adds cursor-pointer). |
| apps/apollo-vertex/registry.json | Adds “insight-*” color tokens used by the new UI. |
| apps/apollo-vertex/package.json | Adds app-level demo scripts and extends lint with the type-drift check. |
| apps/apollo-vertex/locales/en.json | Adds new strings (“invoices”, “my_work”). |
| apps/apollo-vertex/lib/i18n.ts | Avoids re-initializing i18n; updates <html lang> when already initialized. |
| apps/apollo-vertex/app/invoice-review/page.tsx | Adds a route that renders the invoice review template full-screen. |
| apps/apollo-vertex/app/api/demo-trigger/route.ts | Proxies “trigger escalation” requests to the local Slack listener. |
| apps/apollo-vertex/app/api/demo-state/route.ts | Serves the Slack listener’s shared demo-state JSON to the prototype. |
| apps/apollo-vertex/app/api/demo-reply/route.ts | Proxies comms replies to Slack listener to post into Slack thread. |
| apps/apollo-vertex/app/_meta.ts | Hides the invoice-review route from navigation/meta. |
| apps/apollo-vertex/.oxlintrc.json | Excludes demo/prototype directories from oxlint. |
| apps/apollo-vertex/.gitignore | Ignores demo runtime state + isolated Slack listener artifacts. |
| apps/apollo-vertex/.env.example | Adds a template for Slack demo environment variables. |
| res.setHeader("Access-Control-Allow-Origin", "*"); | ||
| res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); | ||
| res.setHeader("Access-Control-Allow-Headers", "Content-Type"); |
| "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", |
| "demo": "slack/node_modules/.bin/concurrently --names APP,SLACK --prefix-colors cyan,magenta \"pnpm dev\" \"cd slack && npm start\"", | ||
| "demo:reset": "cd slack && npm run reset-demo", |
| "@slack/bolt": "^4.4.0" | ||
| }, | ||
| "devDependencies": { | ||
| "concurrently": "^9.1.0" |
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>
… polish Make every resolved timeline row expand to its original exception (lossless history) and unify the whole exception column onto one shared row anatomy. - Expandable resolved rows: click unfolds a read-only, past-tense render of the original exception (chip, muted headline, finding, quiet fix shell with a resolution stamp) inline and inside the completion peek. Per-row local state, survives resolve commits, respects prefers-reduced-motion. - One row anatomy (EventRowBody) for flat, expandable, peek, agent-step, and ResolvingBlock rows: title 14/500 primary, sub secondary, right-aligned tabular-nums time column, fixed chevron slot, center-aligned. - Rhythm tokens: 12px nested peek / 20px events / 28px section transitions; middot separator grammar replaces the pipe in the group header. - Scroll-to-latest pill: reveals past 160px from the live edge, re-anchors and re-engages auto-follow on click, dot flags events committed while scrolled away. Auto-follow rules, choreography, and header scrim unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Routing an exception no longer marks it resolved. It parks the exception in a
waiting state, blocks the disposition, and stubs the corrected-invoice return.
- Data/runtime: `waiting` status + WaitingRef, park/unpark in the runtime store,
openExceptions/getExceptionSummary exclude waiting and report waitingCount.
Seams: sendSupplierEmail (outbound), receiveCorrectedInvoice (return).
- Timeline: route choreography (collapse to a waiting row, no re-check), info
waiting/waiting-complete markers, counter "+ N waiting", and a waiting
terminal that blocks Approve. Dev `?dev=1` simulates the corrected invoice to
close the loop.
- Supplier email: clicking "Ask supplier" opens a draft modal (ported from the
v1 EmailPanelTab into a prop-driven next/ component) as the confirmation step;
Send commits the park with the drafted email. Internal routes park directly.
The sent email is the audit artifact: recipient in the waiting sub, a
read-only subject/body block in the expanded row and the terminal request
card, and a Follow up flow that appends a "Followed up" event.
- Header/queue: Approve disabled with a "Waiting on {who}" tooltip while
waiting; a derived "Waiting" queue group with an info chip.
- Waiting terminal elevation: per-exception request card (what was sent, the
artifact, the response-time clock) + Follow up / Hold invoice actions.
- Polish: single-size-per-line right-aligned tabular timestamps, three-tier
rhythm, scroll-to-latest pill, lossless expandable rows, mid-sentence
lowercased "just now", zero-count clauses dropped from the history label,
bold active-step titles (focus + terminals), and a type-drift guard
(scripts/check-type-drift.mjs) wired into lint.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… event log Approve and Hold become real dispositions, and the whole timeline event log moves into the runtime store so history and disposition survive remounts (freeze/restore leans on that). - Disposition: runtime is the single source in v2/v3 (approved | held); template completionMap/parkedMap are not consulted for approve/hold. Approve is a hard commit with a stamped terminal; Hold is a reversible overlay with a reason popover and Resume; both flow through seams (approveInvoice/holdInvoice/ resumeInvoice). Header chip + Approve gating derive from disposition; overflow Hold removed (terminal buttons are the only Hold entry). Queue gains "On hold" and "Approved" groups. - Event log: RunEvent relocated to the data layer; the log lives in the runtime store, appended atomically inside each mutation (commitResolve / parkException / appendEvents / setDisposition) with batch-safe keys, so it never half-persists or double-fires. Summary and history label derive from it. - Fixes: Auto-approved (non-review) cards are non-interactive by data presence, and all three lookups (timeline / header-details / pager) show honest absence instead of substituting another invoice. Restore the "corrected invoice cleared" summary clause. Stop lowercasing exception labels in the default shortLabel so "PO"/"VAT" survive; give outside-po-period a resolution. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consolidate the invoice-review commit gates onto one shared ReasonDialog (reason chips + optional note) and restructure the queue rail as a status filter. - ReasonDialog: shared Dialog-primitive component (title/description/chips/ note/footer), controlled or trigger-driven; used by Hold, Route, Reject. - Route-to-owner: confirm dialog with named owners (retires bare role strings), reason/note carried on the waiting event and shown in history. - Header overflow: Reject + Hold open dialogs (Hold uses the runtime disposition, Reject destructive); Flag removed until it can park a resumable state. - Disposition events carry an explicit actor so reviewer actions use the person marker, not the AI sparkle; held summary regains its sentence break. - Held terminal hoisted so a header Hold is resumable mid-review. - Queue rail: status-filter tabs (All/Waiting/On hold/Done) sorted by due date under date section labels; flat neutral active tab on a muted track. - SupplierEmailModal title/description align to the Dialog primitives. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring Reject to full parity with Approve/Hold, and add a "show in source" inspect affordance on exception evidence. Reject to disposition parity: - InvoiceDisposition gains "rejected" (permanent, no undo); buildReject + rejectInvoice seam; receiveCorrectedInvoice no-ops for rejected. - Header Reject commits via the runtime disposition (atomic event, no toast), destructive confirm; a permanent disposition disables Approve + overflow. - Rejected terminal (solid destructive marker, no actions, finality copy) supersedes any live/waiting/held/passed state; history stays expandable. - Queue folds rejected into the Done tab; the list table now reflects runtime approve/reject/hold so the Approved badge shows the success token live. Highlight-in-source: - InvoiceException.sourceAnchors seam (anchor id = doc-region contract) + runtime showInSource(nonce) channel; anchors on VAT, price/total, invoice date fixtures. - Highlighter trigger rides the ON INVOICE evidence value (not the action row); click switches to the Source tab, smooth-scrolls the first region to the upper third, and settles an amber fill + ring (same warning token as the chip). Live-exception only; clears on resolve/route/terminal. - Reduced motion: instant scroll, no settle, no elevated frame (synchronous matchMedia + motion-reduce backstop). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…il polish Left panel: - Rename the queue heading to "My Work". - Add a "My Work" nav item that opens the first Due-today invoice's detail; "Invoices" returns to the list. Each invoice now has a shareable, bookmarkable URL (?invoice=INV-XXXX) that updates as you navigate and deep-links on load. Exception timeline: - Approved terminal now mirrors the rejected terminal: solid green check + "Approved" headline + amount/vendor/time stamp + "Sent for payment.". "All checks passed" demotes to a history row (sparkle marker) with the resolution summary; the stale "ready for your decision" line is gone. - Approve plays the full re-check choreography (confirm -> check -> reveal) before revealing the terminal; hold/reject stay snappy. Reduced motion reveals instantly. - Removed the static "Validation runs again after each fix" explainer row. - Rail line signals state with no words: a short fading tail on open states (live/waiting/held/decision-pending), a hard stop on terminals (approved/ rejected). On the live decision node the tail grows down and fades toward the suggested fix card. - Waiting/hold Hold buttons use the secondary variant; tightened peek spacing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Redesigns the Up Next strip to show findings at decision granularity:
grouped rows (type + count badge) when multiple findings share a type,
single enriched rows (synthesis text + muted locator) otherwise.
- buildQueueRows groups waiting exceptions by type; groups of 1 render
as single rows with synthesis ≤45 chars, falling back to the type label
- Header shows "Up next · N findings" only when at least one group row
exists (compression is present); plain "Up next" otherwise
- Active exception excluded from Up Next items before grouping
- New ExceptionTypes: goods-not-received, qty-over-invoiced with EXCEPTION_META
- Adds INV-30291 (CDW Netherlands, grouped state, 8 findings) and
INV-30292 (Falcon Procurement, hybrid state, 4 findings) with full
detailDataMap entries and rootCause seam on INV-30291
- Synthesis sweep: all seeded exceptions now carry ≤45 char synthesis
strings; INV-30292 line 4 price-mismatch intentionally omitted as the
live fallback demo
- Hold until receipt / Route to data owner on INV-30291 gnr suggestions;
suggestionLabel("wait") reads data.label instead of hardcoded string
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ive Up Next Replaces the Up Next queue (active excluded, rows disappear) with a stable Findings map: all non-waiting findings listed in fixed escalation order, one row per type-group, cursor marks the current row in place. Row states (no reordering, no removal): - Pending individual/group: amber dot, synthesis/type label, count badge, chevron, ~120ms hover. Click moves cursor to that row. - Current individual: slightly larger filled amber dot, 500-weight text, hairline ring + muted surface, right-aligned CURRENT micro-label. - Current group: same position, type+locator, progress badge (X of N), CURRENT label, raised surface. Cursor advances as members resolve. - Resolved individual/group: muted check icon, muted text, no chevron, not clickable. Resolved group reads "type · N resolved". Header: FINDINGS · N until first resolve; FINDINGS · R OF N RESOLVED after. Section hides when only one finding total (single-finding invoices). Cursor mechanics: exactly one current row at all times; clicking non-pending is a no-op (fixes the oscillation bug where double-click toggled two issues). Stepper counter uses total non-waiting findings as M (stable denominator). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pletion pill
Seed reasoning bodies on all INV-30291 and INV-30292 group suggestions
that were missing them (gnr-2/3, qoi-1/2/2t/3/3t, price-4), so every
SuggestedFixCard renders a rationale paragraph.
Change the current-group progress pill from position semantics
("N+1 of M") to completion semantics ("N of M done") so it reflects
resolved count only, not cursor position. Resolved group label changes
from "N resolved" to "N done" for consistency.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…or-synced Four-column table (# | ITEM | QTY / PO | AMOUNT) replaces the old list. Billed qty shown amber/500 when it exceeds PO qty; PO qty in muted below. Price-mismatch lines show amount amber with PO line total in muted below. Header reads "QTY / PO" when a PO is linked, "QTY" when none (INV-60118). Removed all ↑ qty / ↑ price badges; INV-66216 monitor arm keeps "high value". Seeded poQty on INV-30291 (8/20/15), INV-30292 (25/30/16/50 + agreed on line 4), INV-GRN-001 (poQty:1). Removed Missing PO flag from INV-60118. Cursor sync: ExceptionTimeline fires runtime.setCursor on active change; RuntimeStore gains getCursor/setCursor backed by cursorMap state. DetailsCombinedTab reads cursor, resolves exception scope to a line number, and applies peak→rest amber highlight (double-rAF, 600ms transition, reduced-motion skip). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ender loop Inline array literals in JSX create a new reference on every render. The SidebarNav useEffect depends on navItems, so a new reference each render caused setExpandedItems → re-render → new reference → infinite loop. Memoize with useMemo + [] so the reference is stable for the component's lifetime. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
runtime.setCursor updates cursorMap → useMemo([map,cursorMap]) produces a new runtime object → ExceptionTimeline's useEffect (which listed runtime as a dep) fired again → setCursor → cursorMap changed → loop. Two guards: - setCursor bails out early when the stored value already equals the new one (returns prev so React skips the state update entirely). - Remove runtime from the cursor-sync effect deps; the effect's trigger is active?.id, not the runtime reference. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A. Findings section: - Remove uppercase/tracking from "Findings · N" section header — sentence case - Remove uppercase/tracking from "Current" row caption — sentence case - Row padding px-0.5 → px-2 so all rows share the same left text edge; current row inset surface is padded + rounded, not full-bleed B. Details panel metadata — grid + top-bar dedupe: - Remove Assignee, Currency, Due date, PO rows (all in persistent top bar) - Replace label-left/value-right rows with a 2-col label-over-value grid - Cluster 1: Doc date · Payment terms · VAT number · Service period (optional) - Cluster 2: Vendor (+ email) · Bill to (+ address) — long values wrap in cell - Drop Invoice / Parties / Reference section headers; hairline separation only - Add servicePeriod?: string to InvoiceDetailData; seed "Apr 1–Jun 30, 2026" on INV-55832 - Line items table now starts meaningfully higher without scrolling C. Panel default width: clamp(380, 28vw, 520) via lazy useState initializer - ~380px at 1280px viewport (floor keeps center column comfortable) - ~423px at 1512px (reference viewport) - ~520px cap on wide monitors - TypeScript: import ShellNavItem type + annotate useMemo<ShellNavItem[]> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove all CSS uppercase/tracking from user-facing labels in both the decision card and the details panel. Labels were stored in sentence case already; the CSS transform was the only thing making them render all-caps. Middle surface: - Finding card side labels (Invoice date, PO window, etc.) - Exception metrics row (Amount, Threshold, Excess) - "Up next" card label Right panel: - Details grid cluster labels (Doc date, Payment terms, VAT number, Service period, Vendor, Bill to) - Line items column headers: ITEM→Item, QTY/PO→Qty/PO, AMOUNT→Amount, remove tracking-wide (column headers now match section-header register) - "Sent" outbound email badge - "AI summary" section header Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add vendorAddress to InvoiceDetailData interface and all 10 seed entries - Add subline slot to line items; replace high-value Badge with warning-colored subline text - Remove judgment prose from linesAlert on 5 invoices; move payment-block to INV-84471 body - Add Due date to cluster 1; render vendorAddress under vendor email (street/city split) - Panel headings: "Invoice details" 16px bold, "Line items" 14px bold, both tracking-tight - Two-tier metadata grid: labels 12px/muted, values 14px/medium; spacing-only separation - Line items: fixed grid-cols-[16px_1fr_56px_80px], tabular-nums, subline slot - Qty/PO collapse to single line (12 / 8); warning color on mismatch - Row highlight bleeds edge-to-edge via -mx-6 px-6 - Total row: flex layout to avoid column overflow; 15px amount - Vendor email rendered as mailto link with hover transition - poQty sweep across INV-55832, INV-66216, INV-91003, INV-48209, INV-77294 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a full correction pipeline so fix actions and manual edits converge on a single runtime source of truth, and auto-resolve exceptions when the predicate registry clears them. Section A - correction pipeline foundation: - Add LineCorrection, DetailCorrections, PredicateBaseData types - Add correction field to Suggestion; suggestionLabel derives copy from it so qty strings never hardcode what the record already knows - Add detailCorrections to InvoiceRuntime; commitResolve merges patches - Add RESOLUTION_PREDICATES registry with qty-over-invoiced + vat-mismatch - Add correctDetail store mutation: merges corrections, runs predicates, emits auto-resolve + revalidated events atomically - Fix INV-30291 seed to use PO quantities (10/25/20) as single source of truth; correction payloads derive label copy from those values - ExceptionTimeline threads detailCorrections through resolveActive/commit - showInSource calls onRequestEdit for registered exception types (vat) Section B - edit mode: - EditModeContext provides editMode, editFocusField, enter/exitEditMode - CorrectedMark inline UserPen icon shown next to corrected field labels - DetailsCombinedTab merges rt.detailCorrections over base data (cd/dc); uses correctedLines for line items table; Edit button on heading - DetailsEditForm: controlled inputs for all extracted fields + lines, dirty tracking with label suffix and border highlight, field count footer - EditModeView: absolute overlay covering center+right, mode header bar with Cancel and Save & re-check, 420px form + flex-1 source split, Esc-to-cancel with AlertDialog discard guard - CenterPanelNext accepts onRequestEdit and passes it to ExceptionTimeline - InvoiceDetailPane provides EditModeContext and renders EditModeView when editMode is true Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…phy, field sync Section A: Route all fix actions through correctDetail (no direct resolvedIds). - outside-po-period predicate + billing-account predicate - INV-55832 date fix via correction.documentDateFormatted - INV-66216 VAT verify via correction.vat - INV-60118 account via correction.billingAccount - correctDetail extras: surfaced/dataPatch/settledSub on same mutation - SessionStorage persistence: corrections survive page reload Section B: Edit mode entrance choreography. - Overlay fades 120ms; form slides translateX 260ms ease-out - PDF column fades + rises 8px with 100ms delay - Exit reverses; doExit drives cancel/save/discard/Esc paths - Loading skeleton on PDF column until pdfReady - prefers-reduced-motion cuts all transitions Section C: Field source sync. - EDIT_FIELD_ANCHORS map + fieldAnchor() + fp() onFocus/onBlur - setFieldHighlight in runtime; takes priority over exception highlight - SourceTab: fieldHighlight overrides exceptionHighlight in activeAnchors - data-anchor on vendor, vendorEmail, billTo, billAddress Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…settle pulses
A. INV-30291 race fix: parseInt instead of Number() for PO qty string ("10 units"
was parsing as NaN, predicate never cleared). nextOpenId now prefers exceptions
after the current index before wrapping, so the cursor advances forward instead
of jumping back to the first item in the list.
B. Marker taxonomy: corrected events now render person marker (reviewer), not
sparkle. Auto-resolved events (auto: true) render sparkle (agent) instead of
the UserRoundCheck resolved marker.
C. correctionPulse added to InvoiceRuntime; correctDetail computes it atomically
(detailFields, lineNums, autoResolvedIds). DetailsCombinedTab settle-pulses
corrected scalar fields and qty cells. FindingsMap rows settle-pulse when their
ID is in autoResolvedIds. Edit-mode save defers correctDetail until 200ms after
exit so the pulse fires on the visible panel, not the hidden overlay.
D. DetailsEditForm section labels: remove uppercase/tracking-wide Tailwind classes
so "Invoice details", "Parties", "Line items" render in sentence-case weight.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds resetAllRuntimes() to the runtime store — wipes in-memory map, cursor map, and sessionStorage in one call. Surfaces as a small RotateCcw icon button next to "My Work" in the LeftNav header, with a tooltip. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Hovering or focusing a data-mutating fix action (suggest_correction, verify) shows a 1.5px dashed warning-toned outline on the evidence value in the exception card and on the matching field/line in the Details panel. Non- mutating actions (route, wait) produce no aim. verify aims at evidence only (empty correction = no panel fields targeted). Aim clears instantly on mouseleave/blur. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…do toast
A. Unifies the evidence-value affordance: replaces the Highlighter with a Pencil
on every warn-toned FindingCell, removes the sourceAnchors guard so the pen
always shows, and expands EXCEPTION_EDIT_FIELD with outside-po-period,
new-vendor, and billing-account mappings. onRequestEdit now accepts an
optional field key so it opens edit mode even when no direct mapping exists.
B. Ghost preview on aim: while a mutating fix action is hovered/focused, the
warn evidence cell shows "current → new" inside the dashed ring and the
corresponding panel metadata field echoes the same inline preview. Line-item
cells stay ring-only. aimGhostValue is computed from the first scalar entry
in rt.aimCorrection and threaded through ExceptionGroup → Stage →
LiveExceptionContent → FindingView → FindingCell.
C. Undo via toast: after any correction commit a Sonner toast shows "Corrected
{Field}" or "{N} fields corrected" with an 8s Undo action. Undo calls
revertDetail which restores detailCorrections to the pre-correction snapshot,
un-resolves the auto-cleared exceptions, and appends a combined
"Correction reverted" person event + "Re-validated" sparkle event. Previous
toast is dismissed before the new one appears.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ts, Bill to spacing fix Replace FindingsMap + ExceptionIndex with IssueDots pagination dots and carousel prev/next arrow buttons on the "Needs your decision" header row. One dot per non-waiting issue; pending = hollow circle, current = amber pill, resolved = muted fill with settle pulse on auto-resolve. Arrow buttons are 26px, hairline border, disabled-at-ends; keyboard ←/→ works when focus is in the decision header. Single issue hides arrows and dots. Removes the exceptionListVariant prop entirely. Also adds mt-0.5 to the Bill to address block so its spacing from the company name matches the Vendor section. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each edit-form input whose field participates in an open finding renders a
reference line beneath it: counterpart label, value, provenance, and a
"Use this value" action that fills the input and marks it sourced.
On save, sourced corrections get per-field events noting the origin
("applied vendor master value", "applied PO quantity") instead of the
plain old→new copy. Manually typed corrections keep the old→new format.
Annotations live-derive from the open exception set, so resolved findings
drop their reference rows and undo restores them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nce seam Adds an explicit `reference` seam to InvoiceException: each exception that implicates a specific editable field now carries the system-of-record value the reviewer should correct toward. Seeded on: INV-GRN-001 price mismatch (PO amount), INV-66216 VAT mismatch (vendor master), INV-55832 outside PO period (PO window end date), and INV-30291/INV-30292 qty-over-invoiced exceptions (PO qty per line). The edit form derives annotations from live open exceptions via referenceFieldKeyToFormKey. Each annotated input shows the reference value beneath it; Use this value fills the field. On save, corrections whose final value matches the reference produce applied-source event copy rather than the plain old→new format. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds optional shortDescription to the line item type; renders below the item title as text-xs muted-foreground, truncated to one line with a tooltip on hover exposing the full string. Seeded on INV-GRN-001 and INV-66216 for visibility. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace annotation line with a compact chip (<value> · <source>) that acts
as the apply action directly. Dry-run runPredicates in handleSave to detect
still-failing exceptions and emit targeted re-check copy ("VAT number still
differs from vendor master") instead of the generic "nothing new". Fix
FIELD_LABELS casing so event labels read "Corrected VAT number" and
"Corrected document date" correctly in-sentence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…revert - Correction toast uses inverted (dark bg / light text) style via classNames - "High value" subline replaced with Badge secondary/warning for a11y - IssueDots reverted to uniform 7px bg-foreground dots Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n issues Navigation domain is now open issues only. Dots, arrows, and the jump menu all traverse open issues; Issue n of m counts position among open, not total. Resolved issues settle-then-disappear from dots; the jump menu drops Done rows entirely. Counter and chrome hide when only one open issue remains. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7ced88d to
05e4b5e
Compare
| function handleSendClick() { | ||
| // Reduced motion: commit immediately, no sending -> sent beats. | ||
| if (reducedMotion) { | ||
| onSend(buildDraft()); | ||
| return; | ||
| } | ||
| setSendPhase("sending"); | ||
| setTimeout(() => { | ||
| setSendPhase("sent"); | ||
| setTimeout(() => onSend(buildDraft()), 500); | ||
| }, 600); | ||
| } |
| <button | ||
| type="button" | ||
| className="cursor-pointer hover:opacity-90" | ||
| > | ||
| {action} | ||
| </button> |
| res.setHeader("Access-Control-Allow-Origin", "*"); | ||
| res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS"); | ||
| res.setHeader("Access-Control-Allow-Headers", "Content-Type"); |
| "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", |
| // it (chat.update) after an action is taken. | ||
| let lastMessage = null; | ||
|
|
||
| const required = ["SLACK_BOT_TOKEN", "SLACK_APP_TOKEN"]; |
No description provided.