From af322791ac3d24a2626863dcb2b1a4beb6605a1b Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Mon, 8 Jun 2026 01:01:39 -0700 Subject: [PATCH] =?UTF-8?q?papers:=20"The=20Hand=20and=20the=20Loop"=20?= =?UTF-8?q?=E2=80=94=20code-style=20analysis=20across=20three=20eras?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A reflective arXiv-format paper reading AC's own git history across the hand-written (2022-23), GPT-grafted (2024-mid25), and agentic-LLM (mid25-26) eras, asking whether the codebase is better off and what's worth preserving from the hand-written style. Built from a 6-agent evidence sweep over commit metadata + per-file inspection at era-boundary revisions; synthesized in the papers VOICE.md voice. - new papers/arxiv-hand-and-loop/ (handloop.tex + references.bib) - registered in cli.mjs PAPER_MAP, audits category, impact 14.5, index desc - metadata.json entry Note: cli.mjs + metadata.json also carry pre-existing uncommitted registration for the in-progress arxiv-nom paper (same file, not mine to strip). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../arxiv-hand-and-loop/ac-paper-layout.sty | 1 + papers/arxiv-hand-and-loop/handloop.tex | 307 ++++++++++++++++++ papers/arxiv-hand-and-loop/references.bib | 36 ++ papers/cli.mjs | 257 +++++++++++---- papers/metadata.json | 10 + 5 files changed, 549 insertions(+), 62 deletions(-) create mode 120000 papers/arxiv-hand-and-loop/ac-paper-layout.sty create mode 100644 papers/arxiv-hand-and-loop/handloop.tex create mode 100644 papers/arxiv-hand-and-loop/references.bib diff --git a/papers/arxiv-hand-and-loop/ac-paper-layout.sty b/papers/arxiv-hand-and-loop/ac-paper-layout.sty new file mode 120000 index 000000000..a6ee47a59 --- /dev/null +++ b/papers/arxiv-hand-and-loop/ac-paper-layout.sty @@ -0,0 +1 @@ +../ac-paper-layout.sty \ No newline at end of file diff --git a/papers/arxiv-hand-and-loop/handloop.tex b/papers/arxiv-hand-and-loop/handloop.tex new file mode 100644 index 000000000..0f343019d --- /dev/null +++ b/papers/arxiv-hand-and-loop/handloop.tex @@ -0,0 +1,307 @@ +% !TEX program = xelatex +\documentclass[10pt,letterpaper,twocolumn]{article} + +% === GEOMETRY === +\usepackage[top=0.75in, bottom=0.75in, left=0.75in, right=0.75in]{geometry} + +% === FONTS === +\usepackage{fontspec} +\usepackage{unicode-math} + +\setmainfont{Latin Modern Roman}[ + Extension=.otf, + UprightFont=lmroman10-regular, + BoldFont=lmroman10-bold, + ItalicFont=lmroman10-italic, + BoldItalicFont=lmroman10-bolditalic, +] +\setsansfont{Latin Modern Sans}[ + Extension=.otf, + UprightFont=lmsans10-regular, + BoldFont=lmsans10-bold, + ItalicFont=lmsans10-oblique, + BoldItalicFont=lmsans10-boldoblique, +] + +\newfontfamily\acbold{ywft-processing-bold}[ + Path=../../system/public/type/webfonts/, + Extension=.ttf +] +\newfontfamily\aclight{ywft-processing-light}[ + Path=../../system/public/type/webfonts/, + Extension=.ttf +] +\setmonofont{Latin Modern Mono}[ + Extension=.otf, + UprightFont=lmmono10-regular, + ItalicFont=lmmono10-italic, + Scale=0.85, +] + +% === PACKAGES === +\usepackage{xcolor} +\usepackage{titlesec} +\usepackage{enumitem} +\usepackage{booktabs} +\usepackage{tabularx} +\usepackage{multicol} +\usepackage{fancyhdr} +\usepackage{hyperref} +\usepackage{xurl} +\usepackage{graphicx} +\graphicspath{{figures/}{../../papers/arxiv-ac/figures/}} +\usepackage{ragged2e} +\usepackage{microtype} +\usepackage{listings} +\usepackage{natbib} +\usepackage[colorspec=0.92]{draftwatermark} + +% === COLORS (AC palette) === +\definecolor{acpink}{RGB}{180,72,135} +\definecolor{acpurple}{RGB}{120,80,180} +\definecolor{acdark}{RGB}{64,56,74} +\definecolor{acgray}{RGB}{119,119,119} +\definecolor{draftcolor}{RGB}{180,72,135} + +% === DRAFT WATERMARK === +\DraftwatermarkOptions{ + text=WORKING DRAFT, + fontsize=3cm, + color=draftcolor!18, + angle=45, + pos={0.5\paperwidth, 0.5\paperheight} +} + +% === HYPERREF === +\hypersetup{ + colorlinks=true, + linkcolor=acpurple, + urlcolor=acpurple, + citecolor=acpurple, + pdfauthor={@jeffrey}, + pdftitle={The Hand and the Loop: Three Eras of My Own Code}, +} + +% === SECTION FORMATTING === +\titleformat{\section} + {\normalfont\bfseries\normalsize\uppercase} + {\thesection.} + {0.5em} + {} +\titlespacing{\section}{0pt}{1.2em}{0.3em} + +\titleformat{\subsection} + {\normalfont\bfseries\small} + {\thesubsection} + {0.5em} + {} +\titlespacing{\subsection}{0pt}{0.8em}{0.2em} + +% === HEADER/FOOTER === +\pagestyle{fancy} +\fancyhf{} +\renewcommand{\headrulewidth}{0pt} +\fancyhead[C]{\footnotesize\color{acpink}\textit{Working Draft --- not for citation}} +\fancyfoot[C]{\footnotesize\thepage} + +% === CUSTOM COMMANDS === +\newcommand{\acdot}{{\color{acpink}.}} +\newcommand{\ac}{\textsc{Aesthetic.Computer}} +% Random caps for Aesthetic.Computer branding +\newcount\acrandtmp +\newcommand{\acrandletter}[2]{% + \acrandtmp=\uniformdeviate 2\relax + \ifnum\acrandtmp=0\relax#1\else#2\fi% +} +\newcommand{\acrandname}{% + \acrandletter{a}{A}\acrandletter{e}{E}\acrandletter{s}{S}\acrandletter{t}{T}% + \acrandletter{h}{H}\acrandletter{e}{E}\acrandletter{t}{T}\acrandletter{i}{I}% + \acrandletter{c}{C}{\color{acpink}.}\acrandletter{c}{C}\acrandletter{o}{O}% + \acrandletter{m}{M}\acrandletter{p}{P}\acrandletter{u}{U}\acrandletter{t}{T}% + \acrandletter{e}{E}\acrandletter{r}{R}% +} + +% === LIST SETTINGS === +\setlist[itemize]{nosep, leftmargin=1.2em, itemsep=0.1em} +\setlist[enumerate]{nosep, leftmargin=1.2em} + +% === COLUMN SEPARATION === +\setlength{\columnsep}{1.8em} + +% === PARAGRAPH SETTINGS === +\setlength{\parindent}{1em} +\setlength{\parskip}{0.3em} + +% Hyphenation for narrow two-column layout +\tolerance=800 +\emergencystretch=1em +\hyphenpenalty=50 + +% === PULL QUOTE === +\newcommand{\pull}[1]{% + \par\vspace{0.4em} + {\aclight\small\color{acpink}\itshape #1}\par + \vspace{0.4em}} + +\begin{document} + +% ============ TITLE BLOCK ============ + +\twocolumn[{% +\begin{center} +\includegraphics[height=4em]{pals}\par\vspace{0.5em} +{\acbold\fontsize{22pt}{26pt}\selectfont\color{acdark} The Hand and the Loop}\par +\vspace{0.2em} +{\aclight\fontsize{11pt}{13pt}\selectfont\color{acpink} Three Eras of My Own Code, and What I Traded the Instrument For}\par +\vspace{0.6em} +{\normalsize\href{https://prompt.ac/@jeffrey}{@jeffrey}}\par +{\small\color{acgray} Aesthetic.Computer}\par +{\small\color{acgray} ORCID: \href{https://orcid.org/0009-0007-4460-4913}{0009-0007-4460-4913}}\par +\vspace{0.3em} +{\small\color{acpurple} \url{https://aesthetic.computer}}\par +\vspace{0.6em} +\rule{\textwidth}{1.5pt} +\vspace{0.5em} +\end{center} + +\begin{center} +{\small\color{acpink}\textbf{[ working draft --- not for citation ]}} +\end{center} +\vspace{0.3em} + +\begin{quote} +\small\noindent\textbf{Abstract.} +I wrote \ac{} by hand for the first year --- 2,766 commits, terse lowercase fragments, single-letter variables that meant something. Then I started grafting GPT-written functions into it. Then I handed the whole thing to an agentic loop. This paper reads my own git history across those three eras and asks one question honestly: are we better off? The numbers are not subtle. Commit velocity went from \textasciitilde8/day to \textasciitilde40/day. \texttt{disk.mjs} went from 5,154 lines to 16,821. \texttt{bios.mjs} more than quadrupled. Emoji in commit subjects went from zero to hundreds, conventional prefixes from 0\% to 38.8\% in recent months, and starting 2026-02-09 roughly one in three commits credits a model as co-author. I gained five new subsystems in a fortnight. I lost the hand --- the instrument-like minimalism where every abbreviation was a decision. This is a reckoning with both, written from inside the codebase it measures. It is a companion to \emph{Repository Archaeology}~\citep{scudder2026archaeology}. +\end{quote} +\vspace{0.5em} +}] + +% ============ 1. THE HAND ============ + +\section{The Hand (2022--2023)} + +I wrote everything. 2,766 commits in the first year, about eight a day. 82\% of them started lowercase --- not a style guide, just how I talk: \emph{``mo `sno` tweaks,''} \emph{``dont crash on glaze if webgl is disabled; update `sprite` for 12xN sheets.''} Backticks around piece names, semicolons chaining three changes into one line, \texttt{mo} for \emph{more}, \texttt{dont} without the apostrophe. No \texttt{fix:}, no \texttt{feat:}, no \texttt{chore:} --- zero conventional prefixes across all 2,766. Zero emoji in any subject. + +The code matched. \texttt{p2} was a point. \texttt{pA} and \texttt{pB} were two of them. \texttt{choose()} picked one of its arguments. \texttt{anyKey()} grabbed a random key off an object. Nobody named it \texttt{getRandomKey} because I knew what \texttt{choose} meant and so did anyone who lived in the idiom. \texttt{num.mjs} had functions like \texttt{even(n) \{ return n \% 2 === 0; \}} --- no guard, no null-check; it assumes you handed it a number because why would you not. \texttt{disk.mjs} in 2023 had 31 \texttt{try}/\texttt{catch} blocks and 144 null guards in 5,154 lines. The comments explained \emph{why}, not \emph{what}: \texttt{let USER; // a holder for the logged-in user. (defined in `boot`)}. Parenthetical, factual, sparse. + +And the pieces had a spine. Every one opened with the same four section headers --- \emph{Boot}, \emph{Paint}, \emph{Act}, \emph{Sim} --- each marked with a little glyph (a hiking boot for boot, a paint palette for paint). The only emoji I used back then lived in those headers and a books-glyph region marker; never in a commit, never in a log except one cheerful pile-of-poo I reused everywhere in the undo stack. It was an instrument. You learned the fingering once and every piece played the same way. + +\pull{zero conventional prefixes across 2,766 hand commits. zero emoji. just ``mo `sno` tweaks.''} + +% ============ 2. THE GRAFT ============ + +\section{The Graft (2024 -- mid 2025)} + +The second era is the quiet one, and it's the one the metrics almost miss. 3,386 commits, still \textasciitilde6/day, still 84\% lowercase. Only six commits in the whole span carried a conventional prefix --- 0.18\%. The commit voice barely moved: \emph{``prevent font loading from blocking the initial ui,''} \emph{``load auth0 after boot,''} \emph{``rm prompt logs.''} Still mine, still terse. + +But underneath, I started grafting. I'd describe a self-contained function to GPT, it would write one, and I'd paste it in. These were islands --- a shader here, a parser helper there --- surrounded by hand-written code that still looked like me. \texttt{disk.mjs} crept from 5,154 to 6,729 lines. \texttt{try}/\texttt{catch} went 31 to 35. Null guards 144 to 178. Barely anything. The grafted functions came in a little more defensive, a little more verbose, but they were sanded down to fit the idiom because I was still the one placing every one of them. \texttt{kidlisp.mjs} was born here --- I hand-wrote it on 2024-06-02, \emph{``a lisp interpreter / compiler for writing Aesthetic Computer pieces,''} with an examples region and a \texttt{TODO} that asks \emph{``Could I get this to evaluate?''} + +This is the era I'd defend hardest, honestly. The model wrote functions; I wrote the architecture. The graft kept the hand's discipline because the hand was still doing the suturing. + +% ============ 3. THE LOOP ============ + +\section{The Loop (mid 2025 -- 2026)} + +Then I handed it over. 13,785 commits in 12.5 months --- about 40 a day, five to six times the hand-era rate. Starting 2026-02-09 the trailers appear: \texttt{Co-Authored-By: Claude Opus}. One in three commits after that date credits the model~\citep{anthropic2026claudecode}. Conventional prefixes go from six total to 1,427, and in recent months 38.8\% of commits are \texttt{fix:}/\texttt{feat:}/\texttt{chore:}~\citep{conventionalcommits}. Emoji show up in subjects for the first time --- a sparkle, a heart, \emph{``nudge prompt corner label text to x:6 where it belongs.''} + +The files exploded. \texttt{disk.mjs}: 5,154 to 16,821. \texttt{bios.mjs}: 5,211 to 21,679, a 316\% jump. \texttt{graph.mjs} nearly tripled. \texttt{boot.mjs} went 203 to 2,039 lines --- a 604\% increase on a file whose whole job is to load \texttt{bios}. \texttt{speaker.mjs} went 226 to 1,020 lines with its if-checks going 13 to 87. The \texttt{try}/\texttt{catch} count in \texttt{disk.mjs} more than tripled to 104, null guards tripled to 434, emoji-decorated \texttt{console.log}s went 47 to 275. Every section got a banner comment. \texttt{fetchPieceMetadata} wraps a fetch in \texttt{try}/\texttt{catch}, warns on failure, and returns a typed \texttt{\{ trustLevel: ``untrusted'' \}} fallback. It is genuinely more resilient than anything I wrote by hand. It is also unmistakably not mine. + +And the breadth --- five new subsystems in roughly a fortnight. \texttt{papers/} (51 arxiv entries, including this one), \texttt{pop/} (215 files), \texttt{slab/} (a macOS desktop suite), the KidLisp realtime clock, the orchestration tooling. That doesn't happen by hand. That does not happen in a year by hand. + +\pull{the loop doesn't know the idiom. it knows JavaScript-in-general.} + +% ============ 4. WHAT CHANGED, MEASURABLY ============ + +\section{What Changed, Measurably} + +Two things grew together: velocity and verbosity. 5$\times$ more commits, 3--6$\times$ larger core files. But here's the honest correction to the scary number --- comment \emph{density} held roughly steady, \textasciitilde24\% of lines in era one down to \textasciitilde21\% in era three (\textasciitilde16\% by another count). The files didn't bloat with chatter. They grew because real surface got added: permission systems, metadata fetching, KidLisp integration, VST bridges, beat detection with constants like \texttt{\#beatSensitivity = 1.15; // was 1.3}. The expansion is mostly feature, not fluff. + +The metadata of work changed harder than the work. Zero conventional prefixes to 38.8\%. Zero co-authorship trailers to one-in-three. Files-per-commit 4.0 to 5.4 --- the loop reaches across the codebase in a single move where I used to touch one piece. Merge commits dropped from 10.2\% to 3.0\%; the workflow went rebase-flat and direct. + +And crucially: the pieces stayed small. 384 of them now, median \textasciitilde200 lines. \texttt{stripes.mjs} is 24 lines, model-generated, and still reads like the hand --- \texttt{const \{ floor, sin, PI \} = Math;} then a loop. The bloat lives in the foundational libs, not the leaves. That distinction is the whole ballgame. + +\begin{table*}[t] +\footnotesize +\centering +\begin{tabularx}{\textwidth}{Xrrr} +\toprule +\textbf{Signal} & \textbf{Hand (2023)} & \textbf{Graft (2024--mid25)} & \textbf{Loop (mid25--26)} \\ +\midrule +Commits in era & 2,766 & 3,386 & 13,785 \\ +Commits per day & \textasciitilde7.6 & \textasciitilde6.2 & \textasciitilde40.2 \\ +Lowercase-starting subjects & 82\% & 84\% & shifted to Title Case \\ +Conventional prefixes (\texttt{fix:}/\texttt{feat:}/\texttt{chore:}) & 0 (0\%) & 6 (0.18\%) & 1,427 (10.4\%; 38.8\% late) \\ +Emoji in commit subjects & 0 & 0--2 & hundreds \\ +\texttt{Co-Authored-By} model trailers & 0 & 0 & \textasciitilde3,234 (33\% after 2026-02-09) \\ +Files changed per commit & 4.0 & 4.3 & 5.4 \\ +Merge commits & 10.2\% & --- & 3.0\% \\ +\midrule +\texttt{disk.mjs} lines & 5,154 & 6,729 & 16,821 \\ +\texttt{bios.mjs} lines & 5,211 & 5,552 & 21,679 \\ +\texttt{boot.mjs} lines & 203 & 519 & 2,039 \\ +\texttt{kidlisp.mjs} lines & new (2024-06-02) & 2,589 & 15,640 \\ +\texttt{disk.mjs} try/catch blocks & 31 & 35 & 104 \\ +\texttt{disk.mjs} null/undefined guards & 144 & 178 & 434 \\ +\texttt{disk.mjs} emoji \texttt{console.log}s & 47 & \textasciitilde50 & 275 \\ +Comment density (\% of lines) & \textasciitilde24\% & \textasciitilde22\% & \textasciitilde21\% \\ +Total pieces (disks) & 58 & incremental & 384 \\ +New subsystems built & 0 & 0 & papers, pop, slab, kidlisp \\ +\bottomrule +\end{tabularx} +\caption{The same codebase across three eras. Era boundaries: hand 2022-12-23 to 2023-12-31; graft 2024-01-01 to 2025-06-30; loop 2025-07-01 to 2026-06-08. Some era-two cells are unmeasured (---). Counts via git history and per-file inspection; sampled metrics noted in \S7.} +\end{table*} + +% ============ 5. WHAT WE GAINED ============ + +\section{What We Gained} + +I'm not going to be precious about this. I got resilience I never wrote. The \texttt{ableton} piece is full of \texttt{sound?.synth?.(...)} --- every audio call optional-chained so a missing synth degrades instead of crashing. The \texttt{aa.mjs} status function guards with \texttt{if (!hudRef) return;} before doing anything. That defensiveness is real and it has saved real sessions. The documentation, even if I didn't ask for it, means someone other than me can read \texttt{disk.mjs} now --- block comments tripled in absolute count. + +And I got reach. \texttt{papers.aesthetic.computer} --- the platter this very paper lives on --- exists because the loop could build academic-paper infrastructure while I was doing something else. The \texttt{pop} music subsystem, 215 files, a whole compositional pipeline. \texttt{slab}, a macOS desktop suite. KidLisp's global UTC clock landing \emph{``musically-timed animation, end to end.''} These are not refactors of things I had. They're capabilities I did not have and would not have had for years. 65$\times$ growth in \texttt{disks} lines of code, most of it breadth --- new pieces, new instruments, new lanes. + +The loop is a force multiplier on a real axis. That's not nothing. That's most of what I wanted when I picked up the tool. + +% ============ 6. WHAT WE LOST ============ + +\section{What We Lost} + +The hand. Specifically: intentional minimalism. In era one every abbreviation was a decision --- \texttt{p2}, \texttt{choose}, \texttt{anyKey}, the lifecycle headers as a learned fingering. The terseness wasn't carelessness, it was the opposite --- it was knowing the idiom so well you could compress it. The loop doesn't know the idiom; it knows JavaScript-in-general. So it writes \texttt{currentBuffer} where I'd write \texttt{c}, it adds a \texttt{try}/\texttt{catch} where I'd let it crash and tell me, it names things for a stranger. The playful names --- \texttt{choose}, \texttt{flip}, \texttt{nonvalue} --- don't get generated. They get replaced by correct, generic, forgettable ones. + +And it over-engineers in the libs: separate reverb state objects for left and right where a shared structure would do; adaptive thresholds with tuning comments that read like they were copied from documentation. \texttt{boot.mjs} at 2,039 lines for a bootstrap is not better than \texttt{boot.mjs} at 203 --- it's just more. \texttt{disk.mjs} at 16,821 lines is a file I can no longer hold in my head, and I used to hold the whole system in my head. That was the point of the instrument: it fit a single mind. + +The commit voice went too. \emph{``mo `sno` tweaks''} became \emph{``chore(electron): bump version to 0.1.46.''} Both are fine. Only one sounds like a person made it. + +\pull{disk.mjs at 16,821 lines is a file i can no longer hold in my head --- and i used to hold the whole system there.} + +% ============ 7. LIMITATIONS ============ + +\section{Limitations} + +The era boundaries are clean lines drawn through a messy continuum. I dated them hand 2022-12-23 to 2023-12-31, graft 2024-01-01 to 2025-06-30, loop 2025-07-01 to 2026-06-08 --- but grafting and hand-writing overlapped for months and nobody flipped a switch. Several metrics are sampled, not exhaustive: files-changed-per-commit comes from 100-commit samples per era, and a few era-two numbers were never measured at all (marked --- in Table 1; I did not invent them). Some counts are noisy --- raw emoji and conventional-prefix counts depend on regex over Unicode ranges and prefix matching, so undercounts are likely, and a couple of methods disagree (emoji-in-subjects lands anywhere from 57 to 715 depending on the range counted). Commit-volume comparisons annualize across uneven spans (343 loop days vs.\ 365 hand days), so the 5$\times$ is roughly right, not exact. + +And the deepest bias is that I'm the author measuring my own codebase and my own taste. ``Lost the hand'' is a value judgment, not a metric. A stranger reading \texttt{disk.mjs} might call the 2026 version simply better. + +% ============ 8. CONCLUSION ============ + +\section{Conclusion: Are We Better Off?} + +Yes --- and I mean it without hedging on the axis that matters most to a working system: capability. Five subsystems in a fortnight, a music pipeline, a paper platter, defensive code that survives sessions my hand-written code would have crashed. I'd take that trade again. The loop let me build things I'd have needed years and collaborators to reach. That's real, and it's most of why I picked up the tool. + +But better-off is not the same as nothing-lost, and the honest verdict has a shape. Keep the loop where it earns its keep: the small pieces (\texttt{stripes} at 24 lines is fine, generated or not), the new infrastructure, the breadth. Constrain it where it hurts: the foundational libs are bloating past the point I can hold them in my head, and a system that no longer fits a single mind has lost the thing that made it an instrument. \texttt{boot.mjs} does not need 2,039 lines. \texttt{disk.mjs} at 16,821 needs a hand to come back and carve. + +What to preserve from the hand-written era: the discipline that terseness is a decision, not an accident. That a name like \texttt{choose} is worth more than \texttt{getRandomKey} because it teaches the idiom. That a commit can sound like a person. That the highest form of knowing a system is being able to write it small. The loop writes correct code. It does not yet write code that knows it's part of an instrument. That part is still mine to keep --- and I intend to. + +\pull{the bloat lives in the foundational libs, not the leaves. that distinction is the whole ballgame.} + +\vspace{0.5em} +\noindent\textbf{Method.} All figures derive from the \texttt{whistlegraph/aesthetic-computer} git history (\textasciitilde20k commits, 2022-12-23 onward) and per-file inspection of \texttt{disk.mjs}, \texttt{bios.mjs}, \texttt{boot.mjs}, \texttt{kidlisp.mjs}, \texttt{speaker.mjs}, and sampled pieces at era-boundary revisions via \texttt{git show :}. Commit-style counts use \texttt{git log} format filters over dated ranges. + +\vspace{0.5em} +\noindent\textbf{ORCID:} \href{https://orcid.org/0009-0007-4460-4913}{0009-0007-4460-4913} + +% ============ REFERENCES ============ + +\bibliographystyle{plainnat} +\bibliography{references} + +\end{document} diff --git a/papers/arxiv-hand-and-loop/references.bib b/papers/arxiv-hand-and-loop/references.bib new file mode 100644 index 000000000..5b927f488 --- /dev/null +++ b/papers/arxiv-hand-and-loop/references.bib @@ -0,0 +1,36 @@ +@misc{scudder2026archaeology, + title={Repository Archaeology: Tracing the Evolution of Aesthetic Computer Through Its Git History}, + author={{@jeffrey}}, + year={2026}, + note={Companion paper reconstructing AC's history from version control} +} + +@misc{scudder2026ac, + title={Aesthetic Computer '26: A Mobile-First Runtime for Creative Computing}, + author={{@jeffrey}}, + year={2026}, + note={Companion paper describing the AC platform} +} + +@misc{scudder2026kidlisp, + title={KidLisp '26: A Minimal Lisp for Generative Art}, + author={{@jeffrey}}, + year={2026}, + note={Companion paper describing the KidLisp language} +} + +@misc{conventionalcommits, + title={Conventional Commits 1.0.0}, + author={{Conventional Commits Contributors}}, + year={2021}, + url={https://www.conventionalcommits.org}, + note={A specification for adding human and machine readable meaning to commit messages} +} + +@misc{anthropic2026claudecode, + title={Claude Code}, + author={{Anthropic}}, + year={2026}, + url={https://claude.com/claude-code}, + note={An agentic command-line coding tool} +} diff --git a/papers/cli.mjs b/papers/cli.mjs index 0b483a0ab..08e89a365 100755 --- a/papers/cli.mjs +++ b/papers/cli.mjs @@ -75,6 +75,11 @@ const PAPER_MAP = { siteName: "repo-archaeology-26-arxiv", title: "Repository Archaeology", }, + "arxiv-hand-and-loop": { + base: "handloop", + siteName: "hand-and-loop-26-arxiv", + title: "The Hand and the Loop", + }, "arxiv-dead-ends": { base: "dead-ends", siteName: "dead-ends-26-arxiv", @@ -212,6 +217,11 @@ const PAPER_MAP = { siteName: "diagrams-from-data-26-arxiv", title: "Diagrams from Data", }, + "arxiv-nom": { + base: "nom", + siteName: "nom-games-26-arxiv", + title: "The nom Games", + }, "cv": { base: "cv", siteName: "jeffrey-alan-scudder-cv", @@ -337,12 +347,94 @@ const DOSSIER_DIRS = [ "arxiv-microvision", "arxiv-calarts-news", ]; -const DOSSIER_SET = new Set(DOSSIER_DIRS); const dossierRank = (dir) => { const i = DOSSIER_DIRS.indexOf(dir); return i === -1 ? 99 : i; }; +// Top-level index categories. Every listed (non-hidden, non-archived) +// paper resolves to exactly one. Order here is the page order. The +// "software" lane is populated from the `extras` array (JOSS/ELS), not +// from PAPER_MAP, so its `dirs` stays empty. Lists flagged `nosort` keep +// their curated DOM order and are skipped by the client-side sort bar. +const CATEGORIES = [ + { + key: "platform", + title: "platform & language", + sub: "the runtime, the language, and the surfaces you actually touch.", + dirs: [ + "arxiv-ac", + "arxiv-kidlisp", + "arxiv-os", + "arxiv-api", + "arxiv-pieces", + "arxiv-notepat", + "arxiv-kidlisp-reference", + "arxiv-kidlisp-cards", + "arxiv-keymaps", + "arxiv-url-tradition", + "arxiv-latency", + "arxiv-penrose", + "arxiv-identity", + ], + }, + { + key: "essays", + title: "essays & criticism", + sub: "arguments about creative computing — its lineage, its players, and its discontents.", + dirs: [ + "arxiv-plork", + "arxiv-sustainability", + "arxiv-goodiepal", + "arxiv-whistlegraph", + "arxiv-complex", + "arxiv-dead-ends", + "arxiv-folk-songs", + "arxiv-futures", + "arxiv-holden", + "arxiv-fraserin", + "arxiv-score-analysis", + "arxiv-comp-strats", + "arxiv-nom", + "essay-may-26", + ], + }, + { + key: "audits", + title: "audits & field studies", + sub: "data turned back on the project itself — who uses it, what it cites, where it came from.", + dirs: [ + "arxiv-archaeology", + "arxiv-hand-and-loop", + "arxiv-network-audit", + "arxiv-diversity", + "arxiv-open-schools", + ], + }, + { + key: "dossiers", + title: "dossiers", + sub: "what's publicly recoverable about art-and-tech institutions and their largest funders — fact-surfacing, not argument.", + nosort: true, + dirs: [...DOSSIER_DIRS, "arxiv-ucla-arts"], + }, + { + key: "software", + title: "software papers", + sub: "conventional software-paper summaries, for archival and citation.", + dirs: [], // populated from the `extras` array (JOSS / ELS) + }, +]; + +// Papers retired to the quiet Archive section at the very bottom of the +// page, regardless of their category. Keyed by `dir`. +const ARCHIVE_DIRS = new Set(["arxiv-calarts"]); + +// dir -> category key (dossiers included; archived + software handled separately) +const CATEGORY_OF = {}; +for (const c of CATEGORIES) for (const d of c.dirs) CATEGORY_OF[d] = c.key; +const categoryOf = (dir) => CATEGORY_OF[dir] || "essays"; + function texName(base, lang) { return lang === "en" ? base : `${base}-${lang}`; } @@ -555,8 +647,11 @@ function genThumbnail(pdfPath, siteName) { } const tmpPrefix = join(THUMBS_DIR, `${siteName}.tmp`); try { + // Cards are physically small (4x6in), so render them at a higher DPI to + // keep the cover crisp; full pages stay at a lighter resolution. + const dpi = /-cards\.pdf$/.test(pdfPath) ? 110 : 72; execSync( - `pdftoppm -jpeg -jpegopt quality=72,progressive=y -r 60 -f 1 -l 1 "${pdfPath}" "${tmpPrefix}"`, + `pdftoppm -jpeg -jpegopt quality=75,progressive=y -r ${dpi} -f 1 -l 1 "${pdfPath}" "${tmpPrefix}"`, { stdio: "pipe", timeout: 30000 }, ); // pdftoppm appends -1 (or -01) for single page output @@ -580,10 +675,19 @@ function genThumbnail(pdfPath, siteName) { function genAllThumbnails() { if (!existsSync(SITE_DIR)) return 0; let count = 0; - for (const name of readdirSync(SITE_DIR)) { - if (!name.endsWith(".pdf")) continue; + const pdfs = readdirSync(SITE_DIR).filter((n) => n.endsWith(".pdf")); + const pdfSet = new Set(pdfs); + for (const name of pdfs) { const siteName = name.slice(0, -4); - if (genThumbnail(join(SITE_DIR, name), siteName)) count++; + // The cards (4x6 single-sheet) variant is the cover art. Surface it via + // the base paper's thumbnail; don't waste a thumb on the cards file + // itself (the index never references `${siteName}-cards.jpg`). + if (siteName.endsWith("-cards")) continue; + // Prefer the cards PDF as the cover source — it's designed to read as a + // card, unlike the dense arXiv first page. Fall back to the full PDF. + const cardsName = `${siteName}-cards.pdf`; + const source = pdfSet.has(cardsName) ? cardsName : name; + if (genThumbnail(join(SITE_DIR, source), siteName)) count++; } return count; } @@ -644,6 +748,7 @@ function updateIndex(entries) { "dead-ends-26-arxiv": 12, "folk-songs-26-arxiv": 13, "repo-archaeology-26-arxiv": 14, + "hand-and-loop-26-arxiv": 14.5, "network-audit-26-arxiv": 15, "kidlisp-reference-26-arxiv": 16, "citation-diversity-audit-26": 17, @@ -697,8 +802,8 @@ function updateIndex(entries) { }, ]; - // Guest papers — moved to platter readings (OCR'd text files) - const guestPdfs = []; + // Guest papers were moved to platter readings (OCR'd text files); the + // lane is retired, so only the JOSS/ELS extras remain. const extras = []; for (const ex of extraPdfs) { const fp = join(SITE_DIR, ex.file); @@ -783,6 +888,11 @@ function updateIndex(entries) { abstract: "Repository Archaeology traces the project through its git history. The paper shows how version control can become a narrative medium for design evolution.", }, + "hand-and-loop-26-arxiv": { + detail: "Three Eras of My Own Code: Hand-Written, GPT-Grafted, Agentic · arXiv 4pp", + abstract: + "The Hand and the Loop reads AC's git history across three eras — hand-written, GPT-grafted, and fully agentic — and asks honestly whether the codebase is better off. It weighs 5x velocity and five new subsystems against the loss of instrument-like minimalism, and argues for keeping the loop in the leaves while a human hand carves the foundational libraries.", + }, "network-audit-26-arxiv": { detail: "Who Uses Aesthetic Computer and What Do They Make? · arXiv 4pp", abstract: @@ -808,6 +918,11 @@ function updateIndex(entries) { abstract: "Five Years from Now is a projection paper about where the project could go if current habits continue. It uses the near future to test the consequences of today's decisions.", }, + "nom-games-26-arxiv": { + detail: "Muncher Arcade for Aesthetic Computer · arXiv", + abstract: + "The nom games rebuild MECC's Word and Number Munchers as Aesthetic Computer pieces --- mathnom, engnom, mexinom, and notenom. The paper shows how one shared engine and a reusable virtual synth controller turn each new subject or language into a content table rather than a new game, with the answer key in notenom doubling as a playable instrument.", + }, "aesthetic-may-26-essay": { detail: "Essay · ~5pp", abstract: @@ -968,7 +1083,7 @@ function updateIndex(entries) { ? `` : ""; return ` -
+
${thumbHtml}
${detail}
@@ -978,21 +1093,8 @@ function updateIndex(entries) {
\n`; } - // Build paper entries HTML — dossiers are split into their own - // "dossiers;" section so the fact-surfacing lane doesn't mix with the - // argumentative papers. - let papersHtml = ""; - let dossiersHtml = ""; - for (const p of papers) { - if (p.hidden) continue; // built + tracked, but not listed publicly - if (DOSSIER_SET.has(p.dir)) continue; // rendered in dossiers section below - papersHtml += renderPaper(p); - } - const dossierPapers = papers - .filter((p) => !p.hidden && DOSSIER_SET.has(p.dir)) - .sort((a, b) => dossierRank(a.dir) - dossierRank(b.dir)); - for (const p of dossierPapers) dossiersHtml += renderPaper(p); - for (const ex of extras) { + // Render one JOSS/ELS "software paper" card from the extras array. + function renderExtra(ex) { const createdStr = ex.created ? fmtDate(ex.created) : ""; const revStr = ex.revisions > 0 ? `r${ex.revisions}` : ""; const exKey = { "joss-ac": "joss-ac", "joss-kidlisp": "joss-kidlisp", "els-kidlisp": "els" }[ex.metaKey] || ex.metaKey; @@ -1001,7 +1103,7 @@ function updateIndex(entries) { const exThumbHtml = exThumbExists ? `` : ""; - papersHtml += ` + return `
${exThumbHtml}
@@ -1012,25 +1114,72 @@ function updateIndex(entries) {
\n`; } - // Build guest papers HTML - let guestHtml = ""; - for (const g of guestPdfs) { - const fp = join(SITE_DIR, g.file); - if (existsSync(fp)) { - guestHtml += ` -
- -
${g.detail}
-
${g.abstract}
-
${g.author}${g.year}
-
\n`; + // Wrap a category's cards in a labeled
. `count` is the number + // of cards; `nosort`/extra modifiers control the client-side sort. + function renderSection({ key, title, sub, nosort }, cardsHtml, count, archive = false) { + if (!cardsHtml) return ""; + const cls = `cat${archive ? " cat--archive" : ""}`; + const nosortAttr = nosort || archive ? " data-nosort" : ""; + return ` +
+
${title};${count}
+
${sub}
+
+${cardsHtml}
+
\n`; + } + + // Bucket every listed paper into exactly one category; archived papers + // are pulled out to their own quiet section at the bottom. + const buckets = {}; + for (const c of CATEGORIES) buckets[c.key] = []; + const archived = []; + for (const p of papers) { + if (p.hidden) continue; // built + tracked, but not listed publicly + if (ARCHIVE_DIRS.has(p.dir)) { + archived.push({ ...p, deprecated: true, psycho: false }); + continue; } + buckets[categoryOf(p.dir)].push(p); } + // Dossiers keep their curated order; other lanes keep importance rank. + buckets.dossiers.sort((a, b) => dossierRank(a.dir) - dossierRank(b.dir)); - // Read current index, replace paper entries between markers - let html = readFileSync(indexPath, "utf8"); + const uncategorized = papers.filter( + (p) => !p.hidden && !ARCHIVE_DIRS.has(p.dir) && !CATEGORY_OF[p.dir], + ); + if (uncategorized.length) { + console.log( + ` WARN ${uncategorized.length} uncategorized paper(s) → essays: ${uncategorized.map((p) => p.dir).join(", ")}`, + ); + } + + // Build the full categorized block (all sections, in page order). + let papersHtml = ""; + for (const c of CATEGORIES) { + if (c.key === "software") { + const cards = extras.map(renderExtra).join(""); + papersHtml += renderSection(c, cards, extras.length); + } else { + const list = buckets[c.key]; + papersHtml += renderSection(c, list.map(renderPaper).join(""), list.length); + } + } + if (archived.length) { + papersHtml += renderSection( + { + key: "archive", + title: "archive", + sub: "deprecated — kept for the record, no longer actively maintained.", + }, + archived.map(renderPaper).join(""), + archived.length, + true, + ); + } - // Replace everything between the sub line and the footer + // Read current index, replace the categorized block between markers. + let html = readFileSync(indexPath, "utf8"); const startMarker = ""; const endMarker = ""; @@ -1055,29 +1204,13 @@ function updateIndex(entries) { } } - // Replace dossier entries between dossiers markers (own container so - // the client-side sort, which reorders only the first .p's parent, - // never scrambles this section). - const dossStart = ""; - const dossEnd = ""; - if (html.includes(dossStart)) { - const dBefore = html.slice(0, html.indexOf(dossStart) + dossStart.length); - const dAfter = html.slice(html.indexOf(dossEnd)); - html = dBefore + "\n" + dossiersHtml + "\n " + dAfter; - } - - // Replace guest papers between guest markers - const guestStart = ""; - const guestEnd = ""; - if (guestHtml && html.includes(guestStart)) { - const gBefore = html.slice(0, html.indexOf(guestStart) + guestStart.length); - const gAfter = html.slice(html.indexOf(guestEnd)); - html = gBefore + "\n" + guestHtml + "\n " + gAfter; - } - writeFileSync(indexPath, html); - const guestCount = guestPdfs.filter(g => existsSync(join(SITE_DIR, g.file))).length; - console.log(` INDEX updated with ${papers.length + extras.length} papers + ${guestCount} guest papers.`); + const sectionCounts = CATEGORIES.map((c) => + c.key === "software" ? `${c.key} ${extras.length}` : `${c.key} ${buckets[c.key].length}`, + ).join(", "); + console.log( + ` INDEX updated: ${papers.length + extras.length} papers across ${CATEGORIES.length} categories (${sectionCounts}${archived.length ? `, archive ${archived.length}` : ""}).`, + ); writeFeed(papers, extras, PAPER_COPY); } @@ -1123,7 +1256,7 @@ function writeFeed(papers, extras, PAPER_COPY) { const SITE_URL = "https://papers.aesthetic.computer"; const FEED_URL = `${SITE_URL}/feed.xml`; const AUTHOR_NAME = "@jeffrey"; - const AUTHOR_URI = "https://orcid.org/0009-0007-4460-4913"; + const AUTHOR_URI = "https://prompt.ac/@jeffrey"; const entries = []; for (const p of papers) { diff --git a/papers/metadata.json b/papers/metadata.json index 31c2af4ff..94171ae23 100644 --- a/papers/metadata.json +++ b/papers/metadata.json @@ -1,4 +1,14 @@ { + "arxiv-hand-and-loop": { + "created": "2026-06-08", + "revisions": 0, + "updated": "2026-06-08T00:00:00.000Z" + }, + "arxiv-nom": { + "created": "2026-06-08", + "revisions": 0, + "updated": "2026-06-08T00:00:00.000Z" + }, "essay-may-26": { "created": "2026-05-25", "revisions": 0, -- 2.51.2