Skip to content

chore(app): drop the preact scaffolding left by the native diff renderer - #1760

Open
chphch wants to merge 1 commit into
slopus:mainfrom
chphch:chore/drop-dead-pierre-preact-scaffolding
Open

chore(app): drop the preact scaffolding left by the native diff renderer#1760
chphch wants to merge 1 commit into
slopus:mainfrom
chphch:chore/drop-dead-pierre-preact-scaffolding

Conversation

@chphch

@chphch chphch commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

The native diff renderer left its scaffolding behind

c1dda880 replaced the Pierre-based diff renderer and dropped @pierre/diffs from packages/happy-app/package.json. Six things that existed only to serve it stayed:

What Why it existed
patches/expose-pierre-diffs-style.cjs added ./style.js to @pierre/diffs's exports map
patches/fix-pierre-trees-preact-hooks.cjs made @pierre/trees load preact/hooks eagerly
patches/force-preact-cjs.cjs forced every preact subpath to the CJS bundle
three require(...) lines in scripts/postinstall.cjs ran the three patches
the resolveRequest hook in packages/happy-app/metro.config.js pinned preact / preact/hooks to one instance
"preact": "^10.29.1" in packages/happy-app/package.json the renderer's own dependency

The last three are all one problem. force-preact-cjs.cjs and the Metro hook say so in their own comments — both name @pierre/trees as the reason two preact module instances existed at all:

Two instances mean two options objects — preact/hooks patches one, @pierre/trees renders against the other, currentComponent stays undefined, r.__H crashes.

With @pierre/trees gone there is no second importer, so there are no two instances, so there is nothing to pin.

Is it actually dead? Three independent checks

Check Result
@pierre/* in any manifest or in pnpm-lock.yaml 0
packages declaring preact as a resolved dependency in pnpm-lock.yaml 0 — only packages/happy-app declares it directly
installed packages that import/require preact 0

The third check was run over the whole installed tree and is easy to get a false zero from, so the same command was pointed at node_modules/preact itself first: it returns 45 files. The harness works; the 0 is a real 0.

@preact/signals-core is unrelated — it is a @lexical/extension dependency and is untouched.

The pieces are coupled

metro.config.js calls require.resolve('preact') at module load. Dropping the dependency without dropping that hook makes Metro throw before it bundles anything, so these land together or not at all.

Proof

Every check below ran on this branch, in a clean worktree branched from main.

Check Result
pnpm install --frozen-lockfile exits 0 — the hand-edited lockfile satisfies every manifest — and leaves pnpm-lock.yaml byte-identical (same SHA-256 before and after)
the same, against a deliberately corrupted lockfile exits 1, ERR_PNPM_BROKEN_LOCKFILE — so the check above can actually fail, and passing it means something
postinstall after removing the three require() lines runs clean; the remaining three patches still apply
tsc --noEmit (happy-app) clean
vitest run (happy-app) 1,213 passed, 1 skipped, 115 files
production web bundle (expo export via bundle-webapp.cjs) succeeds — this is the one that matters, because it is what a removed Metro resolveRequest hook would break

pnpm-lock.yaml is hand-edited, not regenerated. pnpm install --lockfile-only re-resolves every caret range against the registry, so a one-line dependency removal came back as a 190-line diff that also bumped @anthropic-ai/claude-agent-sdk 0.3.179→0.3.246, marked 18.0.2→18.0.5 and four others. Deleting only the three blocks preact owns — the importer's specifier:/version: pair, its packages: entry, its snapshots: entry — gives 8 deleted lines and nothing else.

Scope note

Pure deletion: 181 lines removed, 0 added, across 7 files. The Pierre mentions that remain are unrelated — packages/codium's "Pierre Dark"/"Pierre Light" theme presets, and two comments in CodeEditor.web.tsx recording where its colours came from. @preact/signals-core is also unrelated; it is a @lexical/extension dependency and is untouched.

c1dda88 replaced the Pierre-based diff renderer and dropped @pierre/diffs.
Six things that existed only to serve it stayed: three patches/ scripts, their
require() lines in scripts/postinstall.cjs, metro.config.js's preact
single-instance resolveRequest hook, and happy-app's preact dependency.

force-preact-cjs.cjs and the Metro hook both name @pierre/trees in their own
comments as the reason two preact module instances existed at all. With it gone
there is no second importer, so there is nothing left to pin.

Dead three ways: zero @pierre/* in any manifest or in pnpm-lock.yaml; zero
packages declaring preact as a resolved dependency (only happy-app declared it
directly); zero installed packages importing preact -- the same search finds 45
files inside node_modules/preact itself, so that zero is real.

They are coupled: metro.config.js calls require.resolve('preact') at module
load, so dropping the dependency without the hook makes Metro throw before it
bundles anything.

pnpm-lock.yaml is hand-edited rather than regenerated -- `pnpm install
--lockfile-only` re-resolves every caret range and would have carried six
unrelated package upgrades into a cleanup. The 8 removed lines are all preact.

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
@chphch

chphch commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

Proof — the app boots and renders diffs with the scaffolding gone

The reasonable worry about this PR is not whether it compiles — it is whether removing a Metro resolveRequest hook breaks module resolution at runtime. So here is the app, built from this branch, running.

Captured locally: throwaway standalone happy-server (PGlite, isolated per-instance ports), Expo web, authenticated headlessly via the getDevWebQueryCredentials() dev URL bypass, driven with Playwright. Screen is /dev/diff-bench — the diff harness added alongside the native renderer in c1dda880.

diff-bench rendering on this branch

Every path the removed scaffolding used to sit next to is visibly working:

In the screenshot What it exercises
coloured tokens (export, function, types) engine/highlight.ts → Prism
result1result2 highlighted inside the changed line engine/intraline.ts word diff
⋯ 6 unchanged / ⋯ 4 unchanged layout.ts selectVisibleRows collapse
+3 −1, per-row add/remove tint, line numbers engine/buildDiff.ts / DiffFileView
rows 23 · build 6.5ms · mount 84ms the bench's own counters

Instrumented run, not just eyeballed:

Signal Value
login screen gone / authenticated loginVisible: false, authed: true
diff rows rendered 23
split-mode toggle after first render still 23 rows, no re-render error
browser console errors 0
uncaught page errors 0
failed network requests 0

No Unable to resolve, no Cannot find module, nothing mentioning preact — which is the specific failure a wrongly-removed resolver hook would produce.

CI on this PR is green across all four checks (typecheck, typecheck-and-test, docker-production, docker-standalone).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant