- Hooks: per-instance timers in useReady; cleanup for useIntersectionObserver; useWebSocket no longer reconnects after unmount/refresh, drains the queue and dispatches non-JSON messages - Modal/Popover/Dropdown: onClose only on close, Escape to close, no stray DOM props, hooks before early return, scrollbar-aware positioning, top-left/top-right dropdown positions - Elements: Toast/Search/StarRating per-instance timers, Pagination labels and empty state, Breadcrumbs titles, LinkList dividers/props, segment-wise active-link matching (new utils/link-matches-path), Table object cells - Form: Select with a default option is changeable, Input clear notifies controlled parents and copy works on textareas, Checkbox random label, NumberInputButtons runaway interval, upload prop/spread fixes - Layout: Button disabled while loading, Stack gap and Span truncate via inline styles, Img fallbackImageSrc, className/JSDoc fixes - Editors/MDX/AI: TinyMCE id/cleanup/stale handler/duplicate scripts, AceEditor debounce, markdown heights and frontmatter regex, last assistant message, AIPromptBlock prompt ref - Utils: fetchApi headers/FormData/fetchOptions/no mutation, base64 file names and read errors, slugify underscore trim, serializeQuery zero values - Tests for fetchApi, link matching, slugify and serializeQuery - AGENTS.md updated for the package layout Co-Authored-By: Claude Opus 5.5 <[email protected]>
7.7 KiB
7.7 KiB
Agent Instructions
Stack
- What this is:
@moduletrace/twui, a React component library for Tailwind CSS v4, published to the Moduletrace Gitea npm registry (https://git.tben.me/api/packages/moduletrace/npm/). It is consumed by other projects (e.g.presets/bunext-sqlite), so every change here is a change to a shared, published API. - Runtime/tooling: Bun, not Node. Use
bun install,bun run <script>,bun test,bunx. Nevernpm/npx/yarn/pnpm. - Build:
tscemitssrc/→dist/(JS +.d.ts), thenscripts/post-compile.tsruns. There is no Next.js, Vite, PostCSS config ortailwind.configin this repo - don't add them. - Styling: Tailwind v4 via
src/base.css(@import "tailwindcss",@source "./",@theme inlinetokens).tailwind-merge(twMerge) for class composition. - Icons:
lucide-react(peer dependency).
Commands
| Command | What it does |
|---|---|
bun run typecheck |
tsc --noEmit - must be clean before finishing any change |
bun test src |
Unit tests (src/**/*.test.ts) |
bun run compile |
Clean build into dist/ + post-compile step |
./publish.sh "msg" |
Typecheck → test → compile → git commit/push → bun publish |
publish.sh pushes and publishes - only run it when explicitly asked. Bump version in package.json before publishing.
Layout
| Path | Contents |
|---|---|
src/index.ts |
Root barrel (import { Button } from "@moduletrace/twui") |
src/base.css |
Base stylesheet + default theme tokens; shipped as @moduletrace/twui/base.css |
src/layout/ |
Primitives: Stack, Row, Container, Section, Button, H1-H5, P, Span, Link, Img, ... |
src/elements/ |
Composite UI: Card, Modal, Popover, Table, Tabs, Toast, Tag, LucideIcon, ... |
src/elements/ai/ |
AI prompt blocks (optional peers: openai types, react-markdown) |
src/form/ |
Form, Input (folder index), Select, SearchSelect, Textarea, Checkbox, uploads |
src/hooks/ |
useStatus, useLocalStorage, useWebSocket, ... |
src/editors/ |
AceEditor (optional ace-builds), TinyMCE (folder index, loaded from a URL at runtime) |
src/mdx/, src/next-js/ |
MDX helpers (optional next-mdx-remote, remark-gfm, rehype-prism-plus) |
src/composites/docs/ |
Docs page layout pieces |
src/utils/ |
Pure helpers (slugify, numberfy, ejson, fetchApi, ...) with colocated tests |
src/(partials)/, src/(functions)/, src/(utils)/ |
Internal helpers, not meant for direct import |
src/types.ts |
Shared types/constants |
scripts/post-compile.ts |
Copies base.css, adds .js to relative imports, prepends "use client" |
Public API Rules (MANDATORY)
- Deep import paths are public API. Consumers import
@moduletrace/twui/<folder>/<File>(the"./*"export maps todist/*.js). Renaming or moving a file, or renaming an exported name, is a breaking change - avoid it, or call it out and bump the version accordingly. - Folder-index modules need explicit
exportsentries."./*"can't resolveform/Input→form/Input/index.js. If you add a component asFoo/index.tsx, add a matching entry inpackage.jsonexports(see./form/Input,./editors/TinyMCE,./composites/docs). - Keep the barrel in sync. A new component/hook/util must be added to
src/index.tsasexport { default as Name } from "./path"(plusexport * from "./path"if it exports types/constants). Exception: anything that imports an optional peer (editors, mdx, next-js,elements/ai,RemoteCodeBlock) stays deep-import only - the barrel must never pull in optional peers. - Exported names must be unique across the barrel. Name props types after their component (
TWUIButtonProps,TWUI_TABS_PROPS), never copy another component's type name.bun run typecheckreports clashes as TS2308. twui-*class names are API too. Every component appends a stabletwui-<component>[-variant]class (e.g.twui-button-primary,twui-card) that consumers target in their own CSS. Keep existing ones; add one for new components/variants and list it in the component's JSDoc as@className twui-...(seesrc/elements/Card.tsx).- Theme tokens are API. Components use semantic token utilities (
bg-primary,hover:bg-primary-hover,dark:bg-primary-dark,text-foreground-light, ...) defined insrc/base.css@theme inline. Don't hardcode hex colors in components; if a new token is needed, add it tobase.csswith a sensible default.
Dependencies
dependencies: onlytailwind-mergeandlodash. Keep this list minimal.peerDependencies:react,react-dom,tailwindcss(^4),lucide-react.- Feature-specific packages are optional peers (
peerDependenciesMeta.optional) and also listed indevDependenciesso the repo typechecks. Follow that pattern for any new third-party package used by a single feature, and update the optional-peers table inREADME.md. - Don't let optional peers leak into
.d.tsfiles reachable from the barrel (e.g.src/types.tsmust not import fromace-builds).
Conventions
- Relative imports only inside
src/- there is no@/path alias. Don't add file extensions in source;post-compile.tsadds.jsto the emitted output. - One component per file, default export, named in PascalCase matching the file (hooks/utils use their function name). Prop types extend the underlying element's props (
DetailedHTMLProps<...>orComponentProps<typeof Stack>) and spread the rest onto the root element. - Merge classes with
twMerge, with the caller'sclassNamelast so it wins. - Constant unions are exported as
as constarrays plus a derived type (TWUIButtonColors/TWUIButtonColorType). - Don't write
"use client"in source -post-compile.tsadds it to every emitted module except the barrel,types.js,utils/and(utils)/. Keeputils/free of React/DOM-only code so it stays server-safe. - Env access: never read
process.envdirectly in components (it throws in browser bundles withoutprocess). Wrap each lookup intry/catchand keep the full literalprocess.env.Xexpression so bundlers can inline it - seesrc/editors/TinyMCE/default-base-url.ts. - SSR safety: guard
window/document/localStorageaccess inside effects ortypeof window !== "undefined"checks, never at module top level. - Tests: pure helpers in
src/utils/get a colocated*.test.tsusingbun:test. Test files are excluded from the build.
Verifying a Change
bun run typecheckandbun test srcpass.- For packaging/exports changes:
bun run compile, thenbun pm packand install the tarball into a scratch project. Check that root and deep imports typecheck withskipLibCheck: false, and that@import "@moduletrace/twui/base.css"produces the component classes when run through the Tailwind CLI.