Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e4fae35
feat(apollo-vertex): invoice processing demo with Slack + Outlook flow
petervachon Jul 1, 2026
a4e2d77
feat(apollo-vertex): exception-review timeline (next) behind version …
petervachon Jul 1, 2026
299eea2
feat(apollo-vertex): multi-exception stack, shared model, and phased …
petervachon Jul 2, 2026
cd6ea3c
feat(apollo-vertex): completion moment, resolve choreography polish, …
petervachon Jul 2, 2026
2bbe018
feat(apollo-vertex): layout v1/v2/v3 versions and the v3 "Up next" strip
petervachon Jul 2, 2026
fbe908a
feat(apollo-vertex): expandable resolved rows and timeline row/rhythm…
petervachon Jul 6, 2026
efca734
feat(apollo-vertex): routing waiting state + supplier email flow
petervachon Jul 6, 2026
7bf8444
feat(apollo-vertex): invoice approve/hold disposition on a persistent…
petervachon Jul 6, 2026
abd980d
feat(apollo-vertex): park-family dialogs + status-tab queue rail
petervachon Jul 6, 2026
dc7a238
feat(apollo-vertex): reject disposition parity + highlight-in-source
petervachon Jul 7, 2026
4c0f477
feat(apollo-vertex): left-nav routing + approved terminal parity + ra…
petervachon Jul 7, 2026
e4e82c7
feat(apollo-vertex): Up Next decision-granularity queue + seed data
petervachon Jul 15, 2026
8863704
feat(apollo-vertex): Findings map — cursor model replaces exclude-act…
petervachon Jul 15, 2026
f6c8569
feat(apollo-vertex): findings map refinement — reasoning bodies + com…
petervachon Jul 15, 2026
db9e5ce
feat(apollo-vertex): line items table — numbered, delta-bearing, curs…
petervachon Jul 15, 2026
6c41984
fix(apollo-vertex): stabilize shellNavItems ref to break SidebarNav r…
petervachon Jul 15, 2026
74675d3
fix(apollo-vertex): break cursor-sync infinite render loop
petervachon Jul 15, 2026
e53f37f
feat(apollo-vertex): panel + findings polish pass
petervachon Jul 15, 2026
b23c262
fix(apollo-vertex): sentence case throughout middle and right surfaces
petervachon Jul 15, 2026
3d0e37e
feat(apollo-vertex): details panel v3, vendor address, line items polish
petervachon Jul 16, 2026
c8d49d1
feat(apollo-vertex): correction pipeline + edit mode
petervachon Jul 16, 2026
a4f6b66
feat(apollo-vertex): pipeline routing, sessionStorage, edit choreogra…
petervachon Jul 16, 2026
4ab2f84
feat(apollo-vertex): correction correlation — race fix, marker swap, …
petervachon Jul 16, 2026
83d9fd1
feat(apollo-vertex): reset demo state button in left nav header
petervachon Jul 16, 2026
4a0c762
feat(apollo-vertex): fix-action aim state — dashed ring on hover/focus
petervachon Jul 16, 2026
2332d23
feat(apollo-vertex): correction clarity — pen icon, ghost preview, un…
petervachon Jul 17, 2026
0203075
feat(apollo-vertex): issue carousel — prev/next arrows, pagination do…
petervachon Jul 17, 2026
7d78032
feat(apollo-vertex): finding-aware field references in edit mode
petervachon Jul 17, 2026
fea9724
feat(apollo-vertex): finding-aware field references — explicit refere…
petervachon Jul 17, 2026
ed661ff
feat(apollo-vertex): line item short description in right panel
petervachon Jul 17, 2026
b283a76
feat(apollo-vertex): field reference chip + honest re-check copy
petervachon Jul 17, 2026
84c5849
feat(apollo-vertex): inverted correction toast + warning badge + dot …
petervachon Jul 17, 2026
2a557ee
feat(apollo-vertex): open-only carousel — dots/nav/jump scoped to ope…
petervachon Jul 17, 2026
e658e58
fix(apollo-vertex): attestation aim — ring only, no ghost for verify …
petervachon Jul 17, 2026
f2ceab4
feat(apollo-vertex): fix-action naming — labels state their data effect
petervachon Jul 17, 2026
2ab1a76
feat(apollo-vertex): card action grammar — do/delegate/✕ with undo toast
petervachon Jul 17, 2026
bc55e8c
feat(apollo-vertex): aim pair-mode — suppress ghost when counterpart …
petervachon Jul 19, 2026
d0f9479
feat(apollo-vertex): aim is local — rings/ghosts in evidence pair only
petervachon Jul 19, 2026
dd8d408
feat(apollo-vertex): AI-gradient landing glow on agent-sourced correc…
petervachon Jul 20, 2026
2106bbd
feat(apollo-vertex): narrated apply arc on AI-sourced detail corrections
petervachon Jul 20, 2026
b713903
feat(apollo-vertex): cross-pane phase gating + range aim containment
petervachon Jul 20, 2026
c138ad2
feat(apollo-vertex): timeline hierarchy + arc phase machine + line-it…
petervachon Jul 21, 2026
02bd62d
feat(apollo-vertex): header disposition split button with gated approve
petervachon Jul 24, 2026
e254a61
feat(apollo-vertex): suggested fix feedback loop with correction regen
petervachon Jul 24, 2026
b3c1f5d
feat(apollo-vertex): rename Vendor to Supplier in all UI-facing strings
petervachon Jul 24, 2026
2fcde7f
feat(apollo-vertex): suggested next step card polish + vendor→supplie…
petervachon Jul 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions apps/apollo-vertex/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Slack integration for the Invoice Processing demo (Socket Mode).
# Copy this file to `.env` and fill in the real values from the "Invoice Agent"
# Slack app in the "VS Demos" workspace. NEVER commit `.env` (it is gitignored).

# Bot User OAuth Token — starts with xoxb-
SLACK_BOT_TOKEN=xoxb-your-token-here

# App-Level Token (Socket Mode, scope connections:write) — starts with xapp-
SLACK_APP_TOKEN=xapp-your-token-here

# Signing Secret — 32-char hex. Not strictly required for Socket Mode,
# included for completeness.
SLACK_SIGNING_SECRET=your-32-char-hex-signing-secret

# Channel ID for #ap-exceptions — starts with C
SLACK_CHANNEL_ID=C0XXXXXXXXX

# Port for the Slack listener's small local HTTP endpoint (escalation trigger).
# Kept off 3000 so it never collides with the Next.js dev server.
LISTENER_PORT=3010
7 changes: 7 additions & 0 deletions apps/apollo-vertex/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ public/r/
app/theme.generated.css
.vercel
.env*.local

# Slack demo runtime state (regenerated by the listener / reset script)
data/demo-state.json

# Isolated Slack listener deps (installed with npm, not pnpm)
slack/node_modules
slack/package-lock.json
5 changes: 4 additions & 1 deletion apps/apollo-vertex/.oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@
".dependency-cruiser.js",
"scripts/**",
"next-env.d.ts",
"types/**/*.d.ts"
"types/**/*.d.ts",
"slack/**",
"templates/invoice-review/**",
"app/api/demo-*/**"
],
"overrides": [
{
Expand Down
3 changes: 3 additions & 0 deletions apps/apollo-vertex/app/_meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export default {
"data-querying": "Data Querying",
localization: "Localization",
mcp: "MCP Server",
"invoice-review": {
display: "hidden",
},
auth_callback: {
display: "hidden",
},
Expand Down
34 changes: 34 additions & 0 deletions apps/apollo-vertex/app/api/demo-reply/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { type NextRequest, NextResponse } from "next/server";

// Server-side proxy: the Comms reply input POSTs here, and we forward to the
// Slack listener, which posts the reply into the card's thread (as the
// reviewer) and records it in the shared store.
export const dynamic = "force-dynamic";

const LISTENER_PORT = process.env.LISTENER_PORT || "3010";

export async function POST(request: NextRequest) {
let body = "{}";

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning

The initial value of body is unused, since it is always overwritten.
try {
body = JSON.stringify(await request.json());
} catch {
body = "{}";
}
try {
const res = await fetch(`http://localhost:${LISTENER_PORT}/reply`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
});
const data = await res.json();
return NextResponse.json(data, { status: res.ok ? 200 : res.status });
} catch {
return NextResponse.json(
{
ok: false,
error: `Slack listener not reachable on :${LISTENER_PORT}. Start it with: cd slack && npm start`,
},
{ status: 502 },
);
Comment on lines +26 to +32
}
}
18 changes: 18 additions & 0 deletions apps/apollo-vertex/app/api/demo-state/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { readFile } from "node:fs/promises";
import path from "node:path";
import { NextResponse } from "next/server";

// Reads the shared demo store that the Slack listener writes to. The invoice
// review UI polls this every couple seconds to reflect Slack-driven actions.
export const dynamic = "force-dynamic";

export async function GET() {
try {
const file = path.join(process.cwd(), "data", "demo-state.json");
const raw = await readFile(file, "utf8");
return NextResponse.json(JSON.parse(raw));
} catch {
// Store not created yet (listener never ran) — return an empty overlay.
return NextResponse.json({ invoices: {}, updated_at: null });
}
}
37 changes: 37 additions & 0 deletions apps/apollo-vertex/app/api/demo-trigger/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { type NextRequest, NextResponse } from "next/server";

// Server-side proxy: the prototype's "Escalate to manager" flag action POSTs
// here, and we forward to the standalone Slack listener's local HTTP endpoint.
// Keeps the listener port server-side (no CORS, no client hardcoding).
export const dynamic = "force-dynamic";

const LISTENER_PORT = process.env.LISTENER_PORT || "3010";

export async function POST(request: NextRequest) {
let body = "{}";

Check warning

Code scanning / CodeQL

Useless assignment to local variable Warning

The initial value of body is unused, since it is always overwritten.
try {
body = JSON.stringify(await request.json());
} catch {
body = "{}"; // no body is fine — listener falls back to demo defaults
}
try {
const res = await fetch(
`http://localhost:${LISTENER_PORT}/trigger-escalation`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body,
},
);
const data = await res.json();
return NextResponse.json(data, { status: res.ok ? 200 : 502 });
} catch {
return NextResponse.json(
{
ok: false,
error: `Slack listener not reachable on :${LISTENER_PORT}. Start it with: cd slack && npm start`,
},
{ status: 502 },
);
Comment on lines +29 to +35
}
}
10 changes: 10 additions & 0 deletions apps/apollo-vertex/app/invoice-review/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"use client";
import { InvoiceReviewTemplate } from "@/templates/invoice-review/InvoiceReviewTemplate";

export default function InvoiceReviewPage() {
return (
<div className="fixed inset-0 z-50 bg-background">
<InvoiceReviewTemplate />
</div>
);
}
4 changes: 4 additions & 0 deletions apps/apollo-vertex/lib/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export const SUPPORTED_LOCALES = Object.keys(
const DEFAULT_LOCALE: SupportedLocale = "en";

export const configurei18n = async () => {
if (i18n.isInitialized) {
document.documentElement.lang = i18n.language;
return;
}
await i18n
.use({
type: "backend",
Expand Down
2 changes: 2 additions & 0 deletions apps/apollo-vertex/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@
"in_baseline": "In baseline",
"info": "Info",
"invoice": "Invoice",
"invoices": "Invoices",
"ixp_baseline": "Baseline",
"ixp_count_different": "{{count}} different field(s)",
"ixp_count_identical": "{{count}} identical field(s)",
Expand Down Expand Up @@ -167,6 +168,7 @@
"more_pages": "More pages",
"multi_line_too_few_metrics": "Multi-line needs two distinct metrics, but the request resolved to fewer than two.",
"my_template": "My Template",
"my_work": "My Work",
"need_help": "Need help? Contact your administrator",
"new_conversation": "New conversation",
"new_conversation_confirm_description": "This will clear all messages and cannot be undone.",
Expand Down
4 changes: 3 additions & 1 deletion apps/apollo-vertex/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
"main": "index.js",
"scripts": {
"dev": "pnpm generate:theme && next --turbopack",
"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",
Comment on lines +9 to +10
"build": "pnpm generate:theme && pnpm registry:build && next build",
"start": "next start",
"generate:theme": "node --experimental-strip-types scripts/generate-theme-css.ts",
"registry:build": "shadcn build && node --experimental-strip-types scripts/generate-registry-index.ts",
"postbuild": "pagefind --site .next/server/app --output-path public/_pagefind",
"format": "biome format --write",
"format:check": "biome format",
"lint": "oxlint --type-aware",
"lint": "oxlint --type-aware && node scripts/check-type-drift.mjs",
"lint:fix": "oxlint --fix --type-aware",
"lint:deps": "depcruise registry --config .dependency-cruiser.js",
"typecheck": "tsc --noEmit"
Expand Down
Binary file added apps/apollo-vertex/public/peter-vachon.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions apps/apollo-vertex/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@
"chart-3": "oklch(0.8300 0.1550 75.2000)",
"chart-4": "oklch(0.7200 0.1800 320.8000)",
"chart-5": "oklch(0.6800 0.1500 245.5000)",
"insight-50": "oklch(0.96 0.03 277)",
"insight-100": "oklch(0.92 0.05 277)",
"insight-200": "oklch(0.86 0.09 277)",
"insight-300": "oklch(0.78 0.14 277)",
"insight-400": "oklch(0.70 0.19 277)",
"insight-500": "oklch(0.62 0.22 277)",
"insight-600": "oklch(0.56 0.20 277)",
"insight-700": "oklch(0.48 0.17 277)",
"insight-800": "oklch(0.38 0.13 278)",
"insight-900": "oklch(0.30 0.10 278)",
"sidebar": "oklch(0.9723 0.0074 260.7300)",
"sidebar-foreground": "oklch(0.2394 0.0455 252.4450)",
"sidebar-primary": "oklch(0.64 0.115 208)",
Expand Down Expand Up @@ -258,6 +268,16 @@
"chart-3": "oklch(0.8300 0.1550 75.2000)",
"chart-4": "oklch(0.7200 0.1800 320.8000)",
"chart-5": "oklch(0.6800 0.1500 245.5000)",
"insight-50": "oklch(0.96 0.03 277)",
"insight-100": "oklch(0.92 0.05 277)",
"insight-200": "oklch(0.86 0.09 277)",
"insight-300": "oklch(0.78 0.14 277)",
"insight-400": "oklch(0.70 0.19 277)",
"insight-500": "oklch(0.62 0.22 277)",
"insight-600": "oklch(0.56 0.20 277)",
"insight-700": "oklch(0.48 0.17 277)",
"insight-800": "oklch(0.38 0.13 278)",
"insight-900": "oklch(0.30 0.10 278)",
"sidebar": "oklch(0.1620 0.0310 257.7000)",
"sidebar-foreground": "oklch(0.9525 0.0110 225.9830)",
"sidebar-primary": "oklch(0.69 0.112 207)",
Expand Down
2 changes: 1 addition & 1 deletion apps/apollo-vertex/registry/button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as React from "react";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
Comment on lines 7 to 9
variants: {
variant: {
Expand Down
29 changes: 29 additions & 0 deletions apps/apollo-vertex/registry/shell/shell-profile-extras.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use client";

import { createContext, type ReactNode, useContext } from "react";

/**
* Optional slot for injecting extra items into the shell's user-profile menu.
* Apps wrap their tree in `ShellProfileExtrasProvider` and pass menu items
* (e.g. `DropdownMenuItem`s); they render below the built-in theme/language
* entries. Empty by default, so the shell is unaffected when unused.
*/
const ShellProfileExtrasContext = createContext<ReactNode>(null);

export function ShellProfileExtrasProvider({
items,
children,
}: {
items: ReactNode;
children: ReactNode;
}) {
return (
<ShellProfileExtrasContext.Provider value={items}>
{children}
</ShellProfileExtrasContext.Provider>
);
}

export function useShellProfileExtras(): ReactNode {
return useContext(ShellProfileExtrasContext);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { cn } from "@/lib/utils";
import { useAuth } from "./shell-auth-provider";
import { LANGUAGE_CHANGED_EVENT, LOCALE_OPTIONS } from "./shell-constants";
import type { LanguageChangedEvent } from "./shell-constants";
import { useShellProfileExtras } from "./shell-profile-extras";
import { Text } from "./shell-text";
import { useTheme } from "./shell-theme-provider";

Expand All @@ -20,6 +21,7 @@ export const UserProfileMenuItems = () => {
const { logout } = useAuth();
const { setTheme } = useTheme();
const language = i18n.language;
const extras = useShellProfileExtras();

function setLanguage(code: SupportedLocale) {
document.dispatchEvent(
Expand Down Expand Up @@ -69,6 +71,7 @@ export const UserProfileMenuItems = () => {
))}
</DropdownMenuSubContent>
</DropdownMenuSub>
{extras}
<DropdownMenuSeparator />
<DropdownMenuItem onClick={logout}>
<LogOut className="w-4 h-4" />
Expand Down
49 changes: 49 additions & 0 deletions apps/apollo-vertex/scripts/check-type-drift.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env node
/**
* Type-drift guard for the invoice-review timeline.
*
* The design locks font sizes to an agreed scale, expressed as `text-[Npx]`
* because Tailwind has no 11/13/18/22 classes and the project defines no custom
* font-size scale. This fails loudly if an OFF-SCALE arbitrary size slips in
* (e.g. a headline drifting to `text-[2rem]`), so item 3's revert cannot
* silently regress.
*
* Note: an earlier revision also banned font weights above 500. That rule was
* dropped because the focus/current exception headline is now deliberately
* `font-bold` (the anchor). If a weight ceiling is wanted again, add it here
* consciously (do not add inline escape hatches in the source).
*/
import { readFileSync } from "node:fs";

const FILE = "templates/invoice-review/next/ExceptionTimeline.tsx";

// The agreed size scale. Sizes must be one of these; anything else is drift.
const ALLOWED_SIZES = new Set(["11px", "12px", "13px", "14px", "18px", "22px"]);
const isLength = (v) => /^-?[\d.]+(px|rem|em)$/.test(v.trim());

const violations = [];
const lines = readFileSync(FILE, "utf8").split("\n");

lines.forEach((line, i) => {
for (const m of line.matchAll(/text-\[([^\]]+)\]/g)) {
const value = m[1].trim();
if (isLength(value) && !ALLOWED_SIZES.has(value)) {
violations.push({
line: i + 1,
text: line.trim(),
why: `off-scale size text-[${value}] (allowed: ${[...ALLOWED_SIZES].join(", ")})`,
});
}
}
});

if (violations.length > 0) {
console.error(`Type drift in ${FILE}:`);
for (const v of violations) {
console.error(` ${FILE}:${v.line} ${v.why}`);
console.error(` ${v.text}`);
}
process.exit(1);
}

console.log(`Type-drift guard passed: ${FILE}`);
Loading
Loading