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..3da975d --- /dev/null +++ b/tools/divergence.mjs @@ -0,0 +1,104 @@ +#!/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" 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'; + +// 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 UPSTREAM_HOLDER = 'SPDX-FileCopyrightText:.*Stalwart Labs'; + +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); + +/** 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 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) 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); +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'); +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)}`); +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}`); +}