Files
zeekay bc4ddf332b chore: rebase the tree onto upstream master fa1a9a60
Mechanically re-derived: bin/debrand runs over a fresh import of
posthog/posthog@fa1a9a60d4 (2026-08-06).
The pipeline (.hanzo/workflows), the Dockerfiles and bin/docker*
entrypoints, compose files, LLM.md and the squashed initial migration
are restored from the shipped tree, same as the previous import.

The prune's import-closure guard kept 21 products the new core now
imports; dropping them is a refactor now, recorded rather than forced.
2026-08-06 01:25:36 -07:00

5.3 KiB

name, description
name description
building-react-quill-canvases Author the React + Quill implementation of a Insights canvas: the single-component contract, the allowed imports, Quill (Insights's design system) component and composition rules, theme-aware design tokens, loading skeletons, and the in-canvas date picker. Use after building-canvases has routed a canvas request to a React implementation — dashboards, data boards, forms, tools, or any canvas that should look native to Insights.

Building React + Quill canvases

The whole application is one React/TSX file (src/canvas.tsx in the source project). It must export default a single React component that takes no props — the host mounts it. Do not import react-dom or call createRoot.

Start from the working scaffold in references/starter-scaffold.md on a first build: it already wires the date picker, theme tokens, per-card skeletons, and correct typed-node result reading. Keep that wiring; replace the sample metric and layout.

Imports

Import only from: react, react-dom, react-dom/client, @hanzo/quill, recharts, lucide-react, dayjs. Anything else — including dynamic import(), require(), fetch(), <script> tags, or remote code — fails validation. Use @hanzo/quill for UI, recharts for charts, lucide-react for icons, dayjs for dates.

Quill component rules

A Insights data board must be built entirely from @hanzo/quill components — never a native control or a styled <div> standing in for one:

  • Dropdown/picker → Select (never a native <select>); button → Button (never <button>); text field → Input/Textarea; checkbox → Checkbox; label → Label.
  • Table → Table (TableHeader > TableRow > TableHead, then TableBody > TableRow > TableCell); panel → Card (CardHeader + CardTitle + CardContent); pill → Badge; titles → Heading; body → Text.
  • The only non-Quill tags allowed are plain layout <div>s and recharts elements.
  • Quill is built on Base UI: compose compound parts (Select + SelectTrigger/SelectContent/SelectItem), use controlled value + onValueChange, and swap a part's element with the render prop (e.g. <PopoverTrigger render={<Button …/>} />) instead of wrapping it.
  • Quill components are already themed — never restyle one with Tailwind classes or inline style; use their variant/size props. Put layout utilities (flex, grid, gap-4, p-4) on your own wrapper <div>s.
  • Buttons: default to variant="outline"; variant="primary" for the one main action only.

Styling and theme

  • Style with Tailwind utilities and Quill components; reserve inline style for genuinely dynamic runtime values (fixed sizes use arbitrary-value utilities like h-[280px]).
  • The canvas follows the user's Insights theme; a .dark class on the document root flips at runtime. Color only from the design-token utilities — surfaces bg-background bg-card bg-muted bg-primary bg-success bg-warning bg-info bg-destructive; text text-foreground text-muted-foreground text-card-foreground; borders border-border. Never a hardcoded hex or light-only color.
  • Status tokens invert the usual convention: the bare token (bg-success) is a pale background fill and -foreground (text-success-foreground) is the strong readable color. Colored text or icons always use the -foreground utility; a filled pill pairs bg-success text-success-foreground. Prefer the Quill Badge (variant="success"/"destructive") for deltas so you don't hand-pick.
  • bg-secondary, text-secondary, bg-accent, and bg-popover are not defined in the canvas — avoid them.
  • recharts strokes/fills use token CSS variables (stroke="var(--primary)", grid/axes in var(--border)/var(--muted-foreground)).
  • Write Unicode glyphs (curly quotes, ellipsis, arrows, emoji) as literal characters in JSX — \uXXXX escapes render verbatim in JSX text.

Loading, error, and empty states

Every data point renders a skeleton in its own Card while loading or refreshing: SkeletonText (matching lines and text-size className) for text/number values, Skeleton for blocks/charts. Drive isLoading off the data calls and set it true again on refresh; never show a blank or a jumping layout.

A failed query and an empty result are different states — never let one render as the other. .catch on every ph.query/ph.loadInsight must set an error state that renders visibly (the message plus a Retry button wired to the refresh nonce, as in the starter scaffold), not fall through to zeros, an empty chart, or a "no data yet" message. A query that silently swallows its error makes real breakage (a missing table, an auth failure, a bad query) look like missing data. Reserve the empty state for a query that succeeded with no rows.

Date window

A data board owns its own date control — render Quill's DateTimePicker (never a custom Select or native date input) inside a Popover whose trigger is a Quill Button. PopoverContent gets exactly className="w-auto p-0" and nothing is added to DateTimePicker beyond value/onApply/onCancel (it self-sizes; don't pass compact or widths). Re-run every query when the window changes — see the querying-canvas-data skill for feeding it into dateRange.