- 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]>
72 lines
7.7 KiB
Markdown
72 lines
7.7 KiB
Markdown
# 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`. Never `npm`/`npx`/`yarn`/`pnpm`.
|
|
- **Build:** `tsc` emits `src/` → `dist/` (JS + `.d.ts`), then `scripts/post-compile.ts` runs. There is no Next.js, Vite, PostCSS config or `tailwind.config` in this repo - don't add them.
|
|
- **Styling:** Tailwind v4 via `src/base.css` (`@import "tailwindcss"`, `@source "./"`, `@theme inline` tokens). `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 to `dist/*.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 `exports` entries.** `"./*"` can't resolve `form/Input` → `form/Input/index.js`. If you add a component as `Foo/index.tsx`, add a matching entry in `package.json` `exports` (see `./form/Input`, `./editors/TinyMCE`, `./composites/docs`).
|
|
- **Keep the barrel in sync.** A new component/hook/util must be added to `src/index.ts` as `export { default as Name } from "./path"` (plus `export * 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 typecheck` reports clashes as TS2308.
|
|
- **`twui-*` class names are API too.** Every component appends a stable `twui-<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-...` (see `src/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 in `src/base.css` `@theme inline`. Don't hardcode hex colors in components; if a new token is needed, add it to `base.css` with a sensible default.
|
|
|
|
## Dependencies
|
|
|
|
- `dependencies`: only `tailwind-merge` and `lodash`. Keep this list minimal.
|
|
- `peerDependencies`: `react`, `react-dom`, `tailwindcss` (^4), `lucide-react`.
|
|
- Feature-specific packages are **optional peers** (`peerDependenciesMeta.optional`) and also listed in `devDependencies` so the repo typechecks. Follow that pattern for any new third-party package used by a single feature, and update the optional-peers table in `README.md`.
|
|
- Don't let optional peers leak into `.d.ts` files reachable from the barrel (e.g. `src/types.ts` must not import from `ace-builds`).
|
|
|
|
## Conventions
|
|
|
|
- **Relative imports only** inside `src/` - there is no `@/` path alias. Don't add file extensions in source; `post-compile.ts` adds `.js` to 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<...>` or `ComponentProps<typeof Stack>`) and spread the rest onto the root element.
|
|
- **Merge classes with `twMerge`**, with the caller's `className` last so it wins.
|
|
- **Constant unions** are exported as `as const` arrays plus a derived type (`TWUIButtonColors` / `TWUIButtonColorType`).
|
|
- **Don't write `"use client"` in source** - `post-compile.ts` adds it to every emitted module except the barrel, `types.js`, `utils/` and `(utils)/`. Keep `utils/` free of React/DOM-only code so it stays server-safe.
|
|
- **Env access:** never read `process.env` directly in components (it throws in browser bundles without `process`). Wrap each lookup in `try/catch` and keep the full literal `process.env.X` expression so bundlers can inline it - see `src/editors/TinyMCE/default-base-url.ts`.
|
|
- **SSR safety:** guard `window`/`document`/`localStorage` access inside effects or `typeof window !== "undefined"` checks, never at module top level.
|
|
- **Tests:** pure helpers in `src/utils/` get a colocated `*.test.ts` using `bun:test`. Test files are excluded from the build.
|
|
|
|
## Verifying a Change
|
|
|
|
1. `bun run typecheck` and `bun test src` pass.
|
|
2. For packaging/exports changes: `bun run compile`, then `bun pm pack` and install the tarball into a scratch project. Check that root and deep imports typecheck with `skipLibCheck: false`, and that `@import "@moduletrace/twui/base.css"` produces the component classes when run through the Tailwind CLI.
|