The tree was a debranded snapshot of upstream taken around 2026-02-20, and upstream has moved about five months since. This replaces it with upstream master, mechanically re-derived: bin/debrand does the rename, and the pipeline and squashed migrations are restored from the shipped tree. Not a merge. The previous import replaced the tree wholesale in a single commit, so there is no shared content history to merge against. Reproducing the rename on the new tree is what actually converges. What survives: .hanzo/workflows, the Dockerfiles and bin/docker* entrypoints, compose files, LLM.md, and insights/migrations. The squashed initial migration is load-bearing — the live database records it by name, so upstream's ~918 individual migrations cannot come back. Nine products are dropped as unreferenced. Twenty-one more that we do not run are kept anyway because the core app still imports them; unpicking those is a refactor and does not belong in a rebase. This base does not build yet, and the reasons are not in the rename: upstream now wants Python 3.13.13 against our 3.12.12 and Django 5.2 against our 4.2, and the dependency manifests still name upstream packages that we republish under our own names. Those are decisions, not renames.
12 KiB
Quill architecture review
Honest, senior-architect review of the quill packaging + distribution story, written after the "split tokens CSS + colors.css entry point" change. Ordered by impact, not by effort.
The high-level verdict: what's shipped today is a pragmatic improvement but
it perpetuates the architectural leaks that forced the Code consumer into
hand-rolled workarounds. Fixing those leaks is the path to an A-grade
design system package.
Use this file as a backlog. Check things off as they land.
Bugs (do first, low effort, real breakage)
-
2.
theme-shape.csshas a hidden dependency oncolor-system.css--radius-*is defined ascalc(var(--radius) - 4px).--radiusonly exists if the consumer has loadedcolor-system.css. Importingtheme-shape.csson its own producescalc(NaN)for every radius utility with no error. Same hidden coupling exists for anything referencingvar(--accent)etc. Fix: document the required import order loudly, OR move shared base variables (--radius,--custom-variant dark) into a dedicatedcore.cssthat every other file imports. -
3.
@custom-variant darkonly lives intheme-colors.cssA consumer who importstheme-typography.css+theme-shape.csswithouttheme-colors.csssilently loses dark-mode variant support. Same fix as #2 — hoist the custom variant into a shared core file. -
4.
ROOT_FONT_SIZE: 14 → 16is a silent breaking change The rem base flip was correct in isolation but I shipped it without verifying: - Doesapps/storybooksethtml { font-size: 14px }? - Doesapps/web(the main Insights frontend) rely on the old values? - Do any stories visually regress in light + dark?**Fix:** audit every internal consumer, run Storybook in both themes, screenshot-diff the primitives stories, and only then keep the change. Bundle with a version bump + CHANGELOG entry. -
5. No version bump for breaking changes The session removed
./styles.cssfrom primitives exports, reshapedtailwind-lib.cssfrom monolithic to@import-chained, and changed the rem base. All breaking. Even at0.1.0-alpha.0this warrants a bump (→0.2.0-alpha.0) and a CHANGELOG note describing the migration path for early consumers.
Architectural mistakes (the big DX wins)
-
6. The
./colors.cssentry point is a lie and should be deleted ✅ Resolved as a no-op. The./colors.cssentry only ever existed in a local experimental branch that was never merged to master, so there was nothing to delete. The underlying concern — a "colors-only" path that leaves primitive sizing broken — is structurally prevented by item #7 below, because pre-compiled CSS cannot collide with the consumer's Tailwind theme at all. -
7. Pre-compile Tailwind in the library build ⭐ DONE A new
@hanzo/quillaggregate package atpackages/quill/packages/quillowns the CSS pipeline. Itssrc/index.cssis the Tailwind input (imports tokens, shadcn/tailwind.css, tw-animate-css, and@sources primitives / components / blocks source trees). A build step runs@tailwindcss/cliviascripts/build-css.tsto producedist/quill.css— a flat, minified, pre-compiled stylesheet. The package exposes it as./styles.css. Consumers import the compiled output directly; their bundler treats it as plain CSS and does not need Tailwind. The Storybook app consumes exactly this path as the first-party smoke test and renders every primitive story correctly with zero@sourcedirectives pointing at quill sources. -
8. Decouple primitive sizing from the consumer's theme ✅ Subsumed by #7. Because primitives are pre-compiled against quill's own Tailwind theme at build time, the resulting
dist/quill.csscontains resolved selectors for every utility primitives reference (e.g..p-4 { padding: 16px }) and the consumer's own Tailwind config never touches them. There is no way for the consumer's--spacing-*,--text-*, or--radius-*scale to override primitive sizing. The runtime theming knobs that remain are the CSS custom properties fromcolor-system.css(--primary,--background, etc.) which consumers can override at:rootif they want a recoloured build. -
9. Collapse to one published package:
@hanzo/quill✅ Option B from the design discussion: a newpackages/quill/packages/quillworkspace member holds the public surface and re-exports primitives / components / blocks through a singlesrc/index.ts. Consumersimport { Button } from '@hanzo/quill'. The oldpackages/quill/package.jsonumbrella was renamed from@hanzo/quillto@hanzo/quill-workspace(private) so the name is free for the aggregate. The@hanzo/quill-primitives,@hanzo/quill-components, and@hanzo/quill-blockspackages are nowprivate: trueworkspace members that ship nowhere.@hanzo/quill-tokensstays independently published for consumers who want programmatic access to the typed semantic-color exports. The publish workflow at.github/workflows/publish-quill-npm.ymlwas collapsed to publish only@hanzo/quill-tokensand@hanzo/quill. -
10. Drop
shadcnas a peer dependency ✅ Removed from@hanzo/quill-primitivespeer dependencies and declared as adevDependencyof the@hanzo/quillaggregate instead.shadcn/tailwind.cssis only a collection of@custom-variantdefinitions (data-open,data-closed,data-checked, etc.) plus keyframes — all compile-time macros. At library build time the Tailwind CLI expands them into concrete selectors that get baked intodist/quill.css. Consumers never see@custom-variantand do not needshadcninstalled. -
11. Drop
tw-animate-cssas a peer dependency ✅ Same treatment as #10.tw-animate-cssmoved from@hanzo/quill-primitivespeer dependencies into the aggregate'sdevDependencies. Its utilities (animate-in,fade-in,slide-in-from-*, etc.) expand at build time and end up in the pre-compiled output. Zero runtime footprint for consumers. -
12. Stop shipping
src/in the tarball ✅fileson@hanzo/quill-primitives,@hanzo/quill-components, and@hanzo/quill-blocksdropped from["src", "dist"]to["dist"]. Those packages are now alsoprivate: trueso they are never published anyway; the only package that goes to the registry is@hanzo/quill, which was configured withfiles: ["dist"]from the start.
Smaller DX + hygiene issues
-
13. Declare
sideEffectsin every published package ✅@hanzo/quilldeclaressideEffects: ["*.css"]so bundlers preserve the compiled stylesheet import. The internal@hanzo/quill-primitives,@hanzo/quill-components, and@hanzo/quill-blockspackages declaresideEffects: false(pure JS re-exports, no CSS side effects). -
14. Add
"./package.json": "./package.json"to exports ✅ Added to@hanzo/quill,@hanzo/quill-primitives,@hanzo/quill-components, and@hanzo/quill-blocks. -
15. Pin
tailwindcsspeer dep more tightly"tailwindcss": "^4.0.0"accepts v4 betas which had different@theme inlinesemantics. Pin to^4.1.0or whatever the first stable release was that matches quill's current syntax. -
16. Add an
enginesfield ✅ Added"engines": { "node": ">=20" }to the@hanzo/quillaggregate (the only runtime-installable public package). -
17. The README has five different ways to import CSS
./index.css,./colors.css, plus three granulartheme-*.cssentries. Great DS libraries have one. Every "here's another way" paragraph is a sign the library isn't opinionated enough yet. Once #6 and #7 land, this collapses to a two-line setup (@import 'tailwindcss';followed by@import '@hanzo/quill';). Fix: rewrite the README around a single import story. Move granular controls to an "Advanced" section at the bottom.
Tests and verification (from the previous conversation but still missing)
-
18. Contrast tests at the token level Parameterized unit test that iterates known foreground/background pairs and asserts WCAG AA. Lives in
@hanzo/quill-tokens. Ten lines of test code, catches every "someone tweaked--primary" regression at the source before any component rebuilds. -
19. Storybook +
@storybook/addon-a11yaxe-core in CI Run via@storybook/test-runneragainst every story in both light and dark. Catches contrast regressions in composition, not just raw tokens. -
20. Visual regression via Chromatic / Playwright screenshots Cover every Button variant × state (default / hover / focus / disabled) × theme. Catches the "technically contrast-compliant but looks broken" class of bug.
-
21. Pack-and-install smoke test Run
pnpm --filter @hanzo/quill pack, install the resulting tarball into a disposable temp app, and render<Button />. Hook it up in CI. Catches everyexportsresolution failure, missingdistfile, missing peer dep, and "works in the monorepo but breaks on npm" bug. This is the single most valuable test for a library that ships to external consumers. -
22. Acceptance test: the
Codeconsumer'squill.cssshrinks Pick an absolute line count target for the consumer file after these changes. Without a target, there's no way to know whether the work actually solved the original problem.
Process failures from this session (don't repeat)
-
I ran
pnpm buildand declared success. For a CSS + theme change that's worse than no test — it gives a false sense of safety. Rule going forward: any CSS change must include a Storybook run in both themes before commit. -
I didn't check if
apps/storybookorapps/webimport the CSS files I was modifying. "Works in isolation, breaks in the monorepo" is a completely preventable class of bug. Always grep every consumer of a file before changing its contract. -
I removed
./styles.cssfrom primitives exports without a deprecation shim. At0.1.0-alpha.0it's defensible, but the habit is bad — leave a deprecated alias for one release and warn.
Priority ordering
If tackling one at a time, recommended order:
- #2 + #3 (hidden coupling) — resolved incidentally by #7 landing;
the split theme files never shipped on master and the pre-compiled
pipeline now owns all
@importchaining. Verify and close. - #18 (token contrast tests) — 30 minutes, protects the whole palette.
- #21 (pack-and-install smoke test) — protects the new
@hanzo/quillaggregate against future regressions. - #4 + #5 (rem base audit + version bump) — blocks a real release.
- #17 (README rewrite around a single import story) — now that there's only one public entry, the README should reflect it.
- Everything else — hygiene, can be done opportunistically.
Grade after this branch: B+. The headline wins (#7 pre-compile, #9 single-package collapse, #10/#11/#12 peer-dep cleanup) are in. The remaining gap to A is mostly testing infrastructure (#18, #21) and documentation (#17).