From 18987c1a839870da1fdacea6e7c3a8e82baebbea Mon Sep 17 00:00:00 2001 From: omsherikar Date: Fri, 15 May 2026 18:29:48 +0530 Subject: [PATCH 01/10] =?UTF-8?q?feat(research):=20add=20comparison=20pape?= =?UTF-8?q?r=20#02=20=E2=80=94=20Refactron=20vs=20the=20codemod=20baseline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New /research/comparison-01 page: the head-to-head benchmark against jscodeshift, Comby, ESLint --fix, and LibCST on var->const/let and format->f-string. Speed, coverage, safety on identical inputs. - ResearchComparison01Page component, same paper-style layout as perf-01 - research index: paper 02 flipped from planned to live - route, prerender list, and sitemap updated --- scripts/generate-sitemap.js | 4 +- scripts/prerender.js | 2 + src/App.tsx | 20 + src/components/ResearchComparison01Page.tsx | 771 ++++++++++++++++++++ src/components/ResearchPage.tsx | 479 ++++++++---- 5 files changed, 1148 insertions(+), 128 deletions(-) create mode 100644 src/components/ResearchComparison01Page.tsx diff --git a/scripts/generate-sitemap.js b/scripts/generate-sitemap.js index 8e11baf..5b90d03 100644 --- a/scripts/generate-sitemap.js +++ b/scripts/generate-sitemap.js @@ -19,7 +19,9 @@ const staticRoutes = [ { url: '/about', changefreq: 'monthly', priority: 0.6 }, { url: '/changelog', changefreq: 'weekly', priority: 0.7 }, { url: '/security', changefreq: 'monthly', priority: 0.5 }, - { url: '/research', changefreq: 'monthly', priority: 0.45 }, + { url: '/research', changefreq: 'monthly', priority: 0.55 }, + { url: '/research/perf-01', changefreq: 'yearly', priority: 0.5 }, + { url: '/research/comparison-01', changefreq: 'yearly', priority: 0.5 }, { url: '/privacy-policy', changefreq: 'yearly', priority: 0.3 }, { url: '/terms-of-service',changefreq: 'yearly', priority: 0.3 }, // blog posts added dynamically below diff --git a/scripts/prerender.js b/scripts/prerender.js index 4834909..43c9531 100644 --- a/scripts/prerender.js +++ b/scripts/prerender.js @@ -32,6 +32,8 @@ const PAGES = [ '/changelog', '/security', '/research', + '/research/perf-01', + '/research/comparison-01', '/privacy-policy', '/terms-of-service', ...blogSlugs.map(slug => `/blog/${slug}`), diff --git a/src/App.tsx b/src/App.tsx index c69690b..2472bc6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -26,12 +26,15 @@ import AuthApp from './components/AuthApp'; import NotFoundPage from './components/NotFoundPage'; import ErrorBoundary from './components/ErrorBoundary'; import SkipToMain from './components/SkipToMain'; +import ScrollToTop from './components/ScrollToTop'; import usePerformanceMonitoring from './hooks/usePerformanceMonitoring'; import useAccessibility from './hooks/useAccessibility'; import PageLayout from './components/PageLayout'; import Changelog from './components/Changelog'; import SecurityPage from './components/SecurityPage'; import ResearchPage from './components/ResearchPage'; +import ResearchPerf01Page from './components/ResearchPerf01Page'; +import ResearchComparison01Page from './components/ResearchComparison01Page'; import StatusPage from './components/StatusPage'; import { ThemeProvider } from './contexts/ThemeContext'; @@ -91,6 +94,7 @@ function App() { + {isDocsHost ? ( } /> @@ -161,6 +165,22 @@ function App() { } /> + + + + } + /> + + + + } + /> } /> { + useSEO({ + title: 'Refactron vs the codemod baseline · A head-to-head study | Research', + description: + 'Refactron benchmarked against jscodeshift, Comby, ESLint --fix, and LibCST on var → const/let and format → f-string. Speed, coverage, and safety on identical inputs. Reproducible.', + canonical: 'https://refactron.dev/research/comparison-01', + robots: 'index, follow', + }); + + return ( +
+ + + {/* ───────────────── HERO ───────────────── */} +
+
+ +
+

Research · Comparison report 02

+ +

+ 2026-05-15 +

+
+ +

+ Refactron vs the{' '} + codemod baseline. A + head-to-head. +

+ +

+ Two transforms, four other tools, identical inputs. We measured + speed, coverage, and safety against the existing + deterministic-codemod technology. The result is mixed in exactly + the way an honest benchmark should be. +

+
+ + {/* Metadata strip */} + + + + Om Sherikar + + + + Refactron + + + 2026-05-15 + + + 5 compared + + + Apple M2 + + + + bench/comparison ↗ + + + +
+
+ + {/* ───────────────── 00 · ABSTRACT ───────────────── */} +
+
+ +

00 · Abstract

+

+ Refactron is the slowest tool we measured. It is also the only + one that is top-coverage on both transforms while never producing + a single unsafe rewrite. The two pure codemod tools that ship no + verification step run sub-second and write code that does not + compile. +

+

+ That tension is the paper. Speed without verification bought + broken code in every cell where it was measured. +

+
+
+
+ + {/* ───────────────── 01 · WHY ───────────────── */} +
+
+ +

01 · Why this study

+

+ The engineering baseline, not the competitors. +

+

+ jscodeshift and LibCST are codemod frameworks — you + author codemods with them. Comby is a structural search/replace + DSL. ESLint --fix is a linter's autofix. None of + them is the product a team weighs Refactron against. +

+

+ But they are the existing technology that performs deterministic + source-to-source transformation — exactly what Refactron's engine + does. A new approach earns credibility by being measured against + the established one on identical inputs. This is "transform + + verify versus transform only," not "our product versus theirs." +

+
+
+
+ + {/* ───────────────── 02 · SETUP ───────────────── */} +
+
+ +

02 · Setup

+

+ Identical inputs, three axes. +

+ +
+ + + +
+ +
+
+

+ Speed +

+

+ Wall-clock for the whole invocation, process startup + included. What a user actually waits for. Median of five. +

+
+
+

+ Coverage +

+

+ Per-site exact classification — correct, missed, wrong, + broken — located by stable anchor, not line proximity. +

+
+
+

+ Safety +

+

+ tsc --noEmit / py_compile plus the + fixture's own test suite, run against the tool's output. +

+
+
+
+
+
+ + {/* ───────────────── 03 · RESULTS ───────────────── */} +
+
+ +

03 · Results

+

+ Coverage and safety move together. +

+ +

+ var → const/let +

+

+ TypeScript · 126 planted sites · jscodeshift, Comby, ESLint + applicable +

+ + + Table 1.{' '} + Refactron and ESLint convert every site correctly and safely. The + pure codemod tools emit const on reassigned bindings + — dozens of wrong rewrites, output that fails to compile. + + +
+ +

+ format → f-string +

+

+ Python · 108 planted sites · Comby, LibCST applicable +

+ + + Table 2.{' '} + Refactron converts 107 of 108 (the miss is a nested{' '} + .format()). LibCST is safe but only handles plain{' '} + %s. Comby emits invalid Python at 74 sites. + + +
+
+ + {/* ───────────────── 04 · THE SPLIT ───────────────── */} +
+
+ +

04 · The split

+

+ Careful tools are safe. Unguarded tools are fast and broken. +

+
+
+

+ Careful · safe output +

+

+ Refactron — verification + gate. ESLint — narrow, + well-tuned ruleset. LibCST{' '} + — conservative codemod. +

+

+ Every output compiles and passes tests. +

+
+
+

+ Unguarded · broken output +

+

+ jscodeshift and{' '} + Comby — transform with no + verification step. +

+

+ Sub-second, and every cell failed compilation. +

+
+
+

+ No cell in this study was fast, high-coverage, and safe at once. + Speed without verification bought broken code every time it was + measured. +

+
+
+
+ + {/* ───────────────── 05 · DISCUSSION ───────────────── */} +
+
+ +

05 · Discussion

+

+ Where Refactron wins, and where it loses. +

+ +
+
+

+ Wins · coverage + safety +

+

+ Refactron is top-coverage on both transforms — a tie with + ESLint at 100% on var → const/let, an outright + win at 99.1% vs LibCST's 57.4% on{' '} + format → f-string — and never emits an unsafe + rewrite. No other tool here is top-coverage on both. +

+
+
+

+ Loses · speed +

+

+ Refactron is the slowest tool measured — ~8× slower than + ESLint on var → const/let. This is not an + optimization gap to apologize for; it is the pipeline. ESLint + applies a fix and stops. Refactron applies a transform, + re-parses every changed file, resolves every import, runs the + full test suite on a shadow tree, then writes atomically. The + 5.22s figure is that pipeline. The benchmark's safety + column shows what skipping it costs. +

+
+
+ +

+ The honest claim is not "Refactron is fastest." It is: Refactron + is the only tool measured here that never wrote broken code — and + that guarantee has a price denominated in seconds. +

+
+
+
+ + {/* ───────────────── 06 · LIMITATIONS ───────────────── */} +
+
+ +

06 · What this doesn't claim

+

+ The honest fine print. +

+
+ + These are frameworks and linters, not Refactron's commercial + alternatives. Cursor, SonarQube, and the LLM tools belong in a + separate, categorical study. + + + Refactron ships ten. These two were chosen for tool overlap, + not because they are the hardest cases. + + + Ten files per transform with planted patterns. Real codebases + are messier. The fixtures are published so the methodology can + be challenged. + + + We authored them and committed them for audit. If a jscodeshift + expert shows us a better visitor, we rerun and republish. + +
+
+
+
+ + {/* ───────────────── 07 · HOW IT WAS BUILT ───────────────── */} +
+
+ +

07 · How this benchmark was built

+

+ It embarrassed us first. +

+

+ The first run reported Refactron at 27% coverage on{' '} + var → const/let. Investigation found two real bugs in + Refactron's own transform — a scope-unaware reference scan and a + missed AST node kind. They were fixed (27% → 100%) before + publication. The benchmark also caught a precision flaw in its own + checker that was miscounting every tool; that was corrected too. + A benchmark you publish should be one that has already embarrassed + you in private. +

+ +
+
+
+ + {/* ───────────────── REFERENCES / FOOTER ───────────────── */} +
+
+

References & resources

+
    + + Comparison bench — fixtures, per-tool codemods, harness, raw + results:{' '} + + bench/comparison + + . + + + Refactron 0.2.0 performance report —{' '} + + paper #01 + + . + + + Instagram / LibCST — ConvertFormatStringCommand, the + reference Python format codemod benchmarked here. + + + The var_to_const_let scope-correctness fix and the + printf-grammar percent converter:{' '} + + PR #27 + + . + +
+ +
+ + ← All research + + + Documentation + + + Source ↗ + +
+
+
+
+ ); +}; + +/* ═════════════════ Sub-components ═════════════════════════════════ */ + +const StaticBeams: React.FC = () => ( +
+); + +const Mono: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + + {children} + +); + +const Caption: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +

+ {children} +

+); + +const Meta: React.FC<{ label: string; children: React.ReactNode }> = ({ + label, + children, +}) => ( +
+

+ {label} +

+

{children}

+
+); + +const FactTile: React.FC<{ label: string; value: string; sub: string }> = ({ + label, + value, + sub, +}) => ( +
+

+ {label} +

+

+ {value} +

+

{sub}

+
+); + +const Limitation: React.FC<{ title: string; children: React.ReactNode }> = ({ + title, + children, +}) => ( +
+

+ {title} +

+

{children}

+
+); + +const Ref: React.FC<{ n: string; children: React.ReactNode }> = ({ + n, + children, +}) => ( +
  • + [{n}] + {children} +
  • +); + +const ExtLink: React.FC<{ href: string; children: React.ReactNode }> = ({ + href, + children, +}) => ( + + {children} + +); + +const CodeBlock: React.FC<{ caption: string; code: string }> = ({ + caption, + code, +}) => ( +
    +
    +

    + {caption} +

    +
    + + + +
    +
    +
    +      {code}
    +    
    +
    +); + +/* ═════════════════ Visual: Result table ══════════════════════════ */ + +interface ResultRow { + tool: string; + speed: number; + coverage: number; + detail: string; + wrong: number; + safe: boolean; + brokenNote?: string; + highlight?: boolean; +} + +const ResultTable: React.FC<{ rows: ResultRow[]; speedCap: number }> = ({ + rows, + speedCap, +}) => ( +
    + {/* Header */} +
    + Tool + Speed + Coverage + Wrong + Safety +
    + {rows.map((r, i) => ( +
    + {/* Tool */} + + {r.tool} + + + {/* Speed — bar + value */} +
    +

    + {r.speed.toFixed(2)}s +

    +
    +
    +
    +
    + + {/* Coverage — bar + value */} +
    +

    + = 99 ? 'text-emerald-400/90' : 'text-neutral-300' + } + > + {r.coverage.toFixed(1)}% + {' '} + {r.detail} +

    +
    +
    = 99 ? 'bg-emerald-400/55' : 'bg-white/25' + }`} + style={{ width: `${r.coverage}%` }} + /> +
    +
    + + {/* Wrong */} + 0 ? 'text-rose-400/90' : 'text-neutral-500' + }`} + > + {r.wrong > 0 ? r.wrong : '0'} + {r.brokenNote && ( + + {r.brokenNote} + + )} + + + {/* Safety */} + + + {r.safe ? 'safe' : 'fail'} + + +
    + ))} +
    +); + +export default ResearchComparison01Page; diff --git a/src/components/ResearchPage.tsx b/src/components/ResearchPage.tsx index 3f5fd08..3af98bb 100644 --- a/src/components/ResearchPage.tsx +++ b/src/components/ResearchPage.tsx @@ -1,152 +1,377 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { motion } from 'framer-motion'; -import { Lock, Sparkles } from 'lucide-react'; import useSEO from '../hooks/useSEO'; +/* ─── Tokens shared with ResearchPerf01Page ──────────────────────── */ + +const eyebrow = + 'text-[10px] font-mono uppercase tracking-[0.28em] text-neutral-500'; + +const fadeUp = { + initial: { opacity: 0, y: 18 }, + whileInView: { opacity: 1, y: 0 }, + viewport: { once: true, margin: '-80px' }, + transition: { duration: 0.55 }, +}; + +/* ─── Paper index ────────────────────────────────────────────────── */ + +type PaperStatus = 'live' | 'planned'; + +interface Paper { + no: string; + status: PaperStatus; + date: string; + title: string; + abstract: string; + href?: string; + external?: boolean; +} + +const PAPERS: Paper[] = [ + { + no: '01', + status: 'live', + date: '2026-05-15', + title: 'Refactron 0.2.0. A measured look at deterministic refactoring at scale.', + abstract: + 'Wall-clock benchmarks for analyze, plan, and the 3-gate verifier on synthetic and real Python fixtures. 45% faster on 100k LOC vs the 0.1 baseline. All scripts and raw runs in the public repo.', + href: '/research/perf-01', + }, + { + no: '02', + status: 'live', + date: '2026-05-15', + title: + 'Refactron vs the codemod baseline. A head-to-head on var → const/let and format → f-string.', + abstract: + 'Two transforms, measured against jscodeshift, Comby, ESLint --fix, and LibCST on identical inputs across speed, coverage, and safety. The unverified codemod tools run sub-second and write code that does not compile; Refactron is the slowest tool measured and the only one that is top-coverage on both transforms while never unsafe.', + href: '/research/comparison-01', + }, + { + no: '03', + status: 'planned', + date: 'Target 2026-06', + title: + 'Legacy patterns in the wild. An empirical survey of the top 100 PyPI packages.', + abstract: + 'How prevalent are the patterns Refactron transforms target? Which packages would benefit most from a deterministic refactoring pass? Distribution by transform, by package age, and by test coverage.', + }, + { + no: '04', + status: 'planned', + date: 'Target 2026-06', + title: + 'Cross-file preconditions for callback_to_async_await. A method paper.', + abstract: + 'Why this transform is the hardest of the ten and how the precondition set is constructed. Walks through the call-graph, the safety constraints, and the cases the transform deliberately refuses.', + }, +]; + +/* ─────────────────────────────────────────────────────────────── */ + const ResearchPage: React.FC = () => { useSEO({ title: 'Research | Refactron', description: - 'Refactron’s internal research on deterministic refactoring and verification is private today. Public notes, benchmarks, and write-ups are coming soon.', + "Refactron's research stream. Performance reports, comparison studies, and method papers on deterministic refactoring with verification-first guarantees.", canonical: 'https://refactron.dev/research', robots: 'index, follow', }); return ( -
    -
    - -

    - Research -

    -

    - We are doing serious work here. - - Not everything is ready to share yet. - -

    -

    - Our work on safe, deterministic refactoring, including verification - design, transform pipelines, and how we benchmark against real - codebases, is handled as{' '} - internal research for now. - Not everything belongs in a landing page; we'll publish what we - can, when it's ready. -

    -
    - -
    - -
    -
    - +
    + + +
    + + {/* ── Hero ───────────────────────────────────────────────── */} +
    +
    + +

    Research

    + +

    + What we measure,{' '} + + why we measure it, what we found. + +

    + +

    + Refactron's thesis is that refactoring belongs to deterministic + tools with formal safety guarantees, not to text generators. + These papers are how we hold that claim to scrutiny: published + benchmarks, published methodology, published source. +

    + + {/* Counts strip */} +
    + p.status === 'live').length.toString()} + /> + p.status === 'planned' + ).length.toString()} + /> +
    -

    - Why it stays private -

    -
    -
      -
    • - - Maps, datasets, and methodology drafts evolve quickly; we avoid - publishing half-finished claims. -
    • -
    • - - Some comparisons touch third-party products; we respect - accurate, sourced framing before putting anything on the record. -
    • -
    • - - Customer and partner contexts may fall under confidentiality. We - ship the product first, then share what we can responsibly. -
    • -
    - - - -
    -
    - + +
    +
    + + {/* ── Papers list ────────────────────────────────────────── */} +
    +
    +
    +
    +

    Papers

    +
    +
    +

    Papers

    +
      + {PAPERS.map((p, i) => ( + + ))} +
    -

    - What we'll reveal -

    -
      -
    • - - Deeper notes on verification, structural refactor scope, and how - we think about legacy detection versus lint-only tooling. -
    • -
    • - - Benchmarks and reproducible summaries where we can stand behind - the numbers. -
    • -
    • - - Updates will land here and on{' '} - - Changelog - {' '} - when they go public, with no treasure hunt required. -
    • -
    - -
    +
    + - - - ← Back to home - - · - +
    + +

    About this stream

    +
    +

    + Every Refactron research piece commits to three rules. +

    +
      + + Every published number ships with the script that produced + it. If you can't reproduce a claim from{' '} + + bench/ + + , it shouldn't be in the paper. + + + Methodology is described in detail upfront. No cherry-picked + runs, no proprietary inputs, no mystery hardware. + + + Each paper has a Discussion section listing what it does + not measure. We'd rather ship a narrow paper with sharp + edges than a broad one with caveats hidden in the small + print. + +
    +
    +
    +
    + + + {/* ── Footer / nav ───────────────────────────────────────── */} +
    + +
    +
    + + ); +}; + +/* ═════════════════ Sub-components ═════════════════════════════════ */ + +const DotGridBackdrop: React.FC = () => ( +
    +); + +const Stat: React.FC<{ label: string; value: string }> = ({ label, value }) => ( +
    +

    + {label} +

    +

    + {value} +

    +
    +); + +const Rule: React.FC<{ title: string; children: React.ReactNode }> = ({ + title, + children, +}) => ( +
  • + + {title} + + {children} +
  • +); + +const ExtMono: React.FC<{ href: string; children: React.ReactNode }> = ({ + href, + children, +}) => ( + + {children} + +); + +const PaperRow: React.FC<{ paper: Paper; delay: number }> = ({ + paper, + delay, +}) => { + const live = paper.status === 'live'; + + const inner = ( + + {/* Number column */} +
    +

    + {paper.no} +

    +
    + + {/* Body */} +
    +
    +

    + {paper.date} +

    + - Comparison - - · - +
    +

    + {paper.title} +

    +

    + {paper.abstract} +

    +
    + + {/* Arrow column */} +
    + {live ? ( + - Documentation - - + + + + + ) : ( + + soon + + )}
    -
    + ); + + if (live && paper.href) { + return ( + + {inner} + + ); + } + return inner; }; export default ResearchPage; From 31ca1439700525f78e7a08995219a465c6b0ecbb Mon Sep 17 00:00:00 2001 From: omsherikar Date: Fri, 15 May 2026 18:39:20 +0530 Subject: [PATCH 02/10] feat(research): add coverage + speed-vs-coverage charts to comparison paper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two SVG figures in the comparison paper: - Figure 1: grouped coverage bar chart, bar colour encodes safety - Figure 2: speed-vs-coverage scatter with a 'safe band' highlight — visually shows safe results cluster high, unsafe below 50% --- src/components/ResearchComparison01Page.tsx | 384 +++++++++++++++++++- 1 file changed, 383 insertions(+), 1 deletion(-) diff --git a/src/components/ResearchComparison01Page.tsx b/src/components/ResearchComparison01Page.tsx index 0871dcf..58bd3b1 100644 --- a/src/components/ResearchComparison01Page.tsx +++ b/src/components/ResearchComparison01Page.tsx @@ -219,10 +219,20 @@ const ResearchComparison01Page: React.FC = () => {

    03 · Results

    -

    +

    Coverage and safety move together.

    + {/* Coverage chart */} + + + Figure 1.{' '} + Correct-rewrite coverage per tool. Bar colour encodes safety — + green bars compiled and passed tests, red bars did not. + + +
    +

    var → const/let

    @@ -332,6 +342,19 @@ const ResearchComparison01Page: React.FC = () => {

    Careful tools are safe. Unguarded tools are fast and broken.

    + + {/* Speed vs coverage scatter */} + + + Figure 2.{' '} + Every measured cell, speed against coverage. Safe results (green) + sit in a high-coverage band; unsafe results (red) sit below 50%. + Refactron's points are the slowest — and the only ones both safe + and above 99%. + + +
    +

    @@ -768,4 +791,363 @@ const ResultTable: React.FC<{ rows: ResultRow[]; speedCap: number }> = ({

    ); +/* ═════════════════ Visual: Coverage bar chart ════════════════════ */ + +const MONO = 'ui-monospace, SFMono-Regular, monospace'; +const EMERALD = 'rgba(74, 222, 128, 0.85)'; +const EMERALD_DIM = 'rgba(74, 222, 128, 0.12)'; +const ROSE = 'rgba(244, 63, 94, 0.8)'; +const ROSE_DIM = 'rgba(244, 63, 94, 0.1)'; + +interface CovBar { + tool: string; + pct: number; + safe: boolean; +} + +const COV_GROUPS: { label: string; bars: CovBar[] }[] = [ + { + label: 'var → const/let', + bars: [ + { tool: 'Refactron', pct: 100, safe: true }, + { tool: 'ESLint', pct: 100, safe: true }, + { tool: 'jscodeshift', pct: 46.0, safe: false }, + { tool: 'Comby', pct: 47.6, safe: false }, + ], + }, + { + label: 'format → f-string', + bars: [ + { tool: 'Refactron', pct: 99.1, safe: true }, + { tool: 'LibCST', pct: 57.4, safe: true }, + { tool: 'Comby', pct: 15.7, safe: false }, + ], + }, +]; + +const CoverageChart: React.FC = () => { + const W = 760; + const H = 400; + const padL = 46; + const padR = 24; + const padT = 28; + const plotBottom = 308; + const plotTop = padT; + const plotH = plotBottom - plotTop; + const plotW = W - padL - padR; + const groupW = plotW / COV_GROUPS.length; + const yFor = (p: number) => plotBottom - (p / 100) * plotH; + + return ( +
    + + + + + + + + + + + + + {/* Gridlines + y labels */} + {[0, 25, 50, 75, 100].map((t) => ( + + + + {t}% + + + ))} + + {/* Group divider */} + + + {COV_GROUPS.map((group, gi) => { + const groupStart = padL + gi * groupW; + const sidePad = 30; + const gap = 14; + const n = group.bars.length; + const innerW = groupW - 2 * sidePad; + const barW = (innerW - gap * (n - 1)) / n; + return ( + + {group.bars.map((b, bi) => { + const x = groupStart + sidePad + bi * (barW + gap); + const top = yFor(b.pct); + return ( + + + {/* value */} + + {b.pct % 1 === 0 ? b.pct : b.pct.toFixed(1)}% + + {/* tool label, rotated */} + + {b.tool} + + + ); + })} + {/* group label */} + + {group.label.toUpperCase()} + + + ); + })} + +
    + ); +}; + +/* ═════════════════ Visual: Speed-vs-coverage scatter ══════════════ */ + +interface ScatterPt { + label: string; + speed: number; + coverage: number; + safe: boolean; + lx: number; + ly: number; + anchor: 'start' | 'middle' | 'end'; +} + +const SCATTER_PTS: ScatterPt[] = [ + { label: 'Refactron · var', speed: 5.22, coverage: 100, safe: true, lx: 0, ly: -15, anchor: 'middle' }, + { label: 'ESLint · var', speed: 0.65, coverage: 100, safe: true, lx: 14, ly: 4, anchor: 'start' }, + { label: 'jscodeshift · var', speed: 0.67, coverage: 46.0, safe: false, lx: 14, ly: -7, anchor: 'start' }, + { label: 'Comby · var', speed: 0.29, coverage: 47.6, safe: false, lx: 14, ly: 14, anchor: 'start' }, + { label: 'Refactron · fmt', speed: 3.76, coverage: 99.1, safe: true, lx: 0, ly: -15, anchor: 'middle' }, + { label: 'LibCST · fmt', speed: 2.68, coverage: 57.4, safe: true, lx: 14, ly: 4, anchor: 'start' }, + { label: 'Comby · fmt', speed: 4.79, coverage: 15.7, safe: false, lx: -14, ly: 4, anchor: 'end' }, +]; + +const ScatterChart: React.FC = () => { + const W = 760; + const H = 440; + const padL = 52; + const padR = 40; + const padT = 30; + const padB = 56; + const plotW = W - padL - padR; + const plotH = H - padT - padB; + const xMax = 5.6; + const xFor = (s: number) => padL + (s / xMax) * plotW; + const yFor = (c: number) => padT + (1 - c / 100) * plotH; + + return ( +
    + + {/* "safe band" highlight above 55% */} + + + ALL SAFE RESULTS LAND HERE + + + {/* Y gridlines + labels */} + {[0, 25, 50, 75, 100].map((t) => ( + + + + {t}% + + + ))} + + {/* X ticks + labels */} + {[0, 1, 2, 3, 4, 5].map((s) => ( + + + + {s}s + + + ))} + + {/* Axis titles */} + + SPEED — WALL-CLOCK SECONDS + + + COVERAGE + + + {/* Points */} + {SCATTER_PTS.map((p) => { + const cx = xFor(p.speed); + const cy = yFor(p.coverage); + const color = p.safe ? EMERALD : ROSE; + return ( + + + + + {p.label} + + + ); + })} + + + {/* Legend */} +
    + + + safe — compiles + tests pass + + + + unsafe — failed compilation + +
    +
    + ); +}; + export default ResearchComparison01Page; From eb8292f78484c5e7ddbc34ee9c0eff8a557eecf4 Mon Sep 17 00:00:00 2001 From: omsherikar Date: Fri, 15 May 2026 18:52:45 +0530 Subject: [PATCH 03/10] feat(research): rebuild comparison paper with academic-paper layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructured after the supermemory research-page references: - sticky CONTENTS sidebar with scroll-spy active-section highlight - full-width benchmark band (compact scorecard) below the hero - two-column paper body (TOC + content) - authors box, figure panels with captions - bracketed [n] citations with inline superscript links + references Fix: dropped overflow-x-hidden from the article — it turned the article into a scroll container and broke position:sticky on the TOC. --- src/components/ResearchComparison01Page.tsx | 1147 ++++++++++--------- 1 file changed, 600 insertions(+), 547 deletions(-) diff --git a/src/components/ResearchComparison01Page.tsx b/src/components/ResearchComparison01Page.tsx index 58bd3b1..c234a02 100644 --- a/src/components/ResearchComparison01Page.tsx +++ b/src/components/ResearchComparison01Page.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { motion } from 'framer-motion'; import useSEO from '../hooks/useSEO'; @@ -8,12 +8,46 @@ import useSEO from '../hooks/useSEO'; const eyebrow = 'text-[10px] font-mono uppercase tracking-[0.28em] text-neutral-500'; -const fadeUp = { - initial: { opacity: 0, y: 18 }, - whileInView: { opacity: 1, y: 0 }, - viewport: { once: true, margin: '-80px' }, - transition: { duration: 0.55 }, -}; +const MONO = 'ui-monospace, SFMono-Regular, monospace'; +const EMERALD = 'rgba(74, 222, 128, 0.85)'; +const EMERALD_DIM = 'rgba(74, 222, 128, 0.12)'; +const ROSE = 'rgba(244, 63, 94, 0.8)'; +const ROSE_DIM = 'rgba(244, 63, 94, 0.1)'; + +/* ─── Table of contents ─────────────────────────────────────────── */ + +const TOC: { id: string; label: string }[] = [ + { id: 'abstract', label: 'Abstract' }, + { id: 'why', label: '01 · Why this study' }, + { id: 'setup', label: '02 · Setup' }, + { id: 'results', label: '03 · Results' }, + { id: 'split', label: '04 · The split' }, + { id: 'discussion', label: '05 · Discussion' }, + { id: 'limitations', label: '06 · Limitations' }, + { id: 'how-built', label: '07 · How it was built' }, + { id: 'references', label: 'References' }, +]; + +function useActiveSection(ids: string[]): string { + const [active, setActive] = useState(ids[0] ?? ''); + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + const visible = entries + .filter((e) => e.isIntersecting) + .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top); + if (visible[0]) setActive(visible[0].target.id); + }, + { rootMargin: '-15% 0px -75% 0px' }, + ); + ids.forEach((id) => { + const el = document.getElementById(id); + if (el) observer.observe(el); + }); + return () => observer.disconnect(); + }, [ids]); + return active; +} /* ─────────────────────────────────────────────────────────────── */ @@ -26,19 +60,21 @@ const ResearchComparison01Page: React.FC = () => { robots: 'index, follow', }); + const active = useActiveSection(TOC.map((t) => t.id)); + return (
    - + - {/* ───────────────── HERO ───────────────── */} + {/* ═══════════════ HERO (full-width) ═══════════════ */}
    -
    +
    { 2026-05-15

    -

    Refactron vs the{' '} codemod baseline. A head-to-head.

    -

    Two transforms, four other tools, identical inputs. We measured speed, coverage, and safety against the existing @@ -65,512 +99,394 @@ const ResearchComparison01Page: React.FC = () => { the way an honest benchmark should be.

    - - {/* Metadata strip */} - - - - Om Sherikar - - - - Refactron - - - 2026-05-15 - - - 5 compared - - - Apple M2 - - - - bench/comparison ↗ - - -
    - {/* ───────────────── 00 · ABSTRACT ───────────────── */} -
    -
    - -

    00 · Abstract

    -

    - Refactron is the slowest tool we measured. It is also the only - one that is top-coverage on both transforms while never producing - a single unsafe rewrite. The two pure codemod tools that ship no - verification step run sub-second and write code that does not - compile. -

    -

    - That tension is the paper. Speed without verification bought - broken code in every cell where it was measured. -

    -
    -
    -
    - - {/* ───────────────── 01 · WHY ───────────────── */} -
    -
    - -

    01 · Why this study

    -

    - The engineering baseline, not the competitors. -

    -

    - jscodeshift and LibCST are codemod frameworks — you - author codemods with them. Comby is a structural search/replace - DSL. ESLint --fix is a linter's autofix. None of - them is the product a team weighs Refactron against. -

    -

    - But they are the existing technology that performs deterministic - source-to-source transformation — exactly what Refactron's engine - does. A new approach earns credibility by being measured against - the established one on identical inputs. This is "transform + - verify versus transform only," not "our product versus theirs." -

    -
    + {/* ═══════════════ BENCHMARK BAND (full-width feature strip) ═══════════════ */} +
    +
    +

    + Coverage benchmark ·{' '} + var → const/let &{' '} + format → f-string · 5 + tools · identical inputs +

    +
    - {/* ───────────────── 02 · SETUP ───────────────── */} -
    -
    - -

    02 · Setup

    -

    - Identical inputs, three axes. -

    - -
    - - - + {/* ═══════════════ TWO-COLUMN: TOC + PAPER BODY ═══════════════ */} +
    +
    + {/* ── Sticky TOC ── */} + + + {/* ── Paper body ── */} +
    + {/* Authors box */} + + + {/* 00 · Abstract */} + + 00 · Abstract +

    + Refactron is the slowest tool we measured. It is also the only + one that is top-coverage on both transforms while never + producing a single unsafe rewrite. The two pure codemod tools + that ship no verification step run sub-second and write code + that does not compile. +

    +

    + That tension is the paper. Speed without verification bought + broken code in every cell where it was measured. The benchmark + builds on the deterministic-refactoring tradition + — behaviour preservation checked, not assumed. +

    +
    + + {/* 01 · Why */} + + 01 · Why this study +

    The engineering baseline, not the competitors.

    +

    + jscodeshift and LibCST are codemod frameworks — you + author codemods with them. Comby is a structural search/replace + DSL. ESLint --fix is a linter's autofix. None of + them is the product a team weighs Refactron against. +

    +

    + But they are the existing technology that performs + deterministic source-to-source transformation — exactly what + Refactron's engine does. A new approach earns credibility by + being measured against the established one on identical inputs. + This is "transform + verify versus transform only," not "our + product versus theirs." +

    +
    + + {/* 02 · Setup */} + + 02 · Setup +

    Identical inputs, three axes.

    +
    + + +
    -
    -

    - Safety -

    -

    - tsc --noEmit / py_compile plus the - fixture's own test suite, run against the tool's output. -

    +

    + Each tool runs the equivalent codemod, authored the way a + competent engineer would and committed to the repo for audit. + LibCST uses Instagram's reference{' '} + ConvertFormatStringCommand + ; ESLint runs its stock{' '} + prefer-const + no-var rules. +

    +
    + {[ + { + t: 'Speed', + d: "Wall-clock for the whole invocation, process startup included. What a user actually waits for.", + }, + { + t: 'Coverage', + d: 'Per-site exact classification — correct, missed, wrong, broken — by stable anchor, not line proximity.', + }, + { + t: 'Safety', + d: "tsc --noEmit / py_compile plus the fixture's own test suite, run against the tool's output.", + }, + ].map((x) => ( +
    +

    + {x.t} +

    +

    {x.d}

    +
    + ))}
    -
    - -
    -
    - - {/* ───────────────── 03 · RESULTS ───────────────── */} -
    -
    - -

    03 · Results

    -

    - Coverage and safety move together. -

    - - {/* Coverage chart */} - - - Figure 1.{' '} - Correct-rewrite coverage per tool. Bar colour encodes safety — - green bars compiled and passed tests, red bars did not. - - -
    - -

    - var → const/let -

    -

    - TypeScript · 126 planted sites · jscodeshift, Comby, ESLint - applicable -

    - - - Table 1.{' '} - Refactron and ESLint convert every site correctly and safely. The - pure codemod tools emit const on reassigned bindings - — dozens of wrong rewrites, output that fails to compile. - - -
    - -

    - format → f-string -

    -

    - Python · 108 planted sites · Comby, LibCST applicable -

    - - - Table 2.{' '} - Refactron converts 107 of 108 (the miss is a nested{' '} - .format()). LibCST is safe but only handles plain{' '} - %s. Comby emits invalid Python at 74 sites. - - -
    -
    - - {/* ───────────────── 04 · THE SPLIT ───────────────── */} -
    -
    - -

    04 · The split

    -

    - Careful tools are safe. Unguarded tools are fast and broken. -

    - - {/* Speed vs coverage scatter */} - - - Figure 2.{' '} - Every measured cell, speed against coverage. Safe results (green) - sit in a high-coverage band; unsafe results (red) sit below 50%. - Refactron's points are the slowest — and the only ones both safe - and above 99%. - - -
    + + + {/* 03 · Results */} + + 03 · Results +

    Coverage and safety move together.

    +
    + Figure 1. Correct-rewrite coverage per tool. Bar + colour encodes safety — green compiled and passed tests, + red did not. + + } + > + +
    + +

    + var → const/let +

    +

    + TypeScript · 126 planted sites +

    + -
    -
    -

    - Careful · safe output -

    -

    - Refactron — verification - gate. ESLint — narrow, - well-tuned ruleset. LibCST{' '} - — conservative codemod. -

    -

    - Every output compiles and passes tests. -

    -
    -
    -

    - Unguarded · broken output -

    -

    - jscodeshift and{' '} - Comby — transform with no - verification step. -

    -

    - Sub-second, and every cell failed compilation. -

    +

    + format → f-string +

    +

    + Python · 108 planted sites +

    + + + Table 1. Median of five runs. Refactron and ESLint + convert every site correctly and safely; the pure codemod tools + emit dozens of wrong rewrites that fail to compile. + + + + {/* 04 · The split */} + + 04 · The split +

    Careful tools are safe. Unguarded tools are fast and broken.

    +
    + Figure 2. Every measured cell, speed against + coverage. Safe results (green) sit in a high-coverage band; + unsafe results (red) sit below 50%. + + } + > + +
    +
    +
    +

    + Careful · safe output +

    +

    + Refactron (verification + gate), ESLint (narrow + ruleset), LibCST{' '} + (conservative codemod). Every output compiles and passes + tests. +

    +
    +
    +

    + Unguarded · broken output +

    +

    + jscodeshift and{' '} + Comby transform with no + verification step. Sub-second, and every cell failed + compilation. +

    +
    -
    -

    - No cell in this study was fast, high-coverage, and safe at once. - Speed without verification bought broken code every time it was - measured. -

    - -
    -
    - - {/* ───────────────── 05 · DISCUSSION ───────────────── */} -
    -
    - -

    05 · Discussion

    -

    - Where Refactron wins, and where it loses. -

    - -
    -
    -

    - Wins · coverage + safety -

    -

    +

    + No cell in this study was fast, high-coverage, and safe at once. + Speed without verification bought broken code every time. +

    + + + {/* 05 · Discussion */} + + 05 · Discussion +

    Where Refactron wins, and where it loses.

    +
    + Refactron is top-coverage on both transforms — a tie with ESLint at 100% on var → const/let, an outright win at 99.1% vs LibCST's 57.4% on{' '} format → f-string — and never emits an unsafe rewrite. No other tool here is top-coverage on both. -

    -
    -
    -

    - Loses · speed -

    -

    + + Refactron is the slowest tool measured — ~8× slower than ESLint on var → const/let. This is not an - optimization gap to apologize for; it is the pipeline. ESLint - applies a fix and stops. Refactron applies a transform, - re-parses every changed file, resolves every import, runs the - full test suite on a shadow tree, then writes atomically. The - 5.22s figure is that pipeline. The benchmark's safety - column shows what skipping it costs. -

    + optimization gap to apologize for; it is the pipeline. + Refactron applies a transform, re-parses every changed file, + resolves every import, runs the full test suite on a shadow + tree, then writes atomically. The 5.22s figure is{' '} + that pipeline. +
    -
    - -

    - The honest claim is not "Refactron is fastest." It is: Refactron - is the only tool measured here that never wrote broken code — and - that guarantee has a price denominated in seconds. -

    - -
    -
    - - {/* ───────────────── 06 · LIMITATIONS ───────────────── */} -
    -
    - -

    06 · What this doesn't claim

    -

    - The honest fine print. -

    -
    - - These are frameworks and linters, not Refactron's commercial - alternatives. Cursor, SonarQube, and the LLM tools belong in a - separate, categorical study. - - - Refactron ships ten. These two were chosen for tool overlap, - not because they are the hardest cases. - - - Ten files per transform with planted patterns. Real codebases - are messier. The fixtures are published so the methodology can - be challenged. - - - We authored them and committed them for audit. If a jscodeshift - expert shows us a better visitor, we rerun and republish. - -
    -
    -
    -
    - - {/* ───────────────── 07 · HOW IT WAS BUILT ───────────────── */} -
    -
    - -

    07 · How this benchmark was built

    -

    - It embarrassed us first. -

    -

    - The first run reported Refactron at 27% coverage on{' '} - var → const/let. Investigation found two real bugs in - Refactron's own transform — a scope-unaware reference scan and a - missed AST node kind. They were fixed (27% → 100%) before - publication. The benchmark also caught a precision flaw in its own - checker that was miscounting every tool; that was corrected too. - A benchmark you publish should be one that has already embarrassed - you in private. -

    - + The honest claim is not "Refactron is fastest." It is: Refactron + is the only tool measured here that never wrote broken code — + and that guarantee has a price denominated in seconds. +

    + + + {/* 06 · Limitations */} + + 06 · Limitations +

    The honest fine print.

    +
    + + These are frameworks and linters, not Refactron's commercial + alternatives. Cursor, SonarQube, and the LLM tools belong in a + separate, categorical study. + + + Refactron ships ten. These two were chosen for tool overlap, + not because they are the hardest cases. + + + Ten files per transform with planted patterns. Real codebases + are messier. The fixtures are published so the methodology can + be challenged. + + + We authored them and committed them for audit. If an expert + shows us a better visitor, we rerun and republish. + +
    +
    + + {/* 07 · How it was built */} + + 07 · How this benchmark was built +

    It embarrassed us first.

    +

    + The first run reported Refactron at 27% coverage on{' '} + var → const/let. Investigation found two real bugs + in Refactron's own transform — a scope-unaware reference scan + and a missed AST node kind. They were fixed (27% → 100%) before + publication + . The benchmark also caught a precision flaw in + its own checker that was miscounting every tool; that was + corrected too. A benchmark you publish should be one that has + already embarrassed you in private. +

    +
    + - -
    -
    - - {/* ───────────────── REFERENCES / FOOTER ───────────────── */} -
    -
    -

    References & resources

    -
      - - Comparison bench — fixtures, per-tool codemods, harness, raw - results:{' '} - - bench/comparison - - . - - - Refactron 0.2.0 performance report —{' '} - - paper #01 - - . - - - Instagram / LibCST — ConvertFormatStringCommand, the - reference Python format codemod benchmarked here. - - - The var_to_const_let scope-correctness fix and the - printf-grammar percent converter:{' '} - - PR #27 - - . - -
    - -
    - - ← All research - - - Documentation - - - Source ↗ - + /> +
    + + + {/* References */} + + References +
      + + Comparison bench — fixtures, per-tool codemods, harness, raw + results.{' '} + + github.com/Refactron-ai · bench/comparison + + + + Instagram / LibCST. ConvertFormatStringCommand — the + reference Python format codemod benchmarked here. + + + Opdyke, W. F. (1992). Refactoring Object-Oriented + Frameworks. PhD thesis, University of Illinois + Urbana-Champaign — the precondition-checking foundation + behaviour-preserving refactoring rests on. + + + The var_to_const_let scope-correctness fix and the + printf-grammar percent converter.{' '} + + Refactron_Lib_TS · PR #27 + + + + Refactron 0.2.0 performance report —{' '} + + research paper #01 + + . + +
    + +
    + + ← All research + + + Documentation + + + Source ↗ + +
    +
    -
    +
    ); }; -/* ═════════════════ Sub-components ═════════════════════════════════ */ +/* ═════════════════ Layout primitives ═════════════════════════════ */ -const StaticBeams: React.FC = () => ( +const DotGrid: React.FC = () => (
    ( /> ); +const Block: React.FC<{ + id: string; + last?: boolean; + children: React.ReactNode; +}> = ({ id, last, children }) => ( + + {children} + +); + +const Kicker: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +

    {children}

    +); + +const H2: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +

    + {children} +

    +); + +const P: React.FC<{ children: React.ReactNode; className?: string }> = ({ + children, + className = '', +}) => ( +

    + {children} +

    +); + const Mono: React.FC<{ children: React.ReactNode }> = ({ children }) => ( - + {children} ); +const Cite: React.FC<{ n: string }> = ({ n }) => ( + + [{n}] + +); + const Caption: React.FC<{ children: React.ReactNode }> = ({ children }) => ( -

    +

    {children}

    ); -const Meta: React.FC<{ label: string; children: React.ReactNode }> = ({ - label, - children, -}) => ( -
    -

    - {label} +const Figure: React.FC<{ + caption: React.ReactNode; + children: React.ReactNode; +}> = ({ caption, children }) => ( +

    + {children} +
    + {caption} +
    +
    +); + +const AuthorsBox: React.FC = () => ( +
    +

    + Authors

    -

    {children}

    +
    +
    + + Om Sherikar ↗ + +

    Founder, Refactron

    +
    +
    +
    +

    + Published +

    +

    2026-05-15

    +
    +
    +

    + Hardware +

    +

    Apple M2

    +
    +
    +
    ); @@ -626,11 +624,23 @@ const FactTile: React.FC<{ label: string; value: string; sub: string }> = ({
    ); +const Panel: React.FC<{ title: string; children: React.ReactNode }> = ({ + title, + children, +}) => ( +
    +

    + {title} +

    +

    {children}

    +
    +); + const Limitation: React.FC<{ title: string; children: React.ReactNode }> = ({ title, children, }) => ( -
    +

    {title}

    @@ -638,12 +648,14 @@ const Limitation: React.FC<{ title: string; children: React.ReactNode }> = ({
    ); -const Ref: React.FC<{ n: string; children: React.ReactNode }> = ({ +const RefItem: React.FC<{ n: string; children: React.ReactNode }> = ({ n, children, }) => ( -
  • - [{n}] +
  • + + [{n}] + {children}
  • ); @@ -656,7 +668,7 @@ const ExtLink: React.FC<{ href: string; children: React.ReactNode }> = ({ href={href} target="_blank" rel="noopener noreferrer" - className="text-neutral-400 hover:text-neutral-200 underline underline-offset-2 decoration-neutral-700 hover:decoration-neutral-400 transition-colors" + className="text-neutral-300 hover:text-white underline underline-offset-2 decoration-neutral-700 hover:decoration-neutral-400 transition-colors" > {children} @@ -666,7 +678,7 @@ const CodeBlock: React.FC<{ caption: string; code: string }> = ({ caption, code, }) => ( -
    +

    {caption} @@ -683,7 +695,83 @@ const CodeBlock: React.FC<{ caption: string; code: string }> = ({

    ); -/* ═════════════════ Visual: Result table ══════════════════════════ */ +/* ═════════════════ Benchmark band — compact scorecard ════════════ */ + +const BAND_ROWS: { + transform: string; + tool: string; + coverage: number; + safe: boolean; + lead?: boolean; +}[] = [ + { transform: 'var → const/let', tool: 'Refactron', coverage: 100, safe: true, lead: true }, + { transform: 'var → const/let', tool: 'ESLint --fix', coverage: 100, safe: true }, + { transform: 'var → const/let', tool: 'jscodeshift', coverage: 46.0, safe: false }, + { transform: 'var → const/let', tool: 'Comby', coverage: 47.6, safe: false }, + { transform: 'format → f-string', tool: 'Refactron', coverage: 99.1, safe: true, lead: true }, + { transform: 'format → f-string', tool: 'LibCST', coverage: 57.4, safe: true }, + { transform: 'format → f-string', tool: 'Comby', coverage: 15.7, safe: false }, +]; + +const ScoreBand: React.FC = () => ( +
    +
    + Transform + Tool + Coverage + Safe +
    + {BAND_ROWS.map((r, i) => { + const firstOfGroup = i === 0 || BAND_ROWS[i - 1].transform !== r.transform; + return ( +
    + + {firstOfGroup ? r.transform : ''} + + + {r.tool} + +
    +
    +
    +
    + + {r.coverage % 1 === 0 ? r.coverage : r.coverage.toFixed(1)}% + +
    + + + {r.safe ? '✓' : '✗'} + + +
    + ); + })} +
    +); + +/* ═════════════════ Result table ══════════════════════════════════ */ interface ResultRow { tool: string; @@ -701,8 +789,7 @@ const ResultTable: React.FC<{ rows: ResultRow[]; speedCap: number }> = ({ speedCap, }) => (
    - {/* Header */} -
    +
    Tool Speed Coverage @@ -712,11 +799,10 @@ const ResultTable: React.FC<{ rows: ResultRow[]; speedCap: number }> = ({ {rows.map((r, i) => (
    - {/* Tool */} = ({ > {r.tool} - - {/* Speed — bar + value */}

    {r.speed.toFixed(2)}s @@ -737,8 +821,6 @@ const ResultTable: React.FC<{ rows: ResultRow[]; speedCap: number }> = ({ />

    - - {/* Coverage — bar + value */}

    = ({ />

    - - {/* Wrong */} 0 ? 'text-rose-400/90' : 'text-neutral-500' @@ -773,11 +853,9 @@ const ResultTable: React.FC<{ rows: ResultRow[]; speedCap: number }> = ({ )} - - {/* Safety */} = ({ /* ═════════════════ Visual: Coverage bar chart ════════════════════ */ -const MONO = 'ui-monospace, SFMono-Regular, monospace'; -const EMERALD = 'rgba(74, 222, 128, 0.85)'; -const EMERALD_DIM = 'rgba(74, 222, 128, 0.12)'; -const ROSE = 'rgba(244, 63, 94, 0.8)'; -const ROSE_DIM = 'rgba(244, 63, 94, 0.1)'; - interface CovBar { tool: string; pct: number; @@ -855,8 +927,6 @@ const CoverageChart: React.FC = () => { - - {/* Gridlines + y labels */} {[0, 25, 50, 75, 100].map((t) => ( { ))} - - {/* Group divider */} { strokeWidth="1" strokeDasharray="3 4" /> - {COV_GROUPS.map((group, gi) => { const groupStart = padL + gi * groupW; const sidePad = 30; @@ -916,7 +983,6 @@ const CoverageChart: React.FC = () => { strokeOpacity={0.4} strokeWidth="1" /> - {/* value */} { > {b.pct % 1 === 0 ? b.pct : b.pct.toFixed(1)}% - {/* tool label, rotated */} { ); })} - {/* group label */} { className="w-full h-auto" preserveAspectRatio="xMidYMid meet" > - {/* "safe band" highlight above 55% */} { > ALL SAFE RESULTS LAND HERE - - {/* Y gridlines + labels */} {[0, 25, 50, 75, 100].map((t) => ( { ))} - - {/* X ticks + labels */} {[0, 1, 2, 3, 4, 5].map((s) => ( { ))} - - {/* Axis titles */} { > COVERAGE - - {/* Points */} {SCATTER_PTS.map((p) => { const cx = xFor(p.speed); const cy = yFor(p.coverage); @@ -1128,9 +1183,7 @@ const ScatterChart: React.FC = () => { ); })} - - {/* Legend */} -
    +
    Date: Fri, 15 May 2026 18:59:13 +0530 Subject: [PATCH 04/10] fix(research): de-collide scatter chart labels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - jscodeshift/Comby (both bottom-left, close together) now label in opposite directions — the higher point up, the lower point down - both Refactron points label below — above-the-point clipped the plot top and collided with the band caption - band caption moved from the top-right corner (where Refactron·var sat) to the band's empty middle --- src/components/ResearchComparison01Page.tsx | 26 +++++++++++++-------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/components/ResearchComparison01Page.tsx b/src/components/ResearchComparison01Page.tsx index c234a02..9ffe0fd 100644 --- a/src/components/ResearchComparison01Page.tsx +++ b/src/components/ResearchComparison01Page.tsx @@ -1040,11 +1040,15 @@ interface ScatterPt { } const SCATTER_PTS: ScatterPt[] = [ - { label: 'Refactron · var', speed: 5.22, coverage: 100, safe: true, lx: 0, ly: -15, anchor: 'middle' }, + // Both Refactron points label BELOW — above-the-point would clip the plot + // top and collide with the band caption. + { label: 'Refactron · var', speed: 5.22, coverage: 100, safe: true, lx: 0, ly: 24, anchor: 'middle' }, + { label: 'Refactron · fmt', speed: 3.76, coverage: 99.1, safe: true, lx: 0, ly: 24, anchor: 'middle' }, { label: 'ESLint · var', speed: 0.65, coverage: 100, safe: true, lx: 14, ly: 4, anchor: 'start' }, - { label: 'jscodeshift · var', speed: 0.67, coverage: 46.0, safe: false, lx: 14, ly: -7, anchor: 'start' }, - { label: 'Comby · var', speed: 0.29, coverage: 47.6, safe: false, lx: 14, ly: 14, anchor: 'start' }, - { label: 'Refactron · fmt', speed: 3.76, coverage: 99.1, safe: true, lx: 0, ly: -15, anchor: 'middle' }, + // The two bottom-left points sit close together — separate their labels + // vertically: the higher point (Comby) labels up, the lower (jscodeshift) down. + { label: 'Comby · var', speed: 0.29, coverage: 47.6, safe: false, lx: 13, ly: -12, anchor: 'start' }, + { label: 'jscodeshift · var', speed: 0.67, coverage: 46.0, safe: false, lx: 13, ly: 20, anchor: 'start' }, { label: 'LibCST · fmt', speed: 2.68, coverage: 57.4, safe: true, lx: 14, ly: 4, anchor: 'start' }, { label: 'Comby · fmt', speed: 4.79, coverage: 15.7, safe: false, lx: -14, ly: 4, anchor: 'end' }, ]; @@ -1076,16 +1080,18 @@ const ScatterChart: React.FC = () => { height={yFor(55) - yFor(100)} fill="rgba(74,222,128,0.035)" /> + {/* Band caption sits in the band's empty middle so it never collides + with a data point or its label. */} - ALL SAFE RESULTS LAND HERE + ALL SAFE RESULTS LAND IN THIS BAND {[0, 25, 50, 75, 100].map((t) => ( From 9aed128f56bc36c3f0f50aeedaa422c388842ae9 Mon Sep 17 00:00:00 2001 From: omsherikar Date: Fri, 15 May 2026 19:06:41 +0530 Subject: [PATCH 05/10] feat(research): align perf-01 with the comparison-paper layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructured the performance report to match comparison-01: - sticky CONTENTS sidebar with scroll-spy - full-width benchmark band (compact analyze scorecard) below the hero - two-column paper body (TOC + content) - authors box, figure panels with captions, bracketed [n] citations - dropped the stale 'coming next' section — paper #02 is now live and is linked from the discussion + references instead All charts (v0.1-vs-v0.2 dot plot, pipeline, 3-gate diagram) preserved. --- src/components/ResearchPerf01Page.tsx | 1253 +++++++++++++++++++++++++ 1 file changed, 1253 insertions(+) create mode 100644 src/components/ResearchPerf01Page.tsx diff --git a/src/components/ResearchPerf01Page.tsx b/src/components/ResearchPerf01Page.tsx new file mode 100644 index 0000000..a0ca43a --- /dev/null +++ b/src/components/ResearchPerf01Page.tsx @@ -0,0 +1,1253 @@ +import React, { useEffect, useState } from 'react'; +import { Link } from 'react-router-dom'; +import { motion } from 'framer-motion'; +import useSEO from '../hooks/useSEO'; + +/* ─── Shared tokens (match ResearchComparison01Page) ────────────── */ + +const eyebrow = + 'text-[10px] font-mono uppercase tracking-[0.28em] text-neutral-500'; + +const MONO = 'ui-monospace, SFMono-Regular, monospace'; + +/* ─── Table of contents ─────────────────────────────────────────── */ + +const TOC: { id: string; label: string }[] = [ + { id: 'abstract', label: 'Abstract' }, + { id: 'headline', label: '01 · Headline result' }, + { id: 'methodology', label: '02 · Methodology' }, + { id: 'results', label: '03 · Results — synthetic' }, + { id: 'pipeline', label: '04 · The full pipeline' }, + { id: 'apply', label: '05 · The apply step' }, + { id: 'discussion', label: '06 · Discussion' }, + { id: 'reproduce', label: '07 · Reproducibility' }, + { id: 'references', label: 'References' }, +]; + +function useActiveSection(ids: string[]): string { + const [active, setActive] = useState(ids[0] ?? ''); + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + const visible = entries + .filter((e) => e.isIntersecting) + .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top); + if (visible[0]) setActive(visible[0].target.id); + }, + { rootMargin: '-15% 0px -75% 0px' }, + ); + ids.forEach((id) => { + const el = document.getElementById(id); + if (el) observer.observe(el); + }); + return () => observer.disconnect(); + }, [ids]); + return active; +} + +/* ─────────────────────────────────────────────────────────────── */ + +const ResearchPerf01Page: React.FC = () => { + useSEO({ + title: 'Refactron 0.2.0 · A Performance Report | Research', + description: + 'Wall-clock benchmarks for Refactron 0.2.0: analyze, plan, and the 3-gate verifier on synthetic and real Python fixtures. Reproducible scripts in the repo.', + canonical: 'https://refactron.dev/research/perf-01', + robots: 'index, follow', + }); + + const active = useActiveSection(TOC.map((t) => t.id)); + + return ( +
    + + + {/* ═══════════════ HERO (full-width) ═══════════════ */} +
    +
    + +
    +

    Research · Performance report 01

    + +

    + v0.2.0 · 2026-05-15 +

    +
    +

    + Refactron 0.2.0. A measured look at{' '} + deterministic{' '} + refactoring at scale. +

    +

    + We measured Refactron 0.2.0 on the work users actually do — + analyze a tree, plan a refactor, then verify and apply it. Every + number on this page is a wall-clock measurement. Every script + that produced it lives in the repo. +

    +
    +
    +
    + + {/* ═══════════════ BENCHMARK BAND ═══════════════ */} +
    +
    +

    + Analyze benchmark · v0.1.0-beta.2 →{' '} + v0.2.0 · synthetic + fixtures +

    + +
    +
    + + {/* ═══════════════ TWO-COLUMN: TOC + PAPER BODY ═══════════════ */} +
    +
    + {/* ── Sticky TOC ── */} + + + {/* ── Paper body ── */} +
    + + + {/* 00 · Abstract */} + + 00 · Abstract +

    + Refactron 0.2.0 analyzes a 100k-LOC tree in a median 11.13 + seconds — 45% faster than 0.1.0-beta.2, with run-to-run + variance compressed by 65%. On a real Python project the full + analyze → plan → apply loop, including the 3-gate verifier + running pytest on a shadow tree, completes in roughly five + seconds. +

    +

    + This report measures the cost of safety-first deterministic + refactoring + . Every figure is wall-clock; every harness is + public. +

    +
    + + {/* 01 · Headline */} + + 01 · Headline result +
    + + 45% + + + faster analyze on 100k LOC + +
    +

    + vs 0.1.0-beta.2. Median dropped from 20.58s to 11.13s, and the + long-tail variance compressed by 65% — predictable enough to + drop into a pre-commit hook. +

    +
    + Figure 1. Every measured run on 100k LOC; the + horizontal bar is the median. Both spread and centre + collapse from v0.1 to v0.2. + + } + > + + + +
    +
    + + {/* 02 · Methodology */} + + 02 · Methodology +

    Reproducible by design.

    +

    + Each measurement is the wall-clock real time reported by{' '} + /usr/bin/time -p, captured over five runs after a + single warm-up. We report median, min, and max — never a + single best case. For the apply step, the fixture is freshly + copied per iteration because the command mutates the tree. +

    +
    + + + + +
    +
    + + {/* 03 · Results */} + + 03 · Results — synthetic +

    How fast can we walk a cold tree?

    +

    + Synthetic fixtures generated fresh per run from{' '} + bench/gen-fixture.ts + — mixed Python and TypeScript with every legacy + pattern Refactron's ten transforms target. This isolates the + analyze step. +

    +
    + +
    + + Table 1. Median over five runs. Bars show the spread of + all five measured runs; min and max in the same row. Δ% is the + v0.1 → v0.2 median improvement. + +
    + + {/* 04 · Pipeline */} + + 04 · The full pipeline +

    Real fixture, real test suite.

    +

    + Synthetic numbers isolate the analyze step. Real users run the + whole loop. We measure against{' '} + + python-legacy-mini + + — 9 files, 189 LOC, with a pytest suite that + exercises every function the transforms touch. +

    +
    + Figure 2. The three pipeline stages and their + measured median wall-clock. + + } + > + +
    +
    + + + +
    + + Table 2. Median wall-clock per step, five runs each, + fresh fixture copy per apply. End-to-end:{' '} + + ~5.2s + {' '} + from scan to atomically-written refactor. + +
    + + {/* 05 · Apply step */} + + 05 · Inside the apply step +

    Three gates. All or nothing.

    +

    + Of the 3.38-second apply budget on this fixture, roughly 3s is + the test gate — pytest cold-start dominates at 9 files. On + larger projects the ratio inverts: the test gate becomes bound + by your suite, while plan and verification overhead stay + roughly constant. +

    +
    + Figure 3. Every refactor passes three gates before + any byte is written. Any failure drops to the rejected + state — your tree never changes, so there is nothing to + roll back. + + } + > + +
    +
    + + {/* 06 · Discussion */} + + 06 · Discussion +

    What this report does and doesn't claim.

    +
    + + A fair head-to-head against jscodeshift, Comby, and{' '} + eslint --fix is its own study — now published as{' '} + + research paper #02 + + . + + + Wall-clock only at v0.2. Peak RSS during the 100k LOC analyze + is in the next pass. + + + Apple M2 only. Linux x86 and Windows numbers when the bench + moves into CI. + + + Fixture generation alone takes ~30s and pushes 8 GB. The + bench script supports{' '} + SIZES=500000 bash bench/run-bench.sh; we don't + publish until we can run it with headroom. + +
    +
    + + {/* 07 · Reproducibility */} + + 07 · Reproducibility +

    Run it yourself.

    +

    + Both bench scripts ship in the public repo. No special + hardware, no proprietary fixtures, no telemetry. If your + numbers come out meaningfully different on Apple Silicon, + please open an issue. +

    +
    + + +
    +
    + + {/* References */} + + References +
      + + Synthetic fixture generator and timing harness.{' '} + + bench/gen-fixture.ts + + + + Real Python fixture with pytest suite.{' '} + + fixtures/python-legacy-mini + + + + Opdyke, W. F. (1992). Refactoring Object-Oriented + Frameworks. PhD thesis, University of Illinois + Urbana-Champaign — the foundation behaviour-preserving + refactoring rests on. + + + Per-file parallelization PR — the source of the 45% win.{' '} + + Refactron_Lib_TS · PR #23 + + + + Refactron vs the codemod baseline —{' '} + + research paper #02 + + . + +
    + + +
    +
    +
    +
    +
    + ); +}; + +/* ═════════════════ Layout primitives ═════════════════════════════ */ + +const DotGrid: React.FC = () => ( +
    +); + +const Block: React.FC<{ + id: string; + last?: boolean; + children: React.ReactNode; +}> = ({ id, last, children }) => ( + + {children} + +); + +const Kicker: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +

    {children}

    +); + +const H2: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +

    + {children} +

    +); + +const P: React.FC<{ children: React.ReactNode; className?: string }> = ({ + children, + className = '', +}) => ( +

    + {children} +

    +); + +const Mono: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + + {children} + +); + +const Cite: React.FC<{ n: string }> = ({ n }) => ( + + [{n}] + +); + +const Caption: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +

    + {children} +

    +); + +const Figure: React.FC<{ + caption: React.ReactNode; + children: React.ReactNode; +}> = ({ caption, children }) => ( +
    + {children} +
    + {caption} +
    +
    +); + +/* Wraps a bare SVG chart in the same panel chrome the diagrams use. */ +const ChartFrame: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
    + {children} +
    +); + +const AuthorsBox: React.FC = () => ( +
    +

    + Authors +

    +
    +
    + + Om Sherikar ↗ + +

    Founder, Refactron

    +
    +
    +
    +

    + Published +

    +

    2026-05-15

    +
    +
    +

    + Version +

    +

    refactron 0.2.0

    +
    +
    +
    +
    +); + +const FactTile: React.FC<{ label: string; value: string; sub: string }> = ({ + label, + value, + sub, +}) => ( +
    +

    + {label} +

    +

    {value}

    +

    {sub}

    +
    +); + +const Limitation: React.FC<{ title: string; children: React.ReactNode }> = ({ + title, + children, +}) => ( +
    +

    + {title} +

    +

    {children}

    +
    +); + +const RefItem: React.FC<{ n: string; children: React.ReactNode }> = ({ + n, + children, +}) => ( +
  • + + [{n}] + + {children} +
  • +); + +const ExtLink: React.FC<{ href: string; children: React.ReactNode }> = ({ + href, + children, +}) => ( + + {children} + +); + +const CodeBlock: React.FC<{ caption: string; code: string }> = ({ + caption, + code, +}) => ( +
    +
    +

    + {caption} +

    +
    + + + +
    +
    +
    +      {code}
    +    
    +
    +); + +/* ═════════════════ Benchmark band — compact scorecard ════════════ */ + +const PERF_BAND: { + fixture: string; + files: string; + v01: number; + v02: number; + lead?: boolean; +}[] = [ + { fixture: '10k LOC', files: '448 files', v01: 1.31, v02: 1.21 }, + { + fixture: '100k LOC', + files: '4 465 files', + v01: 20.58, + v02: 11.13, + lead: true, + }, +]; + +const PerfBand: React.FC = () => ( +
    +
    + Fixture + v0.1 + v0.2 + Δ median +
    + {PERF_BAND.map((r, i) => { + const delta = ((r.v01 - r.v02) / r.v01) * 100; + return ( +
    +
    +

    + {r.fixture} +

    +

    {r.files}

    +
    +

    + {r.v01.toFixed(2)}s +

    +

    + {r.v02.toFixed(2)}s +

    +

    5 ? 'text-emerald-400/90' : 'text-neutral-500' + }`} + > + −{delta.toFixed(0)}% +

    +
    + ); + })} +
    +); + +/* ═════════════════ Visual: Comparison Chart (monochrome) ══════════ */ + +const ComparisonChart: React.FC = () => { + const v01Runs = [38.65, 24.66, 20.58, 17.58, 14.99]; + const v02Runs = [13.14, 11.2, 11.13, 10.61, 10.56]; + const v01Median = 20.58; + const v02Median = 11.13; + const maxY = 42; + + const w = 560; + const h = 320; + const padL = 48; + const padR = 16; + const padT = 30; + const padB = 36; + const chartW = w - padL - padR; + const chartH = h - padT - padB; + + const groups = ['v0.1.0-beta.2', 'v0.2.0']; + const groupW = chartW / groups.length; + + const xForRun = (gIdx: number, rIdx: number) => { + const groupStart = padL + gIdx * groupW; + const innerPad = 26; + const inner = groupW - innerPad * 2; + const step = inner / (5 - 1); + return groupStart + innerPad + rIdx * step; + }; + const yFor = (val: number) => padT + chartH - (val / maxY) * chartH; + + const yTicks = [0, 10, 20, 30, 40]; + + return ( + + + + + + + + + + + + + {yTicks.map((t) => ( + + + + {t}s + + + ))} + + + + {v01Runs.map((r, i) => ( + + ))} + + + {v01Median.toFixed(2)}s + + + {v02Runs.map((r, i) => ( + + ))} + + + {v02Median.toFixed(2)}s + + + {groups.map((g, i) => ( + + {g.toUpperCase()} + + ))} + + ); +}; + +/* ═════════════════ Visual: Results Table (monochrome) ═════════════ */ + +interface Row { + label: string; + files: string; + v01: number; + v02: number; + runs: number[]; + cap: number; + highlight?: boolean; +} + +const ResultsTable: React.FC<{ rows: Row[] }> = ({ rows }) => ( +
    +
    + Fixture + v0.1 + v0.2 + Spread (5 runs) + Δ +
    + {rows.map((r, i) => { + const speedup = ((r.v01 - r.v02) / r.v01) * 100; + return ( +
    +
    +

    + {r.label} +

    +

    {r.files}

    +
    +

    + {r.v01.toFixed(2)}s +

    +

    + {r.v02.toFixed(2)}s +

    +
    +
    + {r.runs.map((run, j) => ( +
    + ))} +
    +

    + {Math.min(...r.runs).toFixed(2)}– + {Math.max(...r.runs).toFixed(2)}s +

    +
    +

    5 ? 'text-emerald-400/95' : 'text-neutral-500' + }`} + > + {speedup > 0 ? '−' : '+'} + {Math.abs(speedup).toFixed(0)}% +

    +
    + ); + })} +
    +); + +/* ═════════════════ Visual: Pipeline Hero (monochrome) ═════════════ */ + +const PipelineHero: React.FC = () => ( +
    + + + + + + + + + + + + + + + + {[150, 500, 850].map((x) => ( + + ))} + + {[ + { cx: 150, label: 'ANALYZE', median: '0.16s', accent: false }, + { cx: 500, label: 'PLAN', median: '1.70s', accent: false }, + { cx: 850, label: 'APPLY', median: '3.38s', accent: true }, + ].map((n) => ( + + {n.accent && ( + + )} + + + + {n.label} + + + {n.median} + + + ))} + +
    +); + +/* ═════════════════ Visual: 3-Gate Diagram (monochrome) ════════════ */ + +const ThreeGateDiagram: React.FC = () => ( +
    + + + + + + + + + + + + {[330, 540, 750].map((x) => ( + + + + + ))} + + {[ + { cx: 100, label: 'PLAN', sub: 'RefactorPlan', tone: 'muted' }, + { cx: 330, label: 'GATE 01', sub: 'syntax', tone: 'normal' }, + { cx: 540, label: 'GATE 02', sub: 'imports', tone: 'normal' }, + { cx: 750, label: 'GATE 03', sub: 'tests', tone: 'normal' }, + { cx: 1000, label: 'WRITE', sub: 'atomic', tone: 'accent' }, + ].map((n) => { + const stroke = + n.tone === 'accent' + ? 'rgba(74, 222, 128, 0.85)' + : n.tone === 'muted' + ? 'rgba(255,255,255,0.14)' + : 'rgba(255,255,255,0.28)'; + const labelFill = + n.tone === 'accent' + ? 'rgba(74, 222, 128, 0.95)' + : n.tone === 'muted' + ? 'rgba(255,255,255,0.45)' + : 'rgba(255,255,255,0.7)'; + return ( + + + + {n.label} + + + {n.sub} + + + ); + })} + + + FAIL · tree untouched + + + INPUT + + + PASS · all three + + +
    +); + +const PipelineCard: React.FC<{ + step: string; + label: string; + cmd: string; + median: string; + detail: string; + accent?: boolean; +}> = ({ step, label, cmd, median, detail, accent }) => ( +
    +
    +

    + {step} +

    +

    + {label} +

    +
    +

    + {cmd} +

    +

    + {median} +

    +

    {detail}

    +
    +); + +export default ResearchPerf01Page; From aa3b75f616243df2bda2425da3c2e642f18e4400 Mon Sep 17 00:00:00 2001 From: omsherikar Date: Fri, 15 May 2026 19:08:00 +0530 Subject: [PATCH 06/10] feat: scroll to top on route change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App.tsx already mounts — this adds the component it imports. Resets window scroll on pathname change so navigating between pages (e.g. /research → /research/perf-01) opens at the top instead of inheriting the previous page's offset. Skips when the URL has a #hash. --- src/components/ScrollToTop.tsx | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/components/ScrollToTop.tsx diff --git a/src/components/ScrollToTop.tsx b/src/components/ScrollToTop.tsx new file mode 100644 index 0000000..b114d5d --- /dev/null +++ b/src/components/ScrollToTop.tsx @@ -0,0 +1,28 @@ +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +/** + * Scroll the window to the top whenever the route pathname changes. + * + * React Router does not reset scroll position on navigation by default, + * so links from a deep page (e.g. /research) into another page (e.g. + * /research/perf-01) inherit the scroll offset of the page that was just + * left. Mount this component once inside to fix that. + * + * If the URL contains a `#hash` we leave scroll alone so in-page anchors + * keep working. + */ +export const ScrollToTop: React.FC = () => { + const { pathname, hash } = useLocation(); + + useEffect(() => { + if (hash) return; + // Use 'auto' (instant) — 'smooth' looks broken on long pages because the + // browser scrolls past content before the new page has finished mounting. + window.scrollTo({ top: 0, left: 0, behavior: 'auto' }); + }, [pathname, hash]); + + return null; +}; + +export default ScrollToTop; From 7c5c97cb21e820b6996ceb6876beea0791987219 Mon Sep 17 00:00:00 2001 From: omsherikar Date: Fri, 15 May 2026 19:08:00 +0530 Subject: [PATCH 07/10] fix(about): hero padding, chip card contrast, lifted background - hero: explicit top padding clears the fixed navbar instead of relying on flex-centering into it - hero chip card: corner + on-chip labels lifted out of near-invisible opacity so they read against the page - page background lifted from pure black to the warmer #050506 + radial used across the research pages --- src/components/AboutPage.tsx | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/components/AboutPage.tsx b/src/components/AboutPage.tsx index acfc412..f33e911 100644 --- a/src/components/AboutPage.tsx +++ b/src/components/AboutPage.tsx @@ -722,15 +722,17 @@ const HeroChipModule: React.FC = () => ( .rfn-die-core { animation: rfn-die-pulse 2.6s ease-in-out infinite; } `} - {/* Surrounding mono labels */} -
    + {/* Surrounding mono labels — sit OUTSIDE the chip body in the safe + gutter. Bumped from neutral-600/700 to neutral-400/500 so they + actually read against the page bg instead of disappearing. */} +
    FIG · 01
    -
    +
    DETERMINISTIC · V0.5
    ANALYZE · REFACTOR · VERIFY · DOCUMENT @@ -793,10 +795,12 @@ const HeroChipModule: React.FC = () => (
    - {/* Bottom-left product label */} -
    -
    REFACTRON
    -
    ENGINE
    + {/* Bottom-left product label. The chip die above is bright on hover + of the eye, so labels need real contrast or they get read as part + of the dot grid. */} +
    +
    REFACTRON
    +
    ENGINE
    {/* Bottom-right status LEDs — first one pulses (active) */} @@ -843,13 +847,21 @@ const AboutPage: React.FC = () => { }); return ( -
    +
    {/* ─── Hero ────────────────────────────────────────────────────── */} -
    + {/* Top padding pushes content clear of the fixed navbar (~5rem tall); + bottom padding gives the hero room to breathe before section #2. */} +
    {sectionFades} -
    +
    {
    {/* ─── Contact ─────────────────────────────────────────────── */} -
    +

    From fea8d3b93228ba1a54204299d1abd6ab7586e388 Mon Sep 17 00:00:00 2001 From: omsherikar Date: Fri, 15 May 2026 19:08:00 +0530 Subject: [PATCH 08/10] chore: regenerate sitemap with research routes --- public/sitemap.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/sitemap.xml b/public/sitemap.xml index 1d27c71..96ff2a1 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -1 +1 @@ -https://refactron.dev/weekly1.0https://refactron.dev/blogweekly0.9https://refactron.dev/aboutmonthly0.6https://refactron.dev/changelogweekly0.7https://refactron.dev/securitymonthly0.5https://refactron.dev/researchmonthly0.5https://refactron.dev/privacy-policyyearly0.3https://refactron.dev/terms-of-serviceyearly0.3https://refactron.dev/blog/i-ran-refactron-on-djangos-codebasemonthly0.8https://refactron.dev/blog/refactron-vs-cursor-vs-codeantmonthly0.8https://refactron.dev/blog/why-we-built-verification-engine-firstmonthly0.8https://refactron.dev/blog/legacy-code-ai-refactoringmonthly0.8https://refactron.dev/blog/refactron-on-requests-librarymonthly0.8https://refactron.dev/blog/real-cost-of-not-refactoringmonthly0.8https://refactron.dev/blog/refactron-on-fastapimonthly0.8https://refactron.dev/blog/how-to-safely-refactor-python-code-you-didnt-writemonthly0.8https://refactron.dev/blog/why-refactron-runs-locallymonthly0.8https://refactron.dev/blog/refactron-is-now-a-nodejs-packagemonthly0.8 \ No newline at end of file +https://refactron.dev/weekly1.0https://refactron.dev/blogweekly0.9https://refactron.dev/aboutmonthly0.6https://refactron.dev/changelogweekly0.7https://refactron.dev/securitymonthly0.5https://refactron.dev/researchmonthly0.6https://refactron.dev/research/perf-01yearly0.5https://refactron.dev/research/comparison-01yearly0.5https://refactron.dev/privacy-policyyearly0.3https://refactron.dev/terms-of-serviceyearly0.3https://refactron.dev/blog/i-ran-refactron-on-djangos-codebasemonthly0.8https://refactron.dev/blog/refactron-vs-cursor-vs-codeantmonthly0.8https://refactron.dev/blog/why-we-built-verification-engine-firstmonthly0.8https://refactron.dev/blog/legacy-code-ai-refactoringmonthly0.8https://refactron.dev/blog/refactron-on-requests-librarymonthly0.8https://refactron.dev/blog/real-cost-of-not-refactoringmonthly0.8https://refactron.dev/blog/refactron-on-fastapimonthly0.8https://refactron.dev/blog/how-to-safely-refactor-python-code-you-didnt-writemonthly0.8https://refactron.dev/blog/why-refactron-runs-locallymonthly0.8https://refactron.dev/blog/refactron-is-now-a-nodejs-packagemonthly0.8 \ No newline at end of file From 5d009ca2064c9e9dbc3cff3ae17fd594e01ab1d5 Mon Sep 17 00:00:00 2001 From: omsherikar Date: Fri, 15 May 2026 19:16:09 +0530 Subject: [PATCH 09/10] style(research): prettier-format the research pages --- src/components/ResearchComparison01Page.tsx | 249 ++++++++++++++++---- src/components/ResearchPage.tsx | 19 +- src/components/ResearchPerf01Page.tsx | 90 +++---- 3 files changed, 254 insertions(+), 104 deletions(-) diff --git a/src/components/ResearchComparison01Page.tsx b/src/components/ResearchComparison01Page.tsx index 9ffe0fd..b41c59a 100644 --- a/src/components/ResearchComparison01Page.tsx +++ b/src/components/ResearchComparison01Page.tsx @@ -32,15 +32,15 @@ function useActiveSection(ids: string[]): string { const [active, setActive] = useState(ids[0] ?? ''); useEffect(() => { const observer = new IntersectionObserver( - (entries) => { + entries => { const visible = entries - .filter((e) => e.isIntersecting) + .filter(e => e.isIntersecting) .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top); if (visible[0]) setActive(visible[0].target.id); }, - { rootMargin: '-15% 0px -75% 0px' }, + { rootMargin: '-15% 0px -75% 0px' } ); - ids.forEach((id) => { + ids.forEach(id => { const el = document.getElementById(id); if (el) observer.observe(el); }); @@ -53,14 +53,15 @@ function useActiveSection(ids: string[]): string { const ResearchComparison01Page: React.FC = () => { useSEO({ - title: 'Refactron vs the codemod baseline · A head-to-head study | Research', + title: + 'Refactron vs the codemod baseline · A head-to-head study | Research', description: 'Refactron benchmarked against jscodeshift, Comby, ESLint --fix, and LibCST on var → const/let and format → f-string. Speed, coverage, and safety on identical inputs. Reproducible.', canonical: 'https://refactron.dev/research/comparison-01', robots: 'index, follow', }); - const active = useActiveSection(TOC.map((t) => t.id)); + const active = useActiveSection(TOC.map(t => t.id)); return (

    {

    Contents

    Each tool runs the equivalent codemod, authored the way a competent engineer would and committed to the repo for audit. LibCST uses Instagram's reference{' '} ConvertFormatStringCommand - ; ESLint runs its stock{' '} - prefer-const + no-var rules. + ; ESLint runs its stock prefer-const{' '} + + no-var rules.

    {[ { t: 'Speed', - d: "Wall-clock for the whole invocation, process startup included. What a user actually waits for.", + d: 'Wall-clock for the whole invocation, process startup included. What a user actually waits for.', }, { t: 'Coverage', @@ -222,7 +227,7 @@ const ResearchComparison01Page: React.FC = () => { t: 'Safety', d: "tsc --noEmit / py_compile plus the fixture's own test suite, run against the tool's output.", }, - ].map((x) => ( + ].map(x => (

    {x.t} @@ -241,8 +246,8 @@ const ResearchComparison01Page: React.FC = () => { caption={ <> Figure 1. Correct-rewrite coverage per tool. Bar - colour encodes safety — green compiled and passed tests, - red did not. + colour encodes safety — green compiled and passed tests, red + did not. } > @@ -257,10 +262,39 @@ const ResearchComparison01Page: React.FC = () => {

    @@ -273,9 +307,32 @@ const ResearchComparison01Page: React.FC = () => {

    @@ -289,7 +346,9 @@ const ResearchComparison01Page: React.FC = () => { {/* 04 · The split */} 04 · The split -

    Careful tools are safe. Unguarded tools are fast and broken.

    +

    + Careful tools are safe. Unguarded tools are fast and broken. +

    @@ -428,10 +487,11 @@ bash bench/comparison/harness/run.sh`} reference Python format codemod benchmarked here. - Opdyke, W. F. (1992). Refactoring Object-Oriented - Frameworks. PhD thesis, University of Illinois - Urbana-Champaign — the precondition-checking foundation - behaviour-preserving refactoring rests on. + Opdyke, W. F. (1992).{' '} + Refactoring Object-Oriented Frameworks. PhD thesis, + University of Illinois Urbana-Champaign — the + precondition-checking foundation behaviour-preserving + refactoring rests on. The var_to_const_let scope-correctness fix and the @@ -704,13 +764,45 @@ const BAND_ROWS: { safe: boolean; lead?: boolean; }[] = [ - { transform: 'var → const/let', tool: 'Refactron', coverage: 100, safe: true, lead: true }, - { transform: 'var → const/let', tool: 'ESLint --fix', coverage: 100, safe: true }, - { transform: 'var → const/let', tool: 'jscodeshift', coverage: 46.0, safe: false }, + { + transform: 'var → const/let', + tool: 'Refactron', + coverage: 100, + safe: true, + lead: true, + }, + { + transform: 'var → const/let', + tool: 'ESLint --fix', + coverage: 100, + safe: true, + }, + { + transform: 'var → const/let', + tool: 'jscodeshift', + coverage: 46.0, + safe: false, + }, { transform: 'var → const/let', tool: 'Comby', coverage: 47.6, safe: false }, - { transform: 'format → f-string', tool: 'Refactron', coverage: 99.1, safe: true, lead: true }, - { transform: 'format → f-string', tool: 'LibCST', coverage: 57.4, safe: true }, - { transform: 'format → f-string', tool: 'Comby', coverage: 15.7, safe: false }, + { + transform: 'format → f-string', + tool: 'Refactron', + coverage: 99.1, + safe: true, + lead: true, + }, + { + transform: 'format → f-string', + tool: 'LibCST', + coverage: 57.4, + safe: true, + }, + { + transform: 'format → f-string', + tool: 'Comby', + coverage: 15.7, + safe: false, + }, ]; const ScoreBand: React.FC = () => ( @@ -722,7 +814,8 @@ const ScoreBand: React.FC = () => ( Safe
    {BAND_ROWS.map((r, i) => { - const firstOfGroup = i === 0 || BAND_ROWS[i - 1].transform !== r.transform; + const firstOfGroup = + i === 0 || BAND_ROWS[i - 1].transform !== r.transform; return (
    { - {[0, 25, 50, 75, 100].map((t) => ( + {[0, 25, 50, 75, 100].map(t => ( { @@ -1093,7 +1242,7 @@ const ScatterChart: React.FC = () => { > ALL SAFE RESULTS LAND IN THIS BAND - {[0, 25, 50, 75, 100].map((t) => ( + {[0, 25, 50, 75, 100].map(t => ( { ))} - {[0, 1, 2, 3, 4, 5].map((s) => ( + {[0, 1, 2, 3, 4, 5].map(s => ( { > COVERAGE - {SCATTER_PTS.map((p) => { + {SCATTER_PTS.map(p => { const cx = xFor(p.speed); const cy = yFor(p.coverage); const color = p.safe ? EMERALD : ROSE; diff --git a/src/components/ResearchPage.tsx b/src/components/ResearchPage.tsx index 3af98bb..bf10758 100644 --- a/src/components/ResearchPage.tsx +++ b/src/components/ResearchPage.tsx @@ -34,7 +34,8 @@ const PAPERS: Paper[] = [ no: '01', status: 'live', date: '2026-05-15', - title: 'Refactron 0.2.0. A measured look at deterministic refactoring at scale.', + title: + 'Refactron 0.2.0. A measured look at deterministic refactoring at scale.', abstract: 'Wall-clock benchmarks for analyze, plan, and the 3-gate verifier on synthetic and real Python fixtures. 45% faster on 100k LOC vs the 0.1 baseline. All scripts and raw runs in the public repo.', href: '/research/perf-01', @@ -91,7 +92,6 @@ const ResearchPage: React.FC = () => {
    - {/* ── Hero ───────────────────────────────────────────────── */}
    @@ -120,7 +120,9 @@ const ResearchPage: React.FC = () => {
    p.status === 'live').length.toString()} + value={PAPERS.filter( + p => p.status === 'live' + ).length.toString()} /> { runs, no proprietary inputs, no mystery hardware. - Each paper has a Discussion section listing what it does - not measure. We'd rather ship a narrow paper with sharp - edges than a broad one with caveats hidden in the small - print. + Each paper has a Discussion section listing what it does not + measure. We'd rather ship a narrow paper with sharp edges + than a broad one with caveats hidden in the small print.
    @@ -290,9 +291,7 @@ const PaperRow: React.FC<{ paper: Paper; delay: number }> = ({ viewport={{ once: true, margin: '-60px' }} transition={{ duration: 0.5, delay }} className={`grid grid-cols-[60px_1fr_auto] gap-x-6 sm:gap-x-10 py-9 lg:py-12 ${ - live - ? 'group cursor-pointer' - : 'opacity-75' + live ? 'group cursor-pointer' : 'opacity-75' }`} > {/* Number column */} diff --git a/src/components/ResearchPerf01Page.tsx b/src/components/ResearchPerf01Page.tsx index a0ca43a..5de4ff3 100644 --- a/src/components/ResearchPerf01Page.tsx +++ b/src/components/ResearchPerf01Page.tsx @@ -28,15 +28,15 @@ function useActiveSection(ids: string[]): string { const [active, setActive] = useState(ids[0] ?? ''); useEffect(() => { const observer = new IntersectionObserver( - (entries) => { + entries => { const visible = entries - .filter((e) => e.isIntersecting) + .filter(e => e.isIntersecting) .sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top); if (visible[0]) setActive(visible[0].target.id); }, - { rootMargin: '-15% 0px -75% 0px' }, + { rootMargin: '-15% 0px -75% 0px' } ); - ids.forEach((id) => { + ids.forEach(id => { const el = document.getElementById(id); if (el) observer.observe(el); }); @@ -56,7 +56,7 @@ const ResearchPerf01Page: React.FC = () => { robots: 'index, follow', }); - const active = useActiveSection(TOC.map((t) => t.id)); + const active = useActiveSection(TOC.map(t => t.id)); return (
    {

    We measured Refactron 0.2.0 on the work users actually do — analyze a tree, plan a refactor, then verify and apply it. Every - number on this page is a wall-clock measurement. Every script - that produced it lives in the repo. + number on this page is a wall-clock measurement. Every script that + produced it lives in the repo.

    @@ -118,7 +118,7 @@ const ResearchPerf01Page: React.FC = () => {

    Contents