{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "reconciliation",
  "type": "registry:component",
  "title": "Reconciliation",
  "description": "Two lists matched line by line. Sample data: bank feed against ledger, NORDIC $9,100 against INV-4480 $9,000, a $35 bank fee with no match.",
  "registryDependencies": [
    "@chartory/core",
    "@chartory/plot-recon"
  ],
  "files": [
    {
      "path": "registry/chartory/components/reconciliation.tsx",
      "type": "registry:component",
      "target": "components/chartory/reconciliation.tsx",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// Reconciliation (Chartory chart 075): sizes xs | s | m | l, laid out again from the data at each\n// size; collection \"hearth\" (default) or \"signal\" (with @chartory/signal, Code and Complete). Data in the type's shape (default: the sample). Docs:\n// https://usechartory.com/docs/charts/reconciliation\nimport { ChartView, type ChartViewProps } from \"@/lib/chartory/charts/chart-view\"\nimport { registerChart } from \"@/lib/chartory/charts/defs\"\nimport { data as sample } from \"@/lib/chartory/charts/types/075-reconciliation/data\"\nimport { def } from \"@/lib/chartory/charts/types/075-reconciliation/layout\"\n\nregisterChart(def, sample)\n\nexport type ReconciliationData = typeof sample\nexport type ReconciliationProps = Omit<ChartViewProps, \"def\" | \"data\" | \"states\"> & {\n  /** the data to draw (default: the sample) */\n  data?: ReconciliationData\n  /** further data states: the chart morphs to them (update) or ticks through them (live, with interval) */\n  states?: ReconciliationData[]\n}\n\nexport function Reconciliation(props: ReconciliationProps) {\n  return <ChartView def={def} {...props} />\n}\n"
    },
    {
      "path": "registry/chartory/charts/types/075-reconciliation/data.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/075-reconciliation/data.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 075 Reconciliation · sample data (Figma chart/075-recon-matching, fintech: bank feed vs ledger, Oct 9).\nimport type { ReconData } from \"../../plots/recon\";\n\nexport const data: ReconData = {\n  heads: [\"BANK FEED\", \"LEDGER\"],\n  left: [\"STRIPE · $12,400\", \"ACME CORP · $4,820\", \"AWS · $2,310\", \"PAYROLL · $48,000\", \"REFUND · $129\", \"NORDIC · $9,100\", \"BANK FEE · $35\"],\n  right: [\"INV-4471 · $4,820\", \"STRIPE · $12,400\", \"PAYROLL · $48,000\", \"AWS SEP · $2,310\", \"INV-4480 · $9,000\", \"CREDIT · $129\", \"TOOLS · $640\"],\n  links: [[0, 1, \"ok\"], [1, 0, \"ok\"], [2, 3, \"ok\"], [3, 2, \"ok\"], [4, 5, \"ok\"], [5, 4, \"partial\"]],\n};\n"
    },
    {
      "path": "registry/chartory/charts/types/075-reconciliation/layout.ts",
      "type": "registry:lib",
      "target": "lib/chartory/charts/types/075-reconciliation/layout.ts",
      "content": "// SPDX-License-Identifier: MIT · Copyright (c) 2026 BEY AGENCY LTD · Chartory, usechartory.com\n// 075 Reconciliation (Figma chart/075-recon-matching): C.recon on a day of bank feed and ledger entries.\nimport { buildVariant, type ChartDef, type Size, type TooltipSpec } from \"../../core/shell\";\nimport { recon } from \"../../plots/recon\";\nimport { data } from \"./data\";\n\nconst ok = data.links.filter((l) => l[2] === \"ok\").length, partial = data.links.length - ok;\nconst unL = data.left.filter((_, i) => !data.links.some((l) => l[0] === i));\nconst unR = data.right.filter((_, i) => !data.links.some((l) => l[1] === i));\nconst cap = (s: string) => s.split(\" · \").map((x, i) => (i ? x : x.charAt(0) + x.slice(1).toLowerCase())).join(\" · \");\n\n/** What a variant draws, mark by mark: kind, text style, path commands (an entry's frame is named by its text). */\nconst marks = (d: unknown, s: Size, lg: boolean) =>\n  buildVariant(def, s, lg, d).marks.map((m) => m.kind + \"|\" + (m.kind === \"text\" ? m.style : m.kind === \"vector\" ? m.d.replace(/[^MLCZ]/g, \"\") : \"\")).join(\"\\n\");\n/** True when b draws the same marks as a at every size, legend on and off (narrow entries split the same way). */\nconst sameMarks = (a: unknown, b: unknown) => ([\"xs\", \"s\", \"m\", \"l\"] as Size[]).every((s) => [true, false].every((lg) => marks(a, s, lg) === marks(b, s, lg)));\n\nconst amt = (s: string) => +(s.split(\" · \")[1] ?? \"\").replace(/[$,]/g, \"\");\nconst usd = (v: number) => \"$\" + v.toLocaleString(\"en-US\");\nconst nameOf = (s: string) => cap(s.split(\" · \")[0]);\n\n// tooltip: a match link (groups 0 … links − 1): the two entries it joins, and the difference when the amounts\n// differ; or an entry row (group 100 + row, which the plot gives to the bank entry and the ledger entry on that row):\n// both entries with their state (swatch = matched / amount differs / no match)\nconst tip = (o: typeof data, g: number): TooltipSpec | null => {\n  if (g < 100) {\n    const lk = o.links[g];\n    if (!lk) return null;\n    const [l, r, kind] = lk, tok = kind === \"ok\" ? \"status/success\" : \"status/warning\";\n    const a = amt(o.left[l]), b = amt(o.right[r]);\n    const rows = [{ label: \"Bank · \" + nameOf(o.left[l]), value: usd(a), swatch: tok }, { label: \"Ledger · \" + nameOf(o.right[r]), value: usd(b), swatch: tok }];\n    if (a !== b) rows.push({ label: \"Difference\", value: (a - b > 0 ? \"+\" : \"−\") + usd(Math.abs(a - b)), swatch: tok });\n    return { kind: \"range\", caps: kind === \"ok\" ? \"MATCHED\" : \"AMOUNT DIFFERS\", rows };\n  }\n  const i = g - 100;\n  const st = (side: 0 | 1) => {\n    const lk = o.links.find((q) => q[side] === i);\n    return !lk ? \"status/danger\" : lk[2] === \"ok\" ? \"status/success\" : \"status/warning\";\n  };\n  const rows = [\n    ...(o.left[i] ? [{ label: \"Bank · \" + nameOf(o.left[i]), value: usd(amt(o.left[i])), swatch: st(0) }] : []),\n    ...(o.right[i] ? [{ label: \"Ledger · \" + nameOf(o.right[i]), value: usd(amt(o.right[i])), swatch: st(1) }] : []),\n  ];\n  return rows.length ? { kind: \"range\", caps: \"ROW \" + (i + 1), rows } : null;\n};\n\nexport const def: ChartDef = {\n  no: 75,\n  slug: \"reconciliation\",\n  legend: [[\"status/success\", \"Matched\"], [\"status/warning\", \"Amount differs\"], [\"status/danger\", \"No match\"]],\n  plot: (K, p, w, d) => recon(K, p, w, (d as typeof data | undefined) ?? data),\n  tip: (d, g) => tip(d as typeof data, g),\n  // later in the day: the ledger lists its entries in a new posting order, so the match curves re-route (same entries,\n  // same matches); the first order from `step` on that splits narrow entries the same way at every size is taken\n  update: (d, step) => {\n    const o = d as typeof data, n = o.right.length;\n    const at = (k: number) => ({\n      ...o,\n      right: o.right.map((_, j) => o.right[(j + k) % n]),\n      links: o.links.map(([l, r, kind]) => [l, (r - k + n) % n, kind] as (typeof o.links)[number]),\n    });\n    return Array.from({ length: n - 1 }, (_, i) => at(((step + i - 1) % (n - 1)) + 1)).find((c) => sameMarks(o, c)) ?? o;\n  },\n  describe: {\n    title: \"Bank matching\",\n    summary: `${data.left.length} bank feed entries against ${data.right.length} ledger entries: ${ok} matched, ${partial} with a different amount, ${unL.length + unR.length} without a match.`,\n    table: {\n      head: [\"Bank feed\", \"Ledger\", \"State\"],\n      rows: [\n        ...data.links.map(([l, r, k]) => [cap(data.left[l]), cap(data.right[r]), k === \"ok\" ? \"Matched\" : \"Amount differs\"]),\n        ...unL.map((t) => [cap(t), \"\", \"No match\"]),\n        ...unR.map((t) => [\"\", cap(t), \"No match\"]),\n      ],\n    },\n  },\n};\n"
    }
  ],
  "categories": [
    "charts",
    "flow-between-steps"
  ],
  "meta": {
    "access": "free",
    "no": 75,
    "family": "Flow between steps"
  }
}
