Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .claude/skills/code-review/references/gotchas.md
Original file line number Diff line number Diff line change
@@ -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.
Comment thread
Sardor-M marked this conversation as resolved.

## 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<T>`** — services return `ApiResponse<T>`, 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.
26 changes: 26 additions & 0 deletions .claude/skills/code-review/skill.md
Original file line number Diff line number Diff line change
@@ -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.
84 changes: 84 additions & 0 deletions .claude/skills/coremq-guide/skill.md
Original file line number Diff line number Diff line change
@@ -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
Comment thread
Sardor-M marked this conversation as resolved.
├── 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.
48 changes: 48 additions & 0 deletions .claude/skills/debug-coremq/skill.md
Original file line number Diff line number Diff line change
@@ -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 |
Comment thread
Sardor-M marked this conversation as resolved.

## 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.
47 changes: 47 additions & 0 deletions .claude/skills/new-backend-feature/skill.md
Original file line number Diff line number Diff line change
@@ -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/<feature>.rs`. Request/response structs with `Serialize`/`Deserialize`. Add `pub mod <feature>;` 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<Result<Resp, ApiError>>`.
3. **Service method** — Add to the relevant service in `server/coremq-server/src/services/`. Pure logic, no channels. Returns `Result<Resp, ApiError>`.
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/<feature>.rs`. Creates `oneshot::channel()`, sends the `AdminCommand`, awaits the reply, wraps in `ApiResponse<T>`. Add `pub mod <feature>;` to `controllers/mod.rs`.
6. **Route** — Register in `api/router.rs` with method + path + auth layer. Follow the `/api/v1/<resource>` 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/<your-route> \
-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).
37 changes: 37 additions & 0 deletions .claude/skills/verify/skill.md
Original file line number Diff line number Diff line change
@@ -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.
Loading
Loading