From 2b68e785926b752f3b66e52723b6d0099bef4d53 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Sun, 18 Oct 2026 16:02:59 -0700 Subject: [PATCH 1/2] Measure what's left of upstream; drop two unused components tools/divergence.mjs (npm run divergence) reports how much of the console is still upstream's, against the v1.0.11 fork point: the share of lines upstream didn't write, upstream lines left and in how many files, and the files still carrying upstream's dual header. Each PR of the rewrite to our own code states how far it moved these numbers; --files lists what's left, file by file. The chart and scroll-area components weren't used anywhere, so they go, with @radix-ui/react-scroll-area. --- package-lock.json | 32 ------------- package.json | 4 +- src/components/ui/chart.tsx | 63 ------------------------- src/components/ui/scroll-area.tsx | 44 ------------------ tools/divergence.mjs | 77 +++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 141 deletions(-) delete mode 100644 src/components/ui/chart.tsx delete mode 100644 src/components/ui/scroll-area.tsx create mode 100644 tools/divergence.mjs diff --git a/package-lock.json b/package-lock.json index cea7ce9..0b4591b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,6 @@ "@radix-ui/react-dropdown-menu": "^2.1.24", "@radix-ui/react-label": "^2.1.15", "@radix-ui/react-popover": "^1.1.23", - "@radix-ui/react-scroll-area": "^1.2.18", "@radix-ui/react-select": "^2.3.7", "@radix-ui/react-separator": "^1.1.15", "@radix-ui/react-slot": "^1.3.3", @@ -1286,37 +1285,6 @@ } } }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.18", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz", - "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.3", - "@radix-ui/primitive": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.5", - "@radix-ui/react-context": "1.2.2", - "@radix-ui/react-direction": "1.1.4", - "@radix-ui/react-presence": "1.1.10", - "@radix-ui/react-primitive": "2.1.10", - "@radix-ui/react-use-callback-ref": "1.1.4", - "@radix-ui/react-use-layout-effect": "1.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-select": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", diff --git a/package.json b/package.json index a90e2ee..fa924a9 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "i18n:check": "node tools/i18n-extract.mjs --check", "notice:check": "node tools/notice-check.mjs", "notice:fix": "node tools/notice-check.mjs --fix", - "manual": "node tools/build-manual.mjs --schema ../inbuxa-server/resources/schema/schema.json.gz --out ../inbuxa.org/docs-site/docs/reference" + "manual": "node tools/build-manual.mjs --schema ../inbuxa-server/resources/schema/schema.json.gz --out ../inbuxa.org/docs-site/docs/reference", + "divergence": "node tools/divergence.mjs" }, "dependencies": { "@daypicker/react": "^10.0.1", @@ -31,7 +32,6 @@ "@radix-ui/react-dropdown-menu": "^2.1.24", "@radix-ui/react-label": "^2.1.15", "@radix-ui/react-popover": "^1.1.23", - "@radix-ui/react-scroll-area": "^1.2.18", "@radix-ui/react-select": "^2.3.7", "@radix-ui/react-separator": "^1.1.15", "@radix-ui/react-slot": "^1.3.3", diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx deleted file mode 100644 index 204b69b..0000000 --- a/src/components/ui/chart.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC - * SPDX-FileCopyrightText: 2026 Coffey Labs LLC - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL - * - * Modified by Coffey Labs in 2026 for INBUXA. - */ - -import * as React from 'react'; -import { Tooltip as RechartsTooltip } from 'recharts'; -import type { TooltipPayload } from 'recharts/types/state/tooltipSlice'; -import { cn } from '@/lib/utils'; - -export const CHART_COLORS = [ - 'var(--chart-1)', - 'var(--chart-2)', - 'var(--chart-3)', - 'var(--chart-4)', - 'var(--chart-5)', -] as const; - -export function getChartColor(index: number): string { - return CHART_COLORS[index % CHART_COLORS.length]; -} - -export function ChartContainer({ children, className }: { children: React.ReactNode; className?: string }) { - return
{children}
; -} - -export function ChartTooltipContent({ - active, - payload, - label, - formatter, -}: { - active?: boolean; - payload?: TooltipPayload; - label?: string; - formatter?: (value: number) => string; -}) { - if (!active || !payload?.length) return null; - - return ( -
- {label &&

{label}

} - {payload.map((entry, i) => ( -
- - {entry.name ?? ''} - - {formatter && typeof entry.value === 'number' ? formatter(entry.value) : String(entry.value ?? '')} - -
- ))} -
- ); -} - -export { RechartsTooltip }; diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx deleted file mode 100644 index d5731b4..0000000 --- a/src/components/ui/scroll-area.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC - * - * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL - */ - -import * as React from 'react'; -import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'; - -import { cn } from '@/lib/utils'; - -const ScrollArea = React.forwardRef< - React.ComponentRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - {children} - - - -)); -ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; - -const ScrollBar = React.forwardRef< - React.ComponentRef, - React.ComponentPropsWithoutRef ->(({ className, orientation = 'vertical', ...props }, ref) => ( - - - -)); -ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; - -export { ScrollArea, ScrollBar }; diff --git a/tools/divergence.mjs b/tools/divergence.mjs new file mode 100644 index 0000000..b71e419 --- /dev/null +++ b/tools/divergence.mjs @@ -0,0 +1,77 @@ +#!/usr/bin/env node +// SPDX-FileCopyrightText: 2026 Coffey Labs LLC +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * inbuxa: how much of the console is still upstream's, measured against + * BASE, the upstream release this fork started from. The console no longer + * takes upstream changes, and is being rewritten until none of upstream's + * code is left (inbuxa-drafts/specs/console-rewrite-plan.md); each PR of that + * work states how far it moved these numbers. + * + * node tools/divergence.mjs the summary, for HEAD + * node tools/divergence.mjs the summary, for another commit + * node tools/divergence.mjs --files also each file with upstream lines left + * + * Lines are counted from git's diff, without package-lock.json or binary + * files. "Not upstream's" counts every line upstream didn't write: ours, and + * third-party code such as components taken from shadcn/ui. + */ +import { execFileSync } from 'node:child_process'; + +// Upstream's web interface v1.0.11, the release this fork started from. +const BASE = process.env.DIVERGENCE_BASE || 'dc462b137fbee9c2193946a6db554ff20bcc9b07'; +const EXCLUDE = [':!package-lock.json']; +const DUAL_HEADER = '^\\s*(\\*|//|#)\\s*SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL'; + +const args = process.argv.slice(2); +const showFiles = args.includes('--files'); +const ref = args.find((a) => !a.startsWith('--')) ?? 'HEAD'; + +const git = (...a) => execFileSync('git', a, { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }); +const empty = git('hash-object', '-t', 'tree', '/dev/null').trim(); + +/** Per-file [added, deleted] between two trees, binary files left out. */ +function numstat(from, to) { + const out = new Map(); + for (const line of git('diff', '--numstat', '--no-renames', from, to, '--', '.', ...EXCLUDE).split('\n')) { + const [a, d, path] = line.split('\t'); + if (!path || a === '-') continue; + out.set(path, [Number(a), Number(d)]); + } + return out; +} + +const sum = (m, i) => [...m.values()].reduce((n, v) => n + v[i], 0); + +const baseFiles = numstat(empty, BASE); +const changes = numstat(BASE, ref); +const forkLines = sum(numstat(empty, ref), 0); +const upstreamLines = sum(baseFiles, 0); +const notUpstream = sum(changes, 0); + +const left = []; +for (const [path, [lines]] of baseFiles) { + const n = lines - (changes.get(path)?.[1] ?? 0); + if (n > 0) left.push([n, path]); +} +left.sort((a, b) => b[0] - a[0] || a[1].localeCompare(b[1])); +const upstreamLeft = left.reduce((n, [l]) => n + l, 0); + +let dual = 0; +try { + dual = git('grep', '-l', '-E', DUAL_HEADER, ref, '--').split('\n').filter(Boolean).length; +} catch { + // git grep exits 1 when nothing matches +} + +const pct = (a, b) => `${((100 * a) / b).toFixed(1)}%`; +const n = (x) => x.toLocaleString('en-US'); +console.log(`Against upstream v1.0.11 (${BASE.slice(0, 7)}), at ${git('rev-parse', '--short', ref).trim()}:`); +console.log(` not upstream's: ${pct(notUpstream, forkLines)} (${n(notUpstream)} of ${n(forkLines)} lines)`); +console.log(` upstream lines left: ${n(upstreamLeft)} of ${n(upstreamLines)}, in ${n(left.length)} files`); +console.log(` dual-header files: ${n(dual)}`); +if (showFiles) { + console.log(''); + for (const [l, path] of left) console.log(`${String(l).padStart(6)} ${path}`); +} -- 2.54.0 From 6d62474929ae169874ecdd8ab3c7cfe85aec0ef3 Mon Sep 17 00:00:00 2001 From: John Coffey Date: Sun, 18 Oct 2026 16:30:34 -0700 Subject: [PATCH 2/2] Count a file as rewritten once upstream's header is gone The header only changes when no upstream code is left in a file, but a rewrite still shares blank lines, imports, brackets and shadcn/ui class lists with upstream's copy, so a plain line diff never reaches zero. A file that named Stalwart Labs at the base and no longer does now counts as ours, and the summary prints how many lines those files still share, so the claim can be checked. --- tools/divergence.mjs | 49 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/tools/divergence.mjs b/tools/divergence.mjs index b71e419..3da975d 100644 --- a/tools/divergence.mjs +++ b/tools/divergence.mjs @@ -14,8 +14,15 @@ * node tools/divergence.mjs --files also each file with upstream lines left * * Lines are counted from git's diff, without package-lock.json or binary - * files. "Not upstream's" counts every line upstream didn't write: ours, and - * third-party code such as components taken from shadcn/ui. + * files. "Not upstream's" is every line but upstream's: ours, and third-party + * code such as components taken from shadcn/ui. + * + * A file whose header named Stalwart Labs at BASE and no longer does has been + * rewritten, and none of its lines count as upstream's: the header only + * changes once the rewrite is done. A rewrite still shares some lines with + * upstream's file (blank lines, imports, closing brackets, and class lists + * both took from shadcn/ui), so the summary prints how many, to keep the + * claim checkable. */ import { execFileSync } from 'node:child_process'; @@ -23,6 +30,7 @@ import { execFileSync } from 'node:child_process'; const BASE = process.env.DIVERGENCE_BASE || 'dc462b137fbee9c2193946a6db554ff20bcc9b07'; const EXCLUDE = [':!package-lock.json']; const DUAL_HEADER = '^\\s*(\\*|//|#)\\s*SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL'; +const UPSTREAM_HOLDER = 'SPDX-FileCopyrightText:.*Stalwart Labs'; const args = process.argv.slice(2); const showFiles = args.includes('--files'); @@ -44,26 +52,44 @@ function numstat(from, to) { const sum = (m, i) => [...m.values()].reduce((n, v) => n + v[i], 0); +/** Files at `at` whose lines match `pattern`, as a set of paths. */ +function grepFiles(pattern, at) { + try { + return new Set( + git('grep', '-l', '-E', pattern, at, '--') + .split('\n') + .filter(Boolean) + .map((l) => l.slice(at.length + 1)), + ); + } catch { + // git grep exits 1 when nothing matches + return new Set(); + } +} + const baseFiles = numstat(empty, BASE); const changes = numstat(BASE, ref); const forkLines = sum(numstat(empty, ref), 0); const upstreamLines = sum(baseFiles, 0); -const notUpstream = sum(changes, 0); +const headed = grepFiles(UPSTREAM_HOLDER, BASE); +const stillUpstream = grepFiles(UPSTREAM_HOLDER, ref); const left = []; +let rewritten = 0; +let shared = 0; for (const [path, [lines]] of baseFiles) { const n = lines - (changes.get(path)?.[1] ?? 0); - if (n > 0) left.push([n, path]); + if (n <= 0) continue; + if (!headed.has(path) || stillUpstream.has(path)) left.push([n, path]); + else { + rewritten += 1; + shared += n; + } } left.sort((a, b) => b[0] - a[0] || a[1].localeCompare(b[1])); const upstreamLeft = left.reduce((n, [l]) => n + l, 0); - -let dual = 0; -try { - dual = git('grep', '-l', '-E', DUAL_HEADER, ref, '--').split('\n').filter(Boolean).length; -} catch { - // git grep exits 1 when nothing matches -} +const notUpstream = forkLines - upstreamLeft; +const dual = grepFiles(DUAL_HEADER, ref).size; const pct = (a, b) => `${((100 * a) / b).toFixed(1)}%`; const n = (x) => x.toLocaleString('en-US'); @@ -71,6 +97,7 @@ console.log(`Against upstream v1.0.11 (${BASE.slice(0, 7)}), at ${git('rev-parse console.log(` not upstream's: ${pct(notUpstream, forkLines)} (${n(notUpstream)} of ${n(forkLines)} lines)`); console.log(` upstream lines left: ${n(upstreamLeft)} of ${n(upstreamLines)}, in ${n(left.length)} files`); console.log(` dual-header files: ${n(dual)}`); +console.log(` rewritten files: ${n(rewritten)}, sharing ${n(shared)} lines with upstream's`); if (showFiles) { console.log(''); for (const [l, path] of left) console.log(`${String(l).padStart(6)} ${path}`); -- 2.54.0