Files
wehub-resource-sync da10c3c2c3
Publish Any Commit / build (22) (push) Failing after 1s
Build Extension / build (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:20:35 +08:00

103 lines
6.6 KiB
Markdown

## General Rules
- MUST: Use TypeScript interfaces over types.
- MUST: Keep all types in the global scope.
- MUST: Use arrow functions over function declarations.
- MUST: Default to NO comments. Only add a comment when the user explicitly asks, or when the "why" is truly non-obvious - browser quirks, platform bugs, performance tradeoffs, fragile internal patching, or counter-intuitive design decisions. Never add comments that restate what the code does or what a well-named function/variable already conveys. When in doubt, leave the comment out.
- If a hack is required (like a `setTimeout` that hides a race), prefix with `// HACK: reason for hack`.
- Do not delete descriptive comments >3 lines without confirming with the user.
- MUST: Use kebab-case for files.
- MUST: Use descriptive names for variables (avoid shorthands, or 1-2 character names).
- Example: for `.map()`, use `innerNode` instead of `n`.
- Example: instead of `moved` use `didPositionChange`.
- MUST: Frequently re-evaluate and refactor variable names to be more accurate and descriptive.
- MUST: Do not type cast (`as`) unless absolutely necessary.
- MUST: Remove unused code and don't repeat yourself.
- MUST: Always search the codebase, think of many solutions, then implement the most _elegant_ solution.
- MUST: Put all magic numbers in `constants.ts` using `SCREAMING_SNAKE_CASE` with unit suffixes (`_MS`, `_PX`).
- MUST: Put small, focused utility functions in `utils/` with one utility per file.
- MUST: Use `Boolean(x)` over `!!x`.
## Preact + @preact/signals Rules
React Scan's overlay UI runs as a Preact app mounted inside a Shadow DOM (see [`packages/scan/src/core/index.ts`](packages/scan/src/core/index.ts)). Reactivity is driven by `@preact/signals`, not React state.
### Signals
- MUST: Read signals via `.value` inside JSX/effects: `signalWidget.value` not `signalWidget()`.
- MUST: Write signals atomically: `signalWidget.value = { ...signalWidget.value, dimensions: ... }`. One signal per logical slice.
- MUST: Use `useSignal(initial)` for component-local reactive state, `useComputed(() => ...)` for derived values, `useSignalEffect(() => ...)` for subscriptions/DOM imperative work.
- MUST: Wrap module-level signals exported from a tree-shaken bundle with `/* @__PURE__ */ signal(...)` so production builds can drop unused state.
- SHOULD: Persist user-facing state via `readLocalStorage`/`saveLocalStorage` from [`~web/utils/helpers`](packages/scan/src/web/utils/helpers.ts) and seed the signal with the persisted value.
- NEVER: Mirror one signal into another inside `useSignalEffect` - find the single source of truth (compute on read).
### Effects
Before reaching for `useEffect`/`useSignalEffect`, classify the work:
- MUST: Use `useComputed` when the result is pure derived state from other signals. If no external system is touched, it is not an effect.
- MUST: Use event handlers and direct action calls when work happens because the user clicked, dragged, or navigated. Do not watch a flag in an effect to trigger imperative logic.
- MUST: Use `useEffect(..., [])` (or `useSignalEffect` with no deps) for one-time mount/cleanup of subscriptions, timers, listeners, and `MutationObserver`s. Always return the cleanup.
- MUST: Keep each effect single-purpose - one effect, one external bridge. Split mixed-responsibility effects.
- NEVER: Use an effect just to copy one signal into another.
- NEVER: Use an effect as an event bus (watching a trigger signal to run a command). Call the action directly from the event source.
### Props
- MUST: Access props via `props.title`, not destructuring, when the prop is read inside an event handler that fires later or inside an effect.
- SHOULD: Destructure props at the top of the body for purely-rendered values (read once during render).
- NEVER: Destructure a prop that is itself a signal-driven slice if you intend to re-read it after async work.
### JSX & DOM
- MUST: Use `className` (we render via Preact's React-compat JSX in [`tsconfig.json`](tsconfig.json) `jsxImportSource: preact`).
- MUST: Combine static `className="btn"` with reactive `className={cn("btn", isActive.value && "active")}` via [`cn`](packages/scan/src/web/utils/helpers.ts).
- MUST: Read refs in `useEffect` or via the callback ref pattern - DOM refs are populated after render.
- MUST: Mount overlay UI under the existing shadow root from `initRootContainer()` in [`core/index.ts`](packages/scan/src/core/index.ts). Do not append directly to `document.body`.
- SHOULD: Use `style={{ "--css-var": value }}` for dynamic CSS variables; class toggles for boolean states.
- SHOULD: Type refs as `let element: HTMLElement | null = null` with a guard.
## Build & Toolchain
This is a pnpm 10 monorepo with `packages/*` (libraries, extension, website) and a top-level `kitchen-sink/` (Playwright target). The toolchain is [Vite+](https://viteplus.dev) (`vp lint`, `vp fmt`, `vp check`) and `turbo` for pipeline orchestration.
### Approved built dependencies
The root [`package.json`](package.json) declares `pnpm.onlyBuiltDependencies` for `@parcel/watcher`, `esbuild`, `sharp`, `spawn-sync`, `unrs-resolver`. Without this list, `pnpm install` skips their native build steps and downstream packages fail.
### Build before test
`pnpm build` must complete before `pnpm test`, `pnpm test:e2e`, or `pnpm lint`. After modifying source files, always rebuild before running tests. Turbo enforces this via `dependsOn: ["^build"]` in [`turbo.json`](turbo.json).
### Playwright
`pnpm test:e2e` runs Playwright against the `kitchen-sink` Vite dev server on port 5173 (auto-started by [`playwright.config.ts`](playwright.config.ts)). Chromium must be installed: `npx playwright install chromium --with-deps`.
### Key commands
| Task | Command |
| -------------- | -------------------------------------------- |
| Install | `pnpm install` |
| Build | `pnpm build` |
| Dev watch | `pnpm dev` (watches `react-scan` + kitchen-sink) |
| Unit tests | `pnpm test` |
| E2E tests | `pnpm test:e2e` |
| Lint | `pnpm lint` (oxlint via vite-plus) |
| Lint + fix | `pnpm lint:fix` |
| Format | `pnpm format` (oxfmt via vite-plus) |
| Format check | `pnpm format:check` |
| Typecheck | `pnpm typecheck` |
| Combined | `pnpm check` (lint + fmt check + typecheck) |
## Testing
Run checks always before committing with:
```bash
pnpm build
pnpm lint
pnpm format
pnpm typecheck
pnpm test:e2e
```