Remove probe screenshots + a test fixture shipped by the old filename-only exclusion (deploy_web now skips by path part)
Browse files
web/_qa_live_rail/1_before.png
DELETED
Git LFS Details
|
web/_qa_live_rail/2_menu.png
DELETED
|
Binary file (78.5 kB)
|
|
|
web/_qa_live_rail/3_prompt.png
DELETED
|
Binary file (84.9 kB)
|
|
|
web/_qa_live_rail/4_after.png
DELETED
|
Binary file (69.1 kB)
|
|
|
web/_qa_live_rail/5_fields.png
DELETED
|
Binary file (84.3 kB)
|
|
|
web/_qa_live_rail/debug.png
DELETED
Git LFS Details
|
web/src/pages/_fixtures/sales.ts
DELETED
|
@@ -1,190 +0,0 @@
|
|
| 1 |
-
// ---------------------------------------------------------------------------
|
| 2 |
-
// pages / _fixtures/sales.ts — EXIT wave 2 (W2-7).
|
| 3 |
-
//
|
| 4 |
-
// A Y1 envelope, transcribed from S1's CP-Y1 post. The wave brief is explicit
|
| 5 |
-
// that the client builds against a FIXTURE and not a live API — S1's route may
|
| 6 |
-
// not exist when this renders, and that is expected, not a problem.
|
| 7 |
-
//
|
| 8 |
-
// It is ALSO the render gate's input (`verify_ui.py`'s smoke section), which is
|
| 9 |
-
// the reason it lives in the tree rather than in a scratch file: a fixture that
|
| 10 |
-
// only a human ever looks at drifts from the contract silently, and one a gate
|
| 11 |
-
// renders every run cannot.
|
| 12 |
-
//
|
| 13 |
-
// ⚠ IT MUST STAY A TRANSCRIPTION. Every optional field S1 enumerated is present
|
| 14 |
-
// exactly once so the renderer is exercised on the real shape — `subtitle`,
|
| 15 |
-
// `controls`, `delta_dir:"off"` with a null delta, an inline KPI drill, a
|
| 16 |
-
// `series` chart with `x_order` and a per-row `_drill`, a table with a
|
| 17 |
-
// block-level entity drill AND a `shown`/`total` cap, and a validation block.
|
| 18 |
-
// If the server's shape moves, this file moves with it — never the other way.
|
| 19 |
-
// ---------------------------------------------------------------------------
|
| 20 |
-
|
| 21 |
-
import type { PageEnvelope } from "../../ui/types";
|
| 22 |
-
|
| 23 |
-
const D = (label: string, from: string, to: string, cf: string, ct: string) => ({
|
| 24 |
-
kind: "decomp" as const,
|
| 25 |
-
label,
|
| 26 |
-
scope_type: "period",
|
| 27 |
-
scope_value: null,
|
| 28 |
-
date_from: from,
|
| 29 |
-
date_to: to,
|
| 30 |
-
cmp_from: cf,
|
| 31 |
-
cmp_to: ct,
|
| 32 |
-
});
|
| 33 |
-
|
| 34 |
-
const MONTHS = [
|
| 35 |
-
["2025-08", 301882, 288140], ["2025-09", 342110, 310422], ["2025-10", 388904, 351009],
|
| 36 |
-
["2025-11", 402551, 377430], ["2025-12", 511903, 468221], ["2026-01", 355012, 331880],
|
| 37 |
-
["2026-02", 371445, 340118], ["2026-03", 420338, 402955], ["2026-04", 398217, 372660],
|
| 38 |
-
["2026-05", 441098, 410233], ["2026-06", 462770, 428514], ["2026-07", 425763, 411998],
|
| 39 |
-
] as const;
|
| 40 |
-
|
| 41 |
-
export const SALES_FIXTURE: PageEnvelope = {
|
| 42 |
-
key: "sales",
|
| 43 |
-
title: "Sales",
|
| 44 |
-
subtitle:
|
| 45 |
-
"Live from Odoo · All Business Units · revenue vs the same period last year " +
|
| 46 |
-
"(floral is seasonal — YoY same-period).",
|
| 47 |
-
as_of: "2026-07-30T18:22:04Z",
|
| 48 |
-
controls: [
|
| 49 |
-
{
|
| 50 |
-
key: "bu",
|
| 51 |
-
kind: "bu",
|
| 52 |
-
label: "Business Unit",
|
| 53 |
-
value: "all",
|
| 54 |
-
options: [
|
| 55 |
-
{ value: "all", label: "All" },
|
| 56 |
-
{ value: 5, label: "Fisch" },
|
| 57 |
-
{ value: 6, label: "Royal" },
|
| 58 |
-
],
|
| 59 |
-
},
|
| 60 |
-
{
|
| 61 |
-
key: "period",
|
| 62 |
-
kind: "choice",
|
| 63 |
-
label: "Granularity",
|
| 64 |
-
value: "monthly",
|
| 65 |
-
options: [
|
| 66 |
-
{ value: "monthly", label: "Monthly" },
|
| 67 |
-
{ value: "weekly", label: "Weekly" },
|
| 68 |
-
],
|
| 69 |
-
},
|
| 70 |
-
],
|
| 71 |
-
blocks: [
|
| 72 |
-
{
|
| 73 |
-
type: "kpis",
|
| 74 |
-
key: "scorecard",
|
| 75 |
-
items: [
|
| 76 |
-
{
|
| 77 |
-
key: "ytd", label: "Year to date", value: 4821993.4, fmt: "money",
|
| 78 |
-
delta: 12.4, delta_fmt: "pct", delta_label: "vs LY", delta_dir: "up",
|
| 79 |
-
drill: D("Sales · Year to date", "2026-01-01", "2026-07-30", "2025-01-01", "2025-07-30"),
|
| 80 |
-
},
|
| 81 |
-
{
|
| 82 |
-
key: "mtd", label: "Month to date", value: 425763, fmt: "money",
|
| 83 |
-
delta: 3.3, delta_fmt: "pct", delta_label: "vs LY", delta_dir: "up",
|
| 84 |
-
drill: D("Sales · Month to date", "2026-07-01", "2026-07-30", "2025-07-01", "2025-07-30"),
|
| 85 |
-
},
|
| 86 |
-
{
|
| 87 |
-
key: "wtd", label: "Week to date", value: 88410, fmt: "money",
|
| 88 |
-
delta: -6.1, delta_fmt: "pct", delta_label: "vs LY", delta_dir: "down",
|
| 89 |
-
drill: D("Sales · Week to date", "2026-07-27", "2026-07-30", "2025-07-28", "2025-07-31"),
|
| 90 |
-
},
|
| 91 |
-
// Y1 rule 4 — no orders yet, so NO number and no formatter. The card
|
| 92 |
-
// must never render an alarming −100%.
|
| 93 |
-
{
|
| 94 |
-
key: "today", label: "Today", value: 0, fmt: "money",
|
| 95 |
-
delta: null, delta_fmt: null, delta_label: "no orders yet", delta_dir: "off",
|
| 96 |
-
drill: D("Sales · Today", "2026-07-30", "2026-07-30", "2025-07-30", "2025-07-30"),
|
| 97 |
-
},
|
| 98 |
-
],
|
| 99 |
-
},
|
| 100 |
-
{
|
| 101 |
-
type: "kpis",
|
| 102 |
-
key: "ytd_context",
|
| 103 |
-
items: [
|
| 104 |
-
{ key: "orders", label: "Orders YTD", value: 9412, fmt: "int" },
|
| 105 |
-
{ key: "aov", label: "Average order", value: 512.3, fmt: "money" },
|
| 106 |
-
{ key: "customers", label: "Active customers", value: 812, fmt: "int" },
|
| 107 |
-
],
|
| 108 |
-
},
|
| 109 |
-
{
|
| 110 |
-
type: "section",
|
| 111 |
-
key: "trend_h",
|
| 112 |
-
label: "Revenue trend",
|
| 113 |
-
note: "This period vs the same period last year. Click any bar to decompose that period.",
|
| 114 |
-
},
|
| 115 |
-
{
|
| 116 |
-
type: "chart",
|
| 117 |
-
key: "trend",
|
| 118 |
-
spec: {
|
| 119 |
-
id: "sales-trend", kind: "bar", x: "period", agg: "sum",
|
| 120 |
-
title: "Revenue trend", palette: "brand", axis: { y: { format: "currency" } },
|
| 121 |
-
},
|
| 122 |
-
series: [
|
| 123 |
-
{ y: "revenue", label: "This year" },
|
| 124 |
-
{ y: "revenue_ly", label: "Last year" },
|
| 125 |
-
],
|
| 126 |
-
fields: [
|
| 127 |
-
{ key: "period", label: "Month", type: "text", source: "odoo" },
|
| 128 |
-
{ key: "revenue", label: "Revenue", type: "currency", source: "odoo" },
|
| 129 |
-
{ key: "revenue_ly", label: "Revenue LY", type: "currency", source: "odoo" },
|
| 130 |
-
],
|
| 131 |
-
rows: MONTHS.map(([period, rev, ly]) => ({
|
| 132 |
-
period,
|
| 133 |
-
revenue: rev,
|
| 134 |
-
revenue_ly: ly,
|
| 135 |
-
_drill: D(`Sales · ${period}`, `${period}-01`, `${period}-28`,
|
| 136 |
-
`${Number(period.slice(0, 4)) - 1}${period.slice(4)}-01`,
|
| 137 |
-
`${Number(period.slice(0, 4)) - 1}${period.slice(4)}-28`),
|
| 138 |
-
})),
|
| 139 |
-
// ⛔ The server owns the chronology — the engine would sort these by value.
|
| 140 |
-
x_order: MONTHS.map(([period]) => period),
|
| 141 |
-
drill: { kind: "decomp", row_key: "_drill" },
|
| 142 |
-
shown: 12,
|
| 143 |
-
total: 12,
|
| 144 |
-
},
|
| 145 |
-
{
|
| 146 |
-
type: "section",
|
| 147 |
-
key: "top_h",
|
| 148 |
-
label: "Top customers",
|
| 149 |
-
note: "Year-to-date revenue. Open a customer to see the orders behind the number.",
|
| 150 |
-
},
|
| 151 |
-
{
|
| 152 |
-
type: "table",
|
| 153 |
-
key: "top_customers",
|
| 154 |
-
columns: [
|
| 155 |
-
{ key: "customer", label: "Customer" },
|
| 156 |
-
{ key: "revenue", label: "YTD $", fmt: "money", align: "right" },
|
| 157 |
-
{ key: "orders", label: "Orders", fmt: "int", align: "right" },
|
| 158 |
-
{ key: "last_order", label: "Last order", fmt: "date", align: "right" },
|
| 159 |
-
],
|
| 160 |
-
rows: [
|
| 161 |
-
{ pid: 4412, customer: "ACME FLORAL", revenue: 118402, orders: 37, last_order: "2026-07-24" },
|
| 162 |
-
{ pid: 5120, customer: "GARDEN STATE SUPPLY", revenue: 96110, orders: 29, last_order: "2026-07-19" },
|
| 163 |
-
{ pid: 3308, customer: "BLOOM & CO", revenue: 88240, orders: 41, last_order: "2026-07-28" },
|
| 164 |
-
],
|
| 165 |
-
// ⛔ A REAL cap: 3 of 812. The disclosure is the server's numbers.
|
| 166 |
-
shown: 3,
|
| 167 |
-
total: 812,
|
| 168 |
-
drill: { kind: "customer", id_key: "pid", label_key: "customer" },
|
| 169 |
-
download: { filename: "top_customers" },
|
| 170 |
-
empty: "No confirmed orders in this scope yet.",
|
| 171 |
-
},
|
| 172 |
-
{
|
| 173 |
-
type: "validation",
|
| 174 |
-
key: "validation",
|
| 175 |
-
checks: [
|
| 176 |
-
{
|
| 177 |
-
name: "YTD revenue: order-level == line-level", ok: true,
|
| 178 |
-
expected: 4821993.4, actual: 4821993.4, fmt: "money", note: "gap 0.00",
|
| 179 |
-
},
|
| 180 |
-
{
|
| 181 |
-
name: "Order count ties to the confirmed-order aggregate", ok: true,
|
| 182 |
-
expected: 9412, actual: 9412, fmt: "int",
|
| 183 |
-
},
|
| 184 |
-
],
|
| 185 |
-
},
|
| 186 |
-
// Y1 rule 7 — a block type this client does not know. It must be SKIPPED
|
| 187 |
-
// and render NOTHING, which is what lets the server ship a new type first.
|
| 188 |
-
{ type: "waterfall", key: "future_block", note: "not known to this client" },
|
| 189 |
-
],
|
| 190 |
-
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|