diff --git a/.claude/skills/code-review/references/gotchas.md b/.claude/skills/code-review/references/gotchas.md new file mode 100644 index 0000000..7791888 --- /dev/null +++ b/.claude/skills/code-review/references/gotchas.md @@ -0,0 +1,64 @@ +# Gotchas — Common Mistakes in CoreMQ + +## Comment style + +### Rust (`server/coremq-server/src/**/*.rs`) + +- **Block comments only** — Use `/* */`, never `//` line comments. Project rule. +- Multi-line: + ```rust + /* + * Assigns the next packet ID for QoS 1/2 publishes. + * Returns None if the packet ID counter has wrapped. + */ + ``` +- Do NOT use `// inline` or doc comments (`///`). Convert to `/* */` above the line, or delete if redundant. + +### TypeScript (`client/src/**/*.{ts,tsx}`) + +- **JSDoc only** — `/** */`, never `//` or `/* */` (non-JSDoc). Project rule. +- Single-line: `/** Disconnect the active session. */` is fine for one-liners. +- Do NOT use `// inline` or `// end-of-line` comments. + +## Rust backend + +- **`unwrap()` on `Option::None` panics** — especially around `packet_id` for QoS 1/2 publishes. Always use `ok_or(...)` or `if let Some(...)`. The `fix-panic_id-none-server-panic-bug` branch was created for exactly this class of bug. +- **AdminCommand wiring** — every new command needs all 7 steps wired (model → command variant → service method → engine match arm → controller → route → mod.rs). Forgetting the engine match arm causes the controller's `oneshot::Receiver` to hang forever. +- **Always reply on the oneshot** — the engine's `match` arm MUST send a response back through the `reply` sender, even on error paths. A dropped sender causes the controller to receive `RecvError` and return 500. +- **No blocking calls in async code** — `std::fs`, `std::thread::sleep`, `std::sync::Mutex` (long held) all block the Tokio runtime. Use `tokio::fs`, `tokio::time::sleep`, `tokio::sync::Mutex`. +- **Bounded channels for backpressure** — Use `mpsc::channel(N)` not `unbounded_channel()` for hot paths (publish, connect). Unbounded channels can OOM under load. +- **DashMap iteration locks shards** — never hold a `DashMap::iter()` guard across an `.await` point. Collect keys first, then process. +- **ReDB transactions** — `WriteTransaction` must be committed or it silently rolls back. Always end with `txn.commit()?`. + +## Frontend (React + TypeScript) + +- **`type` over `interface`** — Always. The only exception is `extend-theme-types.d.ts` (MUI module augmentation requires `interface`). +- **`export default function`** — All page and section components. +- **No raw colors** — Never hardcode hex/rgb in `sx`. Use theme tokens: `sx={{ bgcolor: 'background.paper' }}` not `sx={{ bgcolor: '#131825' }}`. +- **Responsive padding always** — `sx={{ p: { xs: 2, sm: 3 } }}`, never `sx={{ p: 3 }}`. +- **Pages are thin** — `pages/foo.tsx` should ONLY import and render the section view. Logic belongs in `sections/foo/foo_view.tsx`. +- **i18n is mandatory** — Any user-facing string must be `t('key')`. Adding a key requires updates to ALL THREE: `118n/en.json`, `118n/ko.json`, `118n/uz.json`. Missing one is a must-fix. + +## Zustand stores + +- **`reset()` is required** — every store must have `reset: () => set(initialState)` for logout cleanup. Missing reset = stale data after re-login. +- **Separate State and Actions types** — State is data only, Actions is functions only. Don't merge them. +- **Shared `initialState` object** — defined once, used as both default and for `reset()`. Don't duplicate the literals. +- **Selectors over full subscription** — `useFooStore(s => s.items)`, not `const { items } = useFooStore()`. The latter re-renders on every store change. + +## API layer + +- **Always wrap in `ApiResponse`** — services return `ApiResponse`, not raw `T`. The wrapper carries `success`, `data`, `error_message`. +- **Token refresh is automatic** — don't manually handle 401 in services; the axios interceptor does it. Adding manual refresh logic causes double-refresh races. +- **Bearer token from cookies** — never read `localStorage` for auth. The interceptor attaches the bearer from cookie helpers. + +## Theme & MUI + +- **Drawers use `bgcolor: '#131825'` literally** — this is the one documented exception. Everywhere else: theme tokens. +- **`JetBrains Mono Variable` for monospace data** — client IDs, topic names, ports. Don't use the default font. + +## Build / Tooling + +- **`Cargo.lock` is committed** — for reproducible broker builds. Don't add it to `.gitignore`. +- **Frontend uses `yarn`** (per Makefile) — `yarn dev`, `yarn install`. Don't switch to `npm install` casually; it produces a different lockfile and CI may break. +- **Default ports** — MQTT TCP `1883`, MQTT TLS `8883`, WS `8083`, REST `18083`, frontend dev `3039`. Don't hardcode different values in tests. diff --git a/.claude/skills/code-review/skill.md b/.claude/skills/code-review/skill.md new file mode 100644 index 0000000..4f5a6f2 --- /dev/null +++ b/.claude/skills/code-review/skill.md @@ -0,0 +1,26 @@ +--- +name: code-review +description: Review changed code for CoreMQ project conventions — catches gotchas linters miss like wrong comment style, missing i18n keys, hardcoded colors, AdminCommand wiring mistakes, Zustand store leaks. TRIGGER when user asks to review, check, or audit code they wrote. +--- + +# CoreMQ Code Review + +Review changed files against the project rules in `.claude/skills.md` and the codebase-specific pitfalls in `references/gotchas.md`. + +## Steps + +1. Run `cargo clippy --all-targets -- -D warnings` (server) and `npm run lint` + `npx prettier --check "src/**/*.{ts,tsx}"` (client) to catch automated issues +2. Read each changed file and check against `.claude/skills.md` rules and `references/gotchas.md` +3. Report findings grouped by severity: **must fix**, **should fix**, **nit** + +Focus on things linters cannot catch: missing oneshot reply on an `AdminCommand` arm, blocking syscalls in async code, hardcoded MUI colors instead of theme tokens, i18n keys missing from one of `en.json`/`ko.json`/`uz.json`, Zustand store missing `reset()`, panics from `unwrap()` on `Option::None` (especially around `packet_id`). + +## Output + +``` +[must fix|should fix|nit] file:line — description +``` + +Fix **must fix** and **should fix** automatically. Ask before fixing nits. + +If more than 5 files, spawn a subagent for the review and implement fixes based on its findings. diff --git a/.claude/skills/coremq-guide/skill.md b/.claude/skills/coremq-guide/skill.md new file mode 100644 index 0000000..99dd550 --- /dev/null +++ b/.claude/skills/coremq-guide/skill.md @@ -0,0 +1,84 @@ +--- +name: coremq-guide +description: Reference for navigating the CoreMQ project — directory layout, build commands, default ports, where things live across the Rust broker and React dashboard. TRIGGER when user asks about project structure, where to put files, or how the server and client connect. +--- + +# CoreMQ Project Guide + +Cargo workspace + Vite-React app. One Rust crate (the broker), one TS app (the dashboard). + +For deep conventions (TypeScript types, Zustand store shape, API routes, theme tokens, AdminCommand pattern), see `.claude/skills.md` — it's the canonical project doc. + +## Structure + +``` +coremq-rust/ +├── server/coremq-server/ — Rust MQTT broker (Tokio, Axum, ReDB, Casbin) +│ └── src/ +│ ├── api/ — Axum REST API (controllers, router, auth) +│ ├── engine/ — Core event loop + command enums + workers +│ ├── services/ — session, topic, jwt +│ ├── protocol/ — MQTT 3.1.1 / 5 wire protocol +│ ├── transport/ — TCP / TLS / WebSocket listeners +│ ├── storage/ — ReDB persistence +│ ├── models/ — Serde structs (api/, engine/) +│ └── main.rs +├── client/ — React 19 + TS + MUI 7 + Zustand admin dashboard +│ └── src/ +│ ├── pages/ — thin route wrappers +│ ├── sections/ — feature views (UI + logic) +│ ├── stores/ — Zustand state +│ ├── services/ — axios API calls +│ ├── types/ — TypeScript types +│ ├── theme/ — MUI dark theme +│ └── 118n/ — en.json, ko.json, uz.json +├── docs/ — ARCHITECTURE.md, COREMQ_AI_NATIVE_PLATFORM.md, drawio diagrams +├── tests/ — integration / stress / qos tests +├── Cargo.toml — workspace root +├── Makefile — `make dev` / `server` / `client` / `fmt` / `lint` / `fix` +└── .claude/skills.md — full project conventions +``` + +## Common Commands + +| What | Command | +| --------------------- | ------------------------------------------------ | +| Run both | `make dev` | +| Run broker only | `make server` (= `cargo run -p coremq-server`) | +| Run frontend only | `make client` (= `cd client && yarn dev`) | +| Install everything | `make setup` | +| Build broker | `cargo build -p coremq-server` | +| Test broker | `cargo test -p coremq-server` | +| Format Rust | `cargo fmt -p coremq-server` | +| Lint Rust | `cargo clippy -p coremq-server --all-targets` | +| Format frontend | `make fmt` | +| Lint frontend | `make lint` | +| Lint + format fix | `make fix` | + +## Default Ports + +| Service | Port | +| ------------- | ------- | +| MQTT TCP | `1883` | +| MQTT TLS | `8883` | +| MQTT WebSocket| `8083` | +| REST API | `18083` | +| Frontend dev | `3039` | + +## Default Credentials + +Username: `admin` / Password: `public` + +## Cross-cutting Wiring + +The broker and dashboard talk over two channels: + +- **REST API** (`http://localhost:18083/api/v1/...`) — admin operations: list sessions/topics/listeners/users, publish, login. See `server/coremq-server/src/api/router.rs` for the full route table. +- **MQTT WebSocket** (`ws://localhost:8083`) — the dashboard speaks MQTT directly to subscribe to live topic updates. See `client/src/sections/websocket/`. + +## Gotchas + +- `Cargo.lock` IS committed (binary crate — needs reproducible builds). +- Frontend uses **`yarn`**, not `npm install` (Makefile uses yarn, lockfile is `yarn.lock`). +- `target/` and `client/node_modules/` are gitignored — don't commit either. +- See `.claude/skills/debug-coremq/skill.md` for runtime issue diagnosis. diff --git a/.claude/skills/debug-coremq/skill.md b/.claude/skills/debug-coremq/skill.md new file mode 100644 index 0000000..bdfa678 --- /dev/null +++ b/.claude/skills/debug-coremq/skill.md @@ -0,0 +1,48 @@ +--- +name: debug-coremq +description: Diagnose CoreMQ broker, REST API, MQTT client, or React dashboard issues — common errors, port conflicts, auth failures, QoS panics, WebSocket disconnects. TRIGGER when user reports a bug, error, or unexpected behavior. +--- + +# Debug CoreMQ + +Systematic debugging for the CoreMQ MQTT broker and admin dashboard. + +## Symptom → Investigation Map + +| Symptom | First check | Then check | +| -------------------------------------- | --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| Broker won't start | `lsof -iTCP:1883 -sTCP:LISTEN` — is another process holding the port? | Check `cargo run` output for `bind` errors and ReDB lock messages | +| `cargo run` panics on QoS publish | Look for `unwrap()` on `packet_id` — must be a known bug class | Check the `fix-panic_id-none-server-panic-bug` branch / commit `2f10d8d` for fix | +| MQTT client disconnects immediately | `cargo run` log: auth failure? Casbin policy missing? | Verify default `admin/public` creds and that JWT secret env var is set | +| REST API returns 401 after login | Token cookie not being attached — check axios interceptor + cookie helpers | Verify `/api/v1/public/login` returns the token and the refresh endpoint works | +| REST controller hangs forever | The engine `match` arm is not sending on the `oneshot::Sender` — must reply | grep for the `AdminCommand` variant in `engine/engine.rs` `run()` loop | +| Frontend shows stale data after logout | Some Zustand store is missing `reset()` | Check every store in `client/src/stores/` has `reset: () => set(initialState)` | +| MQTT WebSocket fails to connect | Is `:8083` listening? Browser console: CORS or upgrade failure? | Check `client/src/sections/websocket/` for the connect logic and broker URL | +| Topic publish doesn't match subscriber | Wildcard order — `+` matches one level, `#` matches multi (must be at end) | Check `services/topic.rs` matcher; verify subscriber QoS is supported by broker | +| `cargo build` fails on macOS | Toolchain stale: `rustup update` | `cargo clean` if proc-macro errors persist | +| Frontend dev server port conflict | `lsof -iTCP:3039 -sTCP:LISTEN` — kill the stale process | Check `vite.config.ts` for the configured port | +| i18n key shows as raw `feature.title` | Key missing from one of `118n/en.json`, `118n/ko.json`, `118n/uz.json` | Add to ALL THREE files — partial translations cause this | + +## Quick Health Check + +```bash +# Backend reachable? +curl -s http://localhost:18083/api/v1/public/login -X POST \ + -H 'content-type: application/json' \ + -d '{"username":"admin","password":"public"}' | jq . + +# MQTT TCP listening? +nc -zv localhost 1883 + +# Frontend dev server up? +curl -s http://localhost:3039 -o /dev/null -w '%{http_code}\n' +``` + +## Gotchas + +- **`packet_id` must be assigned for QoS 1/2 publishes** — missing assignment caused the server panic fixed in `2f10d8d`. Always set before passing to the engine. +- **ReDB write lock is exclusive** — only one `WriteTransaction` at a time. Long-held write txns block all writers. +- **DashMap iteration across `.await` deadlocks** — collect keys first, drop the iter, then await. +- **Token refresh races** — only the axios interceptor handles 401. Don't add manual refresh in services. +- **Frontend uses `yarn`, not `npm install`** — using `npm install` will rewrite lockfile and may break CI. +- **`Cargo.lock` IS committed** — don't `.gitignore` it; the broker is a binary and needs reproducible deps. diff --git a/.claude/skills/new-backend-feature/skill.md b/.claude/skills/new-backend-feature/skill.md new file mode 100644 index 0000000..ffac839 --- /dev/null +++ b/.claude/skills/new-backend-feature/skill.md @@ -0,0 +1,47 @@ +--- +name: new-backend-feature +description: Scaffold a new admin feature in the Rust broker following the AdminCommand → Engine → Service → Controller → Route pattern. TRIGGER when user asks to add, create, or scaffold a new backend feature, REST endpoint, or admin operation. +--- + +# Scaffold New Backend Feature (CoreMQ) + +Every admin operation in CoreMQ flows through the same 7-step wiring. Skipping any step causes hard-to-find bugs (controller hangs, command never runs, route 404s). + +Read `.claude/skills.md` "Engine Command Pattern" before starting. Follow Rust conventions: `/* */` block comments only, snake_case functions, PascalCase types. + +## Before Scaffolding + +1. Check `server/coremq-server/src/api/router.rs` — does a similar route already exist? +2. Check `server/coremq-server/src/engine/commands.rs` — could you extend an existing variant instead of adding a new one? +3. Decide: does this need a oneshot reply (synchronous) or fire-and-forget (rare)? Default: oneshot. + +## The 7 Steps + +1. **Model** — `server/coremq-server/src/models/api/.rs`. Request/response structs with `Serialize`/`Deserialize`. Add `pub mod ;` to `models/api/mod.rs`. +2. **Command variant** — Add to `AdminCommand` enum in `server/coremq-server/src/engine/commands.rs`. Include the request payload + a `reply: oneshot::Sender>`. +3. **Service method** — Add to the relevant service in `server/coremq-server/src/services/`. Pure logic, no channels. Returns `Result`. +4. **Engine handler** — Add a `match` arm in `engine/engine.rs` `run()` loop. Calls the service, then `let _ = reply.send(result);`. **Always reply, even on error.** A dropped sender = hung controller. +5. **Controller** — `server/coremq-server/src/api/controllers/.rs`. Creates `oneshot::channel()`, sends the `AdminCommand`, awaits the reply, wraps in `ApiResponse`. Add `pub mod ;` to `controllers/mod.rs`. +6. **Route** — Register in `api/router.rs` with method + path + auth layer. Follow the `/api/v1/` convention. +7. **Auth policy** — If the route is admin-protected, add the Casbin policy entry. Check existing routes for the pattern. + +## Verify + +1. `cargo check -p coremq-server` — compiles? +2. `cargo clippy -p coremq-server --all-targets -- -D warnings` — clean? +3. Run the broker, hit the new endpoint with `curl`: + ```bash + TOKEN=$(curl -s -X POST http://localhost:18083/api/v1/public/login \ + -H 'content-type: application/json' \ + -d '{"username":"admin","password":"public"}' | jq -r .data.access_token) + + curl -s http://localhost:18083/api/v1/ \ + -H "authorization: Bearer $TOKEN" | jq . + ``` + +## Gotchas + +- **Never drop the `oneshot::Sender` without sending.** Forgetting `reply.send(...)` in an error path = controller hangs until timeout. +- **The engine `run()` loop is single-threaded.** Don't call long-blocking work from a match arm — spawn a task or call into a service that uses a worker pool. +- **`AdminCommand` variants must be exhaustively matched** — adding a variant without an arm is a compile error, which is the bug you want. +- **Frontend wiring is separate** — after the backend is done, see `.claude/skills/web-page/skill.md` for the React side (service function + Zustand store + section view). diff --git a/.claude/skills/verify/skill.md b/.claude/skills/verify/skill.md new file mode 100644 index 0000000..8f7f54c --- /dev/null +++ b/.claude/skills/verify/skill.md @@ -0,0 +1,37 @@ +--- +name: verify +description: Run the full CoreMQ verification suite — cargo check/clippy/test on the Rust broker, lint and prettier on the React client. TRIGGER when user asks to verify, test, check, or validate changes before committing. +--- + +# CoreMQ Verification + +Run all checks in order. Stop on first failure. Report a one-line PASS/FAIL per step. + +## Steps + +### Backend (`server/coremq-server`) + +1. **Type check**: `cargo check -p coremq-server` +2. **Lint**: `cargo clippy -p coremq-server --all-targets -- -D warnings` +3. **Format check**: `cargo fmt -p coremq-server -- --check` +4. **Tests**: `cargo test -p coremq-server` + +### Frontend (`client`) + +5. **Lint**: `cd client && npx eslint "src/**/*.{js,jsx,ts,tsx}"` +6. **Format check**: `cd client && npx prettier --check "src/**/*.{ts,tsx}"` +7. **Type + build**: `cd client && yarn build` + +### Final + +8. **Git status**: ensure no uncommitted untracked artifacts (e.g., `target/`, `dist/`, `node_modules/`) + +## Auto-fix + +- If `cargo fmt --check` fails, auto-fix with `cargo fmt -p coremq-server`. +- If prettier check fails, auto-fix with `cd client && npx prettier --write "src/**/*.{ts,tsx}"`. +- If eslint fails on auto-fixable rules, run `cd client && npm run lint:fix`. + +## Output + +Report one line per step: `PASS` or `FAIL` with a short error summary. After all steps, print a final summary. diff --git a/.claude/skills/web-page/skill.md b/.claude/skills/web-page/skill.md new file mode 100644 index 0000000..4ab87eb --- /dev/null +++ b/.claude/skills/web-page/skill.md @@ -0,0 +1,63 @@ +--- +name: web-page +description: Scaffold a new page or feature section in the CoreMQ React dashboard following the page→view→table/drawer + Zustand store + service pattern. TRIGGER when user asks to create, add, or build a new page, route, or dashboard section in the client. +--- + +# Scaffold New Frontend Page (CoreMQ) + +Create new pages and sections for `client/src/` following CoreMQ conventions. + +Read `.claude/skills.md` (Frontend Architecture, State Management, API Layer, Theme & Styling, i18n) before starting. Adhere to: `type` over `interface`, `export default function`, JSDoc-only comments, single quotes, theme tokens via `sx`, responsive padding `{ xs, sm }`. + +## Before Scaffolding + +1. Check existing routes in `client/src/routes/` — pick the right place to register. +2. Check existing sections in `client/src/sections/` — find the closest pattern (e.g. `topics/`, `session/`). +3. Read `.claude/skills.md` API Layer to confirm the backend endpoint exists. If not, scaffold the backend first via `.claude/skills/new-backend-feature/`. + +## Layout + +For a feature called `feature`, you create: + +``` +client/src/ +├── pages/feature.tsx — thin wrapper +├── sections/feature/ +│ ├── feature_view.tsx — orchestrator +│ ├── feature_table.tsx — table component (optional) +│ └── feature_drawer.tsx — drawer/dialog (optional) +├── stores/feature_store.ts — Zustand store +├── services/feature.ts — axios calls +├── types/feature.ts — TS types +└── routes/ — register the route +``` + +## Steps + +1. **Types** — `types/feature.ts`. Use `type` not `interface`. All API responses go through `ApiResponse` (defined in `types/api_response.ts`). +2. **Service** — `services/feature.ts`. Wrap axios calls; return `ApiResponse`. Don't handle 401 manually — the interceptor does it. +3. **Store** — `stores/feature_store.ts`. Split `FeatureState` (data) and `FeatureActions` (functions). Define `initialState`. Export `useFeatureStore`, selectors, and ensure `reset: () => set(initialState)`. Add to the barrel export `stores/index.ts`. +4. **Page** — `pages/feature.tsx`. Three lines: import view, default-export a function rendering ``. +5. **View** — `sections/feature/feature_view.tsx`. Subscribe to the store via selectors, fire `useEffect(() => fetch(), [])`, render layout with responsive padding `sx={{ p: { xs: 2, sm: 3 } }}`, error alert, loading/empty/data states, and any drawer. +6. **Table / Drawer** — Split out for reuse. Tables receive data via props and emit `onAction(item)`. Drawers manage form state internally and receive `open`/`onClose`/initial data. +7. **Route** — Register in `client/src/routes/`. +8. **Sidebar** — Add the entry to the dashboard navigation in `client/src/layouts/`. +9. **i18n** — Add ALL user-facing strings as keys in `client/src/118n/en.json` AND `ko.json` AND `uz.json`. Missing one is a bug. + +## Verify + +```bash +cd client +npx eslint "src/sections/feature/**" "src/stores/feature_store.ts" "src/services/feature.ts" +npx prettier --check "src/sections/feature/**/*.{ts,tsx}" +yarn dev # then open http://localhost:3039 and walk the feature +``` + +## Gotchas + +- **Hardcoded colors** — never `sx={{ bgcolor: '#abc123' }}`. Always theme tokens (`background.paper`, `text.primary`, `divider`). The one exception is drawers using `bgcolor: '#131825'` (documented in `.claude/skills.md`). +- **Pages must be thin** — three lines: import + default export + render. Logic in `_view`. +- **Selector subscriptions** — `useFeatureStore(s => s.items)`, never destructure the whole store; otherwise the component re-renders on every store mutation. +- **`reset()` is required** — without it, logout leaves stale data in the next user's session. +- **i18n triplet rule** — every key must exist in en/ko/uz. Missing = raw key shown in UI for one language. +- **`JetBrains Mono Variable`** for monospace data (client IDs, topic strings, port numbers) — don't use the default font. diff --git a/.gitignore b/.gitignore index beb49dd..6fbaf50 100644 --- a/.gitignore +++ b/.gitignore @@ -25,9 +25,12 @@ client/tsconfig.tsbuildinfo .DS_Store .vscode -# Root-level markdown except README +# Root-level markdown — only README and canonical project docs are tracked /*.md !README.md +!AGENTS.md +!CONTRIBUTING.md +!CHANGELOG.md # Local PR notes /pr/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9a35781 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,181 @@ +# CoreMQ — Agent Rules + +Project-wide rules for any AI coding agent working in this repo (Claude Code, Cursor, Codex, etc.). The canonical project conventions live in `.claude/skills.md`; the per-task playbooks live in `.claude/skills//skill.md`. This file is the rules-of-engagement layer on top of those. + +## Conversational Style + +- Keep answers short and concise. +- No emojis in commits, issues, PR comments, or code. +- No fluff, cheerful filler, or congratulatory text. +- Technical prose only; be kind but direct (e.g. "Thanks @user" not "Thanks so much @user!"). + +## Code Quality + +### Universal + +- No backwards-compatibility shims unless the user explicitly asks. +- Always ask before removing functionality or code that appears intentional, even if it looks dead. +- Never disable lints or hide warnings to make a build pass — fix the underlying issue, or surface it. +- Never hardcode default ports (`1883`, `8883`, `8083`, `18083`, `3039`) inside business logic. Use the broker config / Vite env / shared constants. +- No secrets in code. Default `admin/public` is for local dev only. + +### Rust (`server/coremq-server/`) + +- **Block comments only** — `/* */`, never `//` or `///`. Project rule. +- No `.unwrap()` on `Option::None` paths — particularly around `packet_id` for QoS 1/2. Use `ok_or(...)` / `if let Some(...)`. The `2f10d8d` commit exists because this rule was violated. +- No blocking syscalls in `async` functions (`std::fs`, `std::thread::sleep`, long-held `std::sync::Mutex`). Use `tokio::*` equivalents. +- Bounded channels (`mpsc::channel(N)`) on hot paths (publish, connect). `unbounded_channel()` is reserved for control-plane signals only. +- Every `AdminCommand` match arm in the engine MUST send on its `oneshot::Sender`, including error paths. A dropped sender hangs the controller until timeout. +- Never hold a `DashMap::iter()` guard across an `.await` point. Collect keys, drop the iterator, then await. +- `WriteTransaction` must always end in `txn.commit()?`. Uncommitted txns silently roll back. + +### TypeScript / React (`client/`) + +- `type` over `interface`. The only exception is `extend-theme-types.d.ts` (MUI module augmentation requires `interface`). +- `export default function ComponentName()` for all pages and section components. +- JSDoc-only comments (`/** */`). No `//`, no non-JSDoc `/* */`. +- Single quotes everywhere (enforced by prettier). +- No `any`. Check `node_modules` for upstream types instead of guessing. +- No hardcoded colors in `sx` — always theme tokens. Documented exception: drawer `bgcolor: '#131825'`. +- Responsive padding always: `sx={{ p: { xs: 2, sm: 3 } }}`. +- Pages are thin wrappers (import view + render). Logic lives in `sections//_view.tsx`. +- Zustand stores: split `State` (data) and `Actions` (functions), shared `initialState` constant, mandatory `reset: () => set(initialState)` for logout cleanup. Subscribe via selectors, not full destructure. +- Never call `localStorage` for auth — the axios interceptor owns token refresh. +- Every user-facing string is `t('key')`, and every key must exist in **all three** of `client/src/118n/{en,ko,uz}.json`. Missing one is a bug, not a nit. + +### Don't touch without coordination + +- `Cargo.lock` — committed on purpose (binary crate). Don't `.gitignore` it. +- `client/yarn.lock` — frontend uses yarn (per Makefile). Don't switch to `npm install`; it produces a different lockfile and CI may break. +- `.claude/skills/code-review/references/gotchas.md` — the canonical pitfall list. When you discover a new gotcha, add an entry there, don't bury it in a PR description. + +## Commands + +After code changes, run the appropriate verification suite. Get full output, no tail. Fix all errors, warnings, and infos before committing. + +| What changed | Run | +| ----------------------------- | ------------------------------------------------------------------------------ | +| Rust (server) | `cargo check -p coremq-server && cargo clippy -p coremq-server --all-targets -- -D warnings && cargo fmt -p coremq-server -- --check` | +| Frontend (client) | `cd client && npx eslint "src/**/*.{js,jsx,ts,tsx}" && npx prettier --check "src/**/*.{ts,tsx}"` | +| Both | Run the full skill: see `.claude/skills/verify/skill.md` | +| You wrote/changed Rust tests | `cargo test -p coremq-server ` and iterate until green | +| You wrote/changed integration | Tests live in `tests/`. Run them from the workspace root: `cargo test --test ` | + +### Don't run + +- `make dev` — long-running, holds two servers. Only the user starts this. +- `make server` / `cargo run -p coremq-server` — long-running broker. Same rule. +- `cd client && yarn dev` — long-running. Same rule. + +If you need to verify behavior end-to-end, ask the user to start the broker / dev server, then drive REST calls with `curl` or hit the dashboard manually. + +### Auto-fix is allowed + +- `cargo fmt -p coremq-server` — formatting +- `cd client && npx prettier --write "src/**/*.{ts,tsx}"` — frontend formatting +- `cd client && npm run lint:fix` — eslint auto-fixable rules + +### Never commit unless the user asks. + +## Skills & Project Guide + +Before starting work, consult the relevant skill. Each one is self-contained and references `.claude/skills.md` for deeper convention details. + +| Skill | When to use | +| ------------------------------------------- | -------------------------------------------------------------------------- | +| `.claude/skills/coremq-guide/` | Project layout, ports, build commands — orientation for new tasks | +| `.claude/skills/code-review/` | Reviewing changes against project conventions before committing or pushing | +| `.claude/skills/verify/` | Pre-commit / pre-push verification suite (cargo + eslint + prettier) | +| `.claude/skills/debug-coremq/` | Diagnosing broker, REST, MQTT, or dashboard issues | +| `.claude/skills/new-backend-feature/` | Scaffolding a new admin feature through the AdminCommand pattern | +| `.claude/skills/web-page/` | Scaffolding a new dashboard page / section / store | +| `.claude/skills.md` | Canonical CoreMQ conventions (Rust + React, API, theme, i18n) | + +## PR Workflow + +- Analyze PRs without pulling locally first (`gh pr view`, `gh pr diff`). +- The `.github/workflows/claude-code-review.yml` workflow auto-reviews every PR push within a 4000-line budget. Do not duplicate that review by hand unless the user asks. +- Tagging `@claude` in a PR or issue comment triggers `.github/workflows/claude.yml` for targeted asks. Use it for follow-ups, not for the initial review. +- If the user approves changes: create a feature branch, pull the PR's branch, rebase on `main`, apply adjustments, run the verify skill, commit, merge into `main`, push, close the PR with a short comment. +- You never open PRs unprompted. Work on a feature branch until it matches user requirements, then merge into `main` and push. + +## Issue / PR Comment Hygiene + +- Write the full comment to a temp file, then `gh issue comment --body-file` or `gh pr comment --body-file`. Never pass multi-line markdown directly via `--body`. +- Preview the exact comment before posting. +- Post exactly one final comment unless the user asks for multiple. +- If a comment is malformed, delete it immediately, then post one corrected comment. +- Keep comments concise, technical, and in the user's tone. + +When closing an issue via commit, include `fixes #` or `closes #` in the commit message. + +## Branch Naming + +Existing branches follow short, descriptive kebab-case names that hint at the change class (e.g. `fix-panic_id-none-server-panic-bug`, `feat-topic-new`). Match this pattern: + +- `fix-` for bug fixes +- `feat-` for new features +- `refactor-` for non-behavioral cleanup +- `chore-` for tooling / docs + +## Commit Messages + +Existing history uses [Conventional Commits](https://www.conventionalcommits.org/) prefixes: + +- `feat:` new feature +- `fix:` bug fix +- `chore:` tooling, deps, docs +- `refactor:` non-behavioral cleanup + +Format: `: `. Body (optional) explains the *why*, not the *what*. If an issue or PR is closed by the commit, append `fixes #` or `closes #` on its own line. + +## **CRITICAL** Git Rules for Parallel Agents **CRITICAL** + +Multiple agents may operate on different files in the same worktree simultaneously. Follow these rules without exception. + +### Committing + +- ONLY commit files YOU changed in THIS session. +- ALWAYS include `fixes #` or `closes #` when there is a related issue or PR. +- NEVER use `git add -A` or `git add .` — they sweep up changes from other agents. +- ALWAYS use `git add ` listing only files you modified. +- Before committing, run `git status` and verify only YOUR files are staged. +- Track which files you created / modified / deleted during the session. + +### Forbidden Git Operations + +These commands can destroy other agents' work: + +- `git reset --hard` — destroys uncommitted changes +- `git checkout .` / `git restore .` — destroys uncommitted changes +- `git clean -fd` — deletes untracked files +- `git stash` — stashes ALL changes including other agents' work +- `git add -A` / `git add .` — stages other agents' uncommitted work +- `git commit --no-verify` — bypasses required checks; never allowed + +### Safe Workflow + +```bash +# 1. Check status first +git status + +# 2. Add ONLY your specific files +git add server/coremq-server/src/services/topic.rs +git add client/src/sections/topics/topics_view.tsx + +# 3. Commit (Conventional Commits) +git commit -m "fix(server): assign packet_id for QoS 1/2 publishes" + +# 4. Push (pull --rebase if needed, NEVER reset/checkout) +git pull --rebase && git push +``` + +### If Rebase Conflicts Occur + +- Resolve conflicts in YOUR files only. +- If a conflict appears in a file you didn't modify, abort and ask the user. +- NEVER force push. + +### User override + +If user instructions conflict with the rules above, ask for explicit confirmation that the user wants to override. Only then proceed. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2856291 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,51 @@ +# Changelog + +All notable changes to CoreMQ are documented here. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). + +**Lockstep versioning.** The broker (`server/coremq-server/Cargo.toml`) and the dashboard (`client/package.json`) share a single project version and are released together. This file is the canonical changelog for that single version stream. Entries may tag the affected side with `server:` / `client:` / `repo:` where useful; an unprefixed entry applies project-wide. + +## [Unreleased] + +### Added + +- Stress test for MQTT QoS handling. +- `repo:` `.claude/skills/` — task-specific playbooks for code review, verification, debugging, scaffolding new backend features, and scaffolding new dashboard pages. +- `repo:` `.github/workflows/claude.yml` — `@claude` mention handler for PRs, issues, and reviews. +- `repo:` `.github/workflows/claude-code-review.yml` — auto PR review with a 4000-line cost guard, `.rs` + TS/JS file context, and tailored CoreMQ system prompt. +- `repo:` `AGENTS.md`, `CONTRIBUTING.md`, `CHANGELOG.md` at project root. + +### Changed + +- `repo:` Aligned `server/coremq-server/Cargo.toml` and `client/package.json` to a single shared project version (`0.1.0`). The dashboard was previously at `3.0.0` — that number was template-inherited and did not reflect project history. + +### Fixed + +- `server:` Assign `packet_id` for QoS 1/2 publishes to prevent server panic ([#5](../../pull/5)). + +## Conventions + +When adding entries: + +- Append under the `## [Unreleased]` section. Never modify already-released version sections — they are immutable. +- Use the subsections **Breaking Changes**, **Added**, **Changed**, **Fixed**, **Removed** in that order. Skip subsections that have no entries. +- One entry per change, written as a complete sentence. Lead with the side tag (`server:` / `client:` / `repo:`) when the scope is non-obvious. +- Link the PR or issue at the end where applicable: `… ([#42](../../pull/42))`. +- For external contributions, attribute: `… ([#42](../../pull/42) by [@username](https://github.com/username))`. + +## Releasing + +CoreMQ is shipped as a binary + a static dashboard, not as published packages. Both share a single version and release together (lockstep). + +1. Bump **both** version fields to the same value: + - `server/coremq-server/Cargo.toml` → `[package].version` + - `client/package.json` → `version` +2. Move `[Unreleased]` entries into a new `## [] — YYYY-MM-DD` section. Leave a fresh empty `[Unreleased]` block above it. +3. Commit with `chore(release): v`. +4. Tag with `git tag v` (e.g. `v0.2.0`). +5. Push branch + tags: `git push && git push --tags`. + +No major releases. Versioning is patch (fixes + additive features) and minor (API-breaking changes). + +If the two manifests drift out of sync, the `Cargo.toml` value is authoritative — bump `package.json` to match before cutting any release. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..03c945d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,169 @@ +# Contributing to CoreMQ + +Thanks for your interest in CoreMQ. This document covers how to set up your environment, what we expect from issues and PRs, and the conventions we use across the codebase. + +If you are an AI agent, read `AGENTS.md` first — it is the authoritative rules-of-engagement file. This document is for humans contributing to the repo. + +## Local Development + +### Prerequisites + +- Rust toolchain (latest stable). `make setup` will install it via `rustup` if missing. +- Node.js 18+ and `yarn`. The frontend uses yarn (not npm); using `npm install` will rewrite the lockfile. +- macOS or Linux. Windows is not actively tested. + +### Setup + +```bash +git clone +cd coremq-rust +make setup # installs Rust if needed, builds the broker, installs frontend deps +``` + +### Running + +```bash +make dev # broker + dashboard concurrently +make server # broker only (cargo run -p coremq-server) +make client # dashboard only (yarn dev) +``` + +### Default endpoints + +| Service | URL / Port | +| ------------- | ----------------------------- | +| MQTT TCP | `localhost:1883` | +| MQTT TLS | `localhost:8883` | +| MQTT WebSocket| `ws://localhost:8083` | +| REST API | `http://localhost:18083` | +| Dashboard | `http://localhost:3039` | + +Default login for the dashboard / REST API: `admin` / `public`. These are local-dev defaults only — never deploy them. + +## Reporting Issues + +Before opening an issue: + +1. Search existing issues — your problem may already be tracked. +2. Reproduce on the latest `main`. Stale local builds account for a surprising fraction of reports. +3. Run the relevant verification (`cargo clippy`, `npx eslint`) — clippy / eslint catch most "is this a bug?" cases. + +A good bug report includes: + +- **What you expected** and **what actually happened**. +- **Reproduction steps** — exact commands, MQTT client used (mosquitto_pub / mqttx / etc.), QoS level, payload, broker config. +- **Logs** — broker stdout, dashboard browser console, any panics or stack traces. +- **Environment** — OS, Rust version (`rustc --version`), Node version (`node --version`), whether you are running `make dev` or just one component. + +A good feature request includes: + +- The problem you are solving (not the solution you have in mind). +- Why existing functionality doesn't cover it. +- What "done" looks like — concrete acceptance criteria. + +Issues that don't meet this bar may be closed without a reply. Reopen after you've added the missing context — we don't hold it against you. + +## Submitting Pull Requests + +### Before you open a PR + +1. **Branch** off `main`. Use the established naming pattern: + - `fix-` for bug fixes + - `feat-` for new features + - `refactor-` for non-behavioral cleanup + - `chore-` for tooling, deps, docs +2. **Match conventions**. The full list lives in `.claude/skills.md`. The most-violated rules: + - Rust uses `/* */` block comments only — no `//`, no `///`. + - TypeScript uses `type` over `interface`, `export default function` for components, JSDoc-only comments, single quotes. + - Frontend pages are thin wrappers; logic lives in `sections//_view.tsx`. + - Zustand stores split `State` and `Actions`, share an `initialState`, and expose `reset()`. + - Theme tokens via `sx`, never hardcoded colors (the one exception is drawer `bgcolor: '#131825'`). + - Every user-facing string is `t('key')` and the key must exist in `client/src/i18n/{en,ko,uz}.json`. +3. **Run the verification suite** (full output, no tail; fix everything): + + ```bash + # Rust + cargo check -p coremq-server + cargo clippy -p coremq-server --all-targets -- -D warnings + cargo fmt -p coremq-server -- --check + cargo test -p coremq-server + + # Frontend + cd client + npx eslint "src/**/*.{js,jsx,ts,tsx}" + npx prettier --check "src/**/*.{ts,tsx}" + yarn build + ``` + +4. **Update `CHANGELOG.md`** under `[Unreleased]`. Don't modify already-released version sections. + +### PR description + +Include: + +- **Summary** — 2-3 sentences on what changed and why. +- **Test plan** — what you ran and what to look for in review. Concrete commands beat narrative. +- **Linked issue** — `fixes #` or `closes #` if applicable. The merge will auto-close. +- **Screenshots** — for any visible dashboard change. + +### What to expect + +- The `claude-code-review.yml` workflow will leave an automated review comment within a minute or two of the push. It is not a substitute for human review, but it catches obvious issues fast. +- A maintainer will review. Tag `@claude` in a comment for targeted automated follow-ups. +- We may ask for changes. Push to the same branch — no force-push to `main`, ever. + +## Commit Messages + +Use [Conventional Commits](https://www.conventionalcommits.org/). Format: `: `. + +| Type | Use for | +| ----------- | -------------------------------------------------- | +| `feat` | New feature | +| `fix` | Bug fix | +| `refactor` | Non-behavioral cleanup | +| `chore` | Tooling, dependencies, build, docs | +| `docs` | Documentation only | +| `test` | Adding or fixing tests | +| `perf` | Performance improvement without behavior change | + +Body (optional) explains the *why*, not the *what*. The diff already shows the *what*. If a commit closes an issue or PR, append `fixes #` or `closes #` on its own line. + +Example: + +``` +fix(server): assign packet_id for QoS 1/2 publishes + +The QoS 1/2 publish path was passing the packet_id field through unset, +which crashed the broker when the in-flight tracker tried to look it up. +Assigning at the publish boundary keeps the broker side of the protocol +correct without changing client APIs. + +fixes #4 +``` + +## Code Style + +We do not relitigate style in code review. The lint and formatter are the source of truth. + +- Rust: `cargo fmt`, `cargo clippy`. CI fails on warnings. +- Frontend: `npx prettier --write` (run via `make fmt`), `npx eslint --fix` (run via `make fix`). + +For convention details that linters cannot enforce, see: + +- `.claude/skills.md` — the canonical CoreMQ conventions doc (Rust + React, API, theme, i18n). +- `.claude/skills/code-review/references/gotchas.md` — recurring pitfalls (`unwrap()` on `Option::None`, dropped oneshots, missing `reset()`, etc.). + +## Testing + +- New behavior should come with a test. If a bug fix isn't covered by an existing test, add one — that's the bar for "fixed." +- Rust tests live alongside their module (`#[cfg(test)] mod tests`) or in `tests/` for integration tests. Run with `cargo test -p coremq-server` or `cargo test --test ` for a specific integration test. +- For QoS / stress regressions, follow the pattern in the recently added stress QoS test. +- Frontend tests are not yet wired up. If you want to add a test framework, open an issue first to align on tooling. + +## Security + +If you find a security issue, do not open a public issue. Email the maintainers (or DM on the project's primary channel) with reproduction steps. We'll coordinate on disclosure. + +## License + +By contributing, you agree your contributions are licensed under the same license as this repository. diff --git a/client/package.json b/client/package.json index 76ffd96..df9fdd0 100644 --- a/client/package.json +++ b/client/package.json @@ -2,7 +2,7 @@ "name": "coremq-dashboard", "author": "", "licence": "MIT", - "version": "3.0.0", + "version": "0.1.0", "private": false, "type": "module", "scripts": {