Restructure agent config: unified .agents/skills + wiki/architecture
Replace per-tool rule trees (.cursor/rules, .claude/rules, .codex/rules) with a single wiki/architecture/ source — 11 pages + README — readable as plain markdown by any agent. Canonical skills live in .agents/skills/; .claude/skills, .cursor/skills, .codex/skills are directory symlinks. AGENTS.md is the entrypoint (rewritten as a lean overview, no per-tool path lists). CLAUDE.md, GEMINI.md, and .github/copilot-instructions.md all point to it. Add open-pr skill (uses .github/pull_request_template.md as the source of truth for the PR body) and remove the dangling .claude/CLAUDE.md relative symlink. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
ca333dd912
commit
cf07fcaa97
@@ -0,0 +1,97 @@
|
||||
---
|
||||
name: open-pr
|
||||
description: Open a pull request on pascalorg/editor using the repo's PR template. Use when the user asks to open/create a PR, push and PR, or ship a branch in the editor repo.
|
||||
allowed-tools: Bash(git *) Bash(gh *) Read
|
||||
---
|
||||
|
||||
Open a pull request against `pascalorg/editor` from the current branch.
|
||||
|
||||
## 1. Pre-flight
|
||||
|
||||
```bash
|
||||
git status # confirm working tree state
|
||||
git branch --show-current # confirm we're on a feature branch, not main
|
||||
git log --oneline main..HEAD
|
||||
```
|
||||
|
||||
Stop if:
|
||||
- The current branch is `main`. Ask the user to create a feature branch first.
|
||||
- The branch has no commits ahead of `main`. Nothing to open a PR for.
|
||||
- There are uncommitted changes the user hasn't asked to commit.
|
||||
|
||||
Run a build sanity check if the change is non-trivial:
|
||||
|
||||
```bash
|
||||
bun typecheck
|
||||
bun build
|
||||
```
|
||||
|
||||
Don't open the PR with a broken build.
|
||||
|
||||
## 2. Read the PR template
|
||||
|
||||
The template is at `.github/pull_request_template.md`. Read it before composing the body — the section headings and checklist items are the source of truth, not your memory of them.
|
||||
|
||||
```bash
|
||||
cat .github/pull_request_template.md
|
||||
```
|
||||
|
||||
Mirror the template exactly:
|
||||
|
||||
- `## What does this PR do?` — one paragraph or a short bullet list. Link related issues with `Fixes #123` when applicable.
|
||||
- `## How to test` — numbered, concrete reviewer steps (commands to run, what to click, expected outcome).
|
||||
- `## Screenshots / screen recording` — if the change is visual, paste a recording link or note that one will be added. If purely non-visual (refactor, internal API), say so explicitly so the reviewer knows nothing is missing.
|
||||
- `## Checklist` — copy the boxes verbatim, ticking the ones already verified.
|
||||
|
||||
## 3. Push and open
|
||||
|
||||
```bash
|
||||
git push -u origin HEAD
|
||||
```
|
||||
|
||||
Check for an existing PR first:
|
||||
|
||||
```bash
|
||||
gh pr view --json url 2>/dev/null
|
||||
```
|
||||
|
||||
If none exists, create it. Pass the body via HEREDOC to preserve markdown formatting:
|
||||
|
||||
```bash
|
||||
gh pr create --title "short, scope-prefixed title" --body "$(cat <<'EOF'
|
||||
## What does this PR do?
|
||||
|
||||
<one-paragraph description; link issues>
|
||||
|
||||
## How to test
|
||||
|
||||
1. <step>
|
||||
2. <step>
|
||||
3. <step>
|
||||
|
||||
## Screenshots / screen recording
|
||||
|
||||
<link or "N/A — non-visual change">
|
||||
|
||||
## Checklist
|
||||
|
||||
- [x] I've tested this locally with `bun dev`
|
||||
- [x] My code follows the existing code style (run `bun check` to verify)
|
||||
- [ ] I've updated relevant documentation (if applicable)
|
||||
- [x] This PR targets the `main` branch
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
Keep the title under ~70 characters. Use a scope prefix when there's an obvious one (`viewer:`, `core:`, `editor:`, `mcp:`).
|
||||
|
||||
If a PR already exists, print its URL and stop — don't recreate.
|
||||
|
||||
## 4. Report
|
||||
|
||||
Return:
|
||||
|
||||
- PR URL
|
||||
- Title used
|
||||
- Local typecheck/build status (if you ran them)
|
||||
- A note for the reviewer if anything in the checklist is left unchecked
|
||||
+13
-13
@@ -10,16 +10,16 @@ Architectural review for Pascal PRs. The user will provide a PR URL, branch name
|
||||
|
||||
Read these before reviewing any diff. They are the source of truth, not your training data:
|
||||
|
||||
- `.claude/rules/systems.md` — core systems vs viewer systems, what each may do
|
||||
- `.claude/rules/renderers.md` — renderer responsibilities and prohibitions
|
||||
- `.claude/rules/tools.md` — editor tools live only in `apps/editor/components/tools/`
|
||||
- `.claude/rules/viewer-isolation.md` — viewer must stay editor-agnostic
|
||||
- `.claude/rules/layers.md`
|
||||
- `.claude/rules/selection-managers.md`
|
||||
- `.claude/rules/scene-registry.md`
|
||||
- `.claude/rules/spatial-queries.md`
|
||||
- `.claude/rules/node-schemas.md`
|
||||
- `.claude/rules/events.md`
|
||||
- `wiki/architecture/systems.md` — core systems vs viewer systems, what each may do
|
||||
- `wiki/architecture/renderers.md` — renderer responsibilities and prohibitions
|
||||
- `wiki/architecture/tools.md` — editor tools live only in `apps/editor/components/tools/`
|
||||
- `wiki/architecture/viewer-isolation.md` — viewer must stay editor-agnostic
|
||||
- `wiki/architecture/layers.md`
|
||||
- `wiki/architecture/selection-managers.md`
|
||||
- `wiki/architecture/scene-registry.md`
|
||||
- `wiki/architecture/spatial-queries.md`
|
||||
- `wiki/architecture/node-schemas.md`
|
||||
- `wiki/architecture/events.md`
|
||||
|
||||
Only the first four are required on every review; read the rest when the diff touches their subject area.
|
||||
|
||||
@@ -73,7 +73,7 @@ Write the classification down before writing findings. If core gains "Floorplan"
|
||||
- `packages/core/**` does not import Three.js, react-three-fiber, or anything from `packages/viewer` / `apps/editor`.
|
||||
- `packages/core/**` does not introduce types or helpers named after an editor view (`Floorplan*`, `Paint*`, `Draft*`). Generic plan-geometry helpers are fine; view-specific vocabulary is not.
|
||||
- Renderers contain no geometry generation or domain logic — that belongs in a system.
|
||||
- Tools mutate `useScene` (committed state) and `useLiveTransforms` (ephemeral drag state); direct `sceneRegistry` mesh transforms are allowed only under the live-drag exception in `.claude/rules/tools.md`. No business logic, no imports from `packages/viewer`.
|
||||
- Tools mutate `useScene` (committed state) and `useLiveTransforms` (ephemeral drag state); direct `sceneRegistry` mesh transforms are allowed only under the live-drag exception in `wiki/architecture/tools.md`. No business logic, no imports from `packages/viewer`.
|
||||
|
||||
### B. Hook hygiene (`useEditor`, `useScene`, `useViewer`)
|
||||
- Stores hold state + setters only. No business logic, side effects, async work, or derived computations inside the store definition.
|
||||
@@ -95,7 +95,7 @@ Write the classification down before writing findings. If core gains "Floorplan"
|
||||
|
||||
Group findings by severity:
|
||||
|
||||
- **Blocker** — violates a rule in `.claude/rules` or breaks a layer boundary. Must be fixed before merge.
|
||||
- **Blocker** — violates a rule in `wiki/architecture/` or breaks a layer boundary. Must be fixed before merge.
|
||||
- **Suggestion** — likely problem, worth discussing. Not a hard block.
|
||||
- **Nit** — minor, optional.
|
||||
|
||||
@@ -103,7 +103,7 @@ For each finding, include:
|
||||
|
||||
1. File and line: `path/to/file.ts:42`
|
||||
2. The offending snippet (short — 1–5 lines)
|
||||
3. The rule it violates, linked to the rule file (e.g. `.claude/rules/viewer-isolation.md`)
|
||||
3. The rule it violates, linked to the wiki page (e.g. `wiki/architecture/viewer-isolation.md`)
|
||||
4. A concrete proposed fix
|
||||
|
||||
Skip formatting, import ordering, and anything CI already covers.
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/creating-rules.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/events.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/layers.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/node-schemas.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/renderers.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/scene-registry.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/selection-managers.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/spatial-queries.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/systems.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/tools.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/viewer-isolation.mdc
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../.agents/skills
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/creating-rules.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/events.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/layers.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/node-schemas.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/renderers.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/scene-registry.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/selection-managers.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/spatial-queries.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/systems.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/tools.mdc
|
||||
@@ -1 +0,0 @@
|
||||
../../.cursor/rules/viewer-isolation.mdc
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../.agents/skills
|
||||
@@ -1,119 +0,0 @@
|
||||
---
|
||||
name: review-architecture
|
||||
description: Review a PR against the Pascal architectural rules — layer boundaries (core/viewer/editor), systems/renderers/tools separation, hook hygiene (useEditor/useScene/useViewer), and selector performance. Use when the user asks to review a PR, audit a branch, or check that changes respect the codebase's architecture.
|
||||
allowed-tools: Bash(git *) Bash(gh *) Read Grep Glob
|
||||
---
|
||||
|
||||
Architectural review for Pascal PRs. The user will provide a PR URL, branch name, or ask to review the current branch.
|
||||
|
||||
## 1. Load the rules (required — do not skip)
|
||||
|
||||
Read these before reviewing any diff. They are the source of truth, not your training data:
|
||||
|
||||
- `.codex/rules/systems.md` — core systems vs viewer systems, what each may do
|
||||
- `.codex/rules/renderers.md` — renderer responsibilities and prohibitions
|
||||
- `.codex/rules/tools.md` — editor tools live only in `apps/editor/components/tools/`
|
||||
- `.codex/rules/viewer-isolation.md` — viewer must stay editor-agnostic
|
||||
- `.codex/rules/layers.md`
|
||||
- `.codex/rules/selection-managers.md`
|
||||
- `.codex/rules/scene-registry.md`
|
||||
- `.codex/rules/spatial-queries.md`
|
||||
- `.codex/rules/node-schemas.md`
|
||||
- `.codex/rules/events.md`
|
||||
|
||||
Only the first four are required on every review; read the rest when the diff touches their subject area.
|
||||
|
||||
## 2. Fetch the diff
|
||||
|
||||
```bash
|
||||
# If the user gave a PR URL or number:
|
||||
gh pr diff <pr-number-or-url>
|
||||
|
||||
# If reviewing the current branch:
|
||||
git diff main...HEAD
|
||||
```
|
||||
|
||||
Also list changed files so you can map each to the relevant rule:
|
||||
|
||||
```bash
|
||||
gh pr view <pr> --json files --jq '.files[].path'
|
||||
# or
|
||||
git diff --name-only main...HEAD
|
||||
```
|
||||
|
||||
## 3. Layer classification — do this BEFORE the checklist
|
||||
|
||||
For every new file, new type, new store field, or new exported helper introduced by the diff, answer one question: **which layer does this belong to — core, viewer, or editor?** If the answer is "editor" but the code lives in `packages/core` or `packages/viewer` (or vice versa), flag it as a **blocker**. This is the most common and most damaging class of violation, and the checklist below won't reliably catch it on its own — do this pass explicitly.
|
||||
|
||||
### The three layers and what they own
|
||||
|
||||
**`packages/core` — domain data + pure logic.**
|
||||
Owns: node schemas, the scene store (`useScene`), live transforms store, core systems (wall mitering, slab polygons, space detection), event bus, plain 2D/3D math helpers, `sceneRegistry`. Consumed by every downstream package, including read-only embeds. Must not know about: Three.js/R3F, `packages/viewer`, `apps/editor`, any rendering or UI concept, any tool/mode/phase concept, or any *view*-specific concept (floorplan, paint preview, cursor indicators, selection outline styling, etc.).
|
||||
|
||||
**`packages/viewer` — the 3D canvas, shippable standalone.**
|
||||
Owns: `<Viewer>`, renderers, viewer systems (cutouts, zones, level positions, scans), the viewer store (`useViewer`) *for genuine presentation state only* (selection path, camera/level/wall/view modes, theme, display toggles, hover id). Consumed by both the editor and the read-only `/viewer/[id]` route. Must not know about: editor state (`useEditor`, tools, phases, modes), editor-only names baked into presentation modes (`'delete'`, `'paint-ready'`), editor-only state types (material preview, active paint target, floorplan anything).
|
||||
|
||||
**`apps/editor` (and editor-scoped packages) — the editing experience.**
|
||||
Owns: tools, `useEditor`, action menus, panels, the floorplan panel and its helpers, paint mode, selection-manager phase/mode logic, cursor badges, command palette, keyboard shortcuts — anything absent from the read-only viewer route. Injects itself into `<Viewer>` via children and props, never the reverse.
|
||||
|
||||
### Five triggers that mean "this is probably editor"
|
||||
|
||||
1. **Would the read-only `/viewer/[id]` route need this?** If no, it belongs in `apps/editor`.
|
||||
2. **Does the name contain an editor-specific word?** (`Floorplan`, `Paint…`, `Draft…`, `Marquee`, `CursorBadge`, `HoverMode`, `…Tool`, `Moving…`, `Curving…`.) Default to editor and justify loudly if it's anywhere else.
|
||||
3. **Does the type or field reference a tool/mode/phase vocabulary?** (`'delete'`, `'paint-ready'`, `'material-paint'`, `'site'`/`'structure'`/`'furnish'`, `'build'`/`'edit'`.) Belongs in `useEditor`, not `useViewer` or core.
|
||||
4. **Does the helper compute something only a 2D editor view needs?** (Floorplan transforms, measurement offsets, SVG path builders, marquee bounds scoped to floorplan.) Editor. Generic 2D geometry that any view could use (polygon math, rotation, clamping, line thickening) can live in core *as long as its names are generic* — no `Floorplan` prefix.
|
||||
5. **Does a new store field have a setter that no part of the target layer ever calls?** (e.g. `setMaterialPreview` in `useViewer` that only the editor would ever invoke.) That's a layering smell — the state belongs in the caller's layer.
|
||||
|
||||
Write the classification down before writing findings. If core gains "Floorplan" types, or the viewer gains paint-mode vocabulary, or a renderer grows editor awareness — those are the blockers to lead with, not downstream symptoms.
|
||||
|
||||
## 4. Review checklist
|
||||
|
||||
### A. Layer boundaries
|
||||
- `packages/viewer/**` does not import from `apps/editor` or reference `useEditor`, tool state, phase, or mode.
|
||||
- `packages/core/**` does not import Three.js, react-three-fiber, or anything from `packages/viewer` / `apps/editor`.
|
||||
- `packages/core/**` does not introduce types or helpers named after an editor view (`Floorplan*`, `Paint*`, `Draft*`). Generic plan-geometry helpers are fine; view-specific vocabulary is not.
|
||||
- Renderers contain no geometry generation or domain logic — that belongs in a system.
|
||||
- Tools mutate `useScene` (committed state) and `useLiveTransforms` (ephemeral drag state); direct `sceneRegistry` mesh transforms are allowed only under the live-drag exception in `.codex/rules/tools.md`. No business logic, no imports from `packages/viewer`.
|
||||
|
||||
### B. Hook hygiene (`useEditor`, `useScene`, `useViewer`)
|
||||
- Stores hold state + setters only. No business logic, side effects, async work, or derived computations inside the store definition.
|
||||
- Derived values belong in selectors or systems, not in the store body.
|
||||
- No cross-store coupling: a store's action should not call another store's actions inside itself.
|
||||
- New state added to `useViewer` must be presentation-only (selection, camera, level mode, display toggles). Editor-only state (active tool, phase, edit mode, paint preview, floorplan state) goes in `useEditor`.
|
||||
|
||||
### C. Selector performance
|
||||
- Top-level components (pages, layouts, providers, `<Viewer>` siblings) must not subscribe to large or frequently-changing slices — e.g. `useScene(s => s.nodes)`, `useScene(s => s)`. Flag these: they re-render the whole subtree on every mutation.
|
||||
- Selectors that return new object or array references each call (e.g. `s => ({ a: s.a, b: s.b })`, `s => s.items.filter(...)`) without a custom equality function (shallow or custom) are re-render hazards.
|
||||
- Prefer subscribing by ID deep in the tree (one node per renderer) over subscribing to the full collection high up.
|
||||
|
||||
### D. Separation of concerns
|
||||
- Viewer and core stay unaware of editor-specific concepts (tools, phases, active modes, editor UI state, view-specific helpers).
|
||||
- Editor-only overlays and systems are injected as children of `<Viewer>`, not added inside the viewer package.
|
||||
- New node types added correctly: schema → core system (if derived geometry) → viewer renderer → register in `NodeRenderer`.
|
||||
|
||||
## 5. Output format
|
||||
|
||||
Group findings by severity:
|
||||
|
||||
- **Blocker** — violates a rule in `.codex/rules` or breaks a layer boundary. Must be fixed before merge.
|
||||
- **Suggestion** — likely problem, worth discussing. Not a hard block.
|
||||
- **Nit** — minor, optional.
|
||||
|
||||
For each finding, include:
|
||||
|
||||
1. File and line: `path/to/file.ts:42`
|
||||
2. The offending snippet (short — 1–5 lines)
|
||||
3. The rule it violates, linked to the rule file (e.g. `.codex/rules/viewer-isolation.md`)
|
||||
4. A concrete proposed fix
|
||||
|
||||
Skip formatting, import ordering, and anything CI already covers.
|
||||
|
||||
If the PR fully complies, say so explicitly — do not invent nits to appear thorough.
|
||||
|
||||
## 6. Final summary
|
||||
|
||||
End with:
|
||||
|
||||
- Blocker count, suggestion count, nit count
|
||||
- One-sentence verdict: ready to merge / needs changes / needs discussion
|
||||
- If blockers exist, list the files the author should open first
|
||||
@@ -1,79 +0,0 @@
|
||||
---
|
||||
description: How to create and maintain project rules
|
||||
globs: .cursor/rules/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Creating Rules
|
||||
|
||||
Rules live in two places and are kept in sync via symlinks:
|
||||
|
||||
- `.cursor/rules/<rule-name>.mdc` — source of truth (Cursor format)
|
||||
- `.claude/rules/<rule-name>.md` — symlink pointing to the cursor file
|
||||
|
||||
## Workflow
|
||||
|
||||
**1. Write the rule in `.cursor/rules/`**
|
||||
|
||||
```
|
||||
.cursor/rules/my-rule.mdc
|
||||
```
|
||||
|
||||
**2. Create a symlink in `.claude/rules/`**
|
||||
|
||||
```bash
|
||||
ln -s ../../.cursor/rules/my-rule.mdc .claude/rules/my-rule.md
|
||||
```
|
||||
|
||||
The `../../` prefix is required because the symlink lives two levels deep.
|
||||
|
||||
**3. Verify**
|
||||
|
||||
```bash
|
||||
ls -la .claude/rules/my-rule.md
|
||||
# → .claude/rules/my-rule.md -> ../../.cursor/rules/my-rule.mdc
|
||||
```
|
||||
|
||||
## Rule File Format
|
||||
|
||||
```markdown
|
||||
---
|
||||
description: One-line summary of what this rule covers
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Rule Title
|
||||
|
||||
Short intro paragraph.
|
||||
|
||||
## Section
|
||||
|
||||
Concrete guidance with examples.
|
||||
```
|
||||
|
||||
- Set `alwaysApply: true` only for rules that apply to every file in the project.
|
||||
- Use `globs` to scope a rule to specific paths (e.g. `packages/viewer/**`).
|
||||
|
||||
## Good Practices
|
||||
|
||||
- Keep rules under 500 lines. Split large rules into smaller focused files.
|
||||
- Include concrete examples or reference real files with `@filename`.
|
||||
- Add a rule when the same mistake has been made more than once — not preemptively.
|
||||
- Prefer showing a correct example over listing prohibitions.
|
||||
|
||||
## Existing Rules
|
||||
|
||||
| Rule | Covers |
|
||||
|---|---|
|
||||
| `creating-rules` | This file — how to add rules |
|
||||
| `renderers` | Node renderer pattern in `packages/viewer` |
|
||||
| `systems` | Core and viewer systems architecture |
|
||||
| `tools` | Editor tools structure in `apps/editor` |
|
||||
| `viewer-isolation` | Keeping `@pascal-app/viewer` editor-agnostic |
|
||||
| `scene-registry` | Global node ID → Object3D map and `useRegistry` |
|
||||
| `selection-managers` | Two-layer selection (viewer + editor), events, outliner |
|
||||
| `events` | Typed event bus — emitting and listening to node and grid events |
|
||||
| `node-schemas` | Zod schema pattern for node types, createNode, updateNode |
|
||||
| `spatial-queries` | Placement validation (canPlaceOnFloor/Wall/Ceiling) for tools |
|
||||
| `layers` | Three.js layer constants, ownership, and rendering separation |
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../.agents/skills
|
||||
@@ -1 +0,0 @@
|
||||
../../../.claude/skills/review-architecture/SKILL.md
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../AGENTS.md
|
||||
@@ -1,49 +1,51 @@
|
||||
# Pascal Agent Instructions
|
||||
# Agent Instructions — `pascalorg/editor`
|
||||
|
||||
This repository uses shared architecture rules for AI assistants. Treat the rule files as the source of truth for architecture-sensitive work.
|
||||
Public, open-source home of `@pascal-app/{core,viewer,editor,mcp}` and the standalone editor app. Consumed both as npm packages and (in `pascalorg/private-editor`) as a git submodule.
|
||||
|
||||
## Required Rule Sources
|
||||
## Repo Shape
|
||||
|
||||
The canonical rules live in `.cursor/rules/*.mdc`.
|
||||
| Path | Purpose |
|
||||
|---|---|
|
||||
| `packages/core` | Scene graph, node schemas, stores, event bus, core systems — pure logic, no Three.js |
|
||||
| `packages/viewer` | Standalone 3D canvas: renderers, viewer systems, presentation state |
|
||||
| `packages/editor` | Editor UI components reused by the standalone app and embedders |
|
||||
| `packages/mcp` | MCP server and scene storage adapters |
|
||||
| `apps/editor` | Standalone editor app — composes `viewer` + `editor` + tools |
|
||||
|
||||
Claude-compatible paths are exposed in `.claude/rules/*.md`.
|
||||
Codex-compatible paths are exposed in `.codex/rules/*.md`.
|
||||
## Where to look
|
||||
|
||||
Both should point to the same Cursor rule sources so Claude and Codex review the exact same rules.
|
||||
- **Architecture rules** — `wiki/architecture/` (read on demand; index in `wiki/architecture/README.md`).
|
||||
- **Skills (ready workflows)** — `.agents/skills/<name>/SKILL.md`. Same content is reachable as `.claude/skills/`, `.cursor/skills/`, `.codex/skills/` (symlinks to `.agents/skills/`).
|
||||
- **Repo orientation for humans** — `README.md`, `SETUP.md`, `CONTRIBUTING.md`.
|
||||
|
||||
## Architecture Rules
|
||||
`CLAUDE.md`, `GEMINI.md`, and `.github/copilot-instructions.md` are symlinks to this file. Codex reads this file directly.
|
||||
|
||||
Read the relevant rules before making or reviewing changes in these areas:
|
||||
## Layer Boundaries (read once, internalise)
|
||||
|
||||
- `.codex/rules/systems.md` — core systems vs viewer systems, what each may do
|
||||
- `.codex/rules/renderers.md` — renderer responsibilities and prohibitions
|
||||
- `.codex/rules/tools.md` — editor tools live only in `apps/editor/components/tools/`
|
||||
- `.codex/rules/viewer-isolation.md` — viewer must stay editor-agnostic
|
||||
- `.codex/rules/layers.md`
|
||||
- `.codex/rules/selection-managers.md`
|
||||
- `.codex/rules/scene-registry.md`
|
||||
- `.codex/rules/spatial-queries.md`
|
||||
- `.codex/rules/node-schemas.md`
|
||||
- `.codex/rules/events.md`
|
||||
- **`packages/core`** owns domain data and pure logic. It must not import Three.js, `packages/viewer`, `apps/editor`, rendering/UI concepts, tools, modes, phases, or view-specific concepts such as floorplan or paint preview.
|
||||
- **`packages/viewer`** owns the standalone 3D canvas, renderers, viewer systems, and genuine presentation state. It must not know about `useEditor`, editor tools, phases, modes, paint mode, floorplan state, or editor-only presentation vocabulary.
|
||||
- **`apps/editor`** owns the editing experience: tools, `useEditor`, panels, floorplan helpers, paint mode, keyboard shortcuts, command palette, action menus, cursor badges, and editor-only overlays. Editor features are injected into `<Viewer>` via props and children.
|
||||
|
||||
For architecture reviews, the first four are always required. Read the remaining rules when the diff touches their subject area.
|
||||
Details, examples, and rationale live in `wiki/architecture/layers.md`, `wiki/architecture/viewer-isolation.md`, `wiki/architecture/systems.md`, `wiki/architecture/renderers.md`, `wiki/architecture/tools.md`.
|
||||
|
||||
## Layer Boundaries
|
||||
## When making architecture-sensitive changes
|
||||
|
||||
`packages/core` owns domain data and pure logic. It must not import Three.js, `packages/viewer`, `apps/editor`, rendering/UI concepts, tools, modes, phases, or view-specific concepts such as floorplan or paint preview.
|
||||
Read the relevant page in `wiki/architecture/` **before** writing code. The page list lives in `wiki/architecture/README.md`. As a minimum:
|
||||
|
||||
`packages/viewer` owns the standalone 3D canvas, renderers, viewer systems, and genuine presentation state. It must not know about `useEditor`, editor tools, phases, modes, paint mode, floorplan state, or editor-only presentation vocabulary.
|
||||
- Adding a node type → `node-schemas.md`, `renderers.md`, `systems.md`
|
||||
- Adding a tool → `tools.md`, `spatial-queries.md`, `events.md`
|
||||
- Adding a system → `systems.md`, `scene-registry.md`
|
||||
- Anything in `packages/viewer` → `viewer-isolation.md`, `layers.md`
|
||||
- Anything touching selection → `selection-managers.md`, `scene-registry.md`, `events.md`
|
||||
|
||||
`apps/editor` owns the editing experience: tools, `useEditor`, panels, floorplan helpers, paint mode, keyboard shortcuts, command palette, action menus, cursor badges, and editor-only overlays. Editor features are injected into `<Viewer>` via props and children.
|
||||
## When reviewing a PR
|
||||
|
||||
## Review Expectations
|
||||
Invoke the `review-architecture` skill (`.agents/skills/review-architecture/SKILL.md`). It loads the required architecture pages, fetches the diff, classifies each new file by layer, and reports findings grouped by severity.
|
||||
|
||||
When reviewing architecture changes:
|
||||
## Operating rules
|
||||
|
||||
1. Classify every new file, type, store field, and exported helper as core, viewer, or editor before writing findings.
|
||||
2. Lead with layer-boundary blockers.
|
||||
3. Check hook hygiene for `useEditor`, `useScene`, and `useViewer`.
|
||||
4. Check selector performance for broad subscriptions and selectors that allocate fresh references.
|
||||
5. Skip formatting and import ordering unless they hide a real behavior or architecture issue.
|
||||
|
||||
Use `.codex/skills/review-architecture/SKILL.md` when the user asks Codex to review a PR, audit a branch, or check architecture compliance.
|
||||
- Read the full file before editing. Plan all changes, then make one complete edit.
|
||||
- When the user corrects you, stop and re-read their message.
|
||||
- After two consecutive tool failures, stop and change approach.
|
||||
- Don't introduce backwards-compatibility shims, dead code, or speculative abstractions.
|
||||
- Don't write new comments unless they explain a non-obvious *why*.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Architecture
|
||||
|
||||
Canonical rules for code that touches `packages/core`, `packages/viewer`, `packages/editor`, `packages/mcp`, or `apps/editor`. Read on demand from `AGENTS.md` and from `.agents/skills/review-architecture/SKILL.md`.
|
||||
|
||||
## Pages
|
||||
|
||||
| Page | Covers |
|
||||
|---|---|
|
||||
| [layers](layers.md) | Three.js layer constants, ownership, and rendering separation |
|
||||
| [systems](systems.md) | Core and viewer systems architecture |
|
||||
| [renderers](renderers.md) | Node renderer pattern in `packages/viewer` |
|
||||
| [tools](tools.md) | Editor tools structure in `apps/editor` |
|
||||
| [viewer-isolation](viewer-isolation.md) | Keeping `@pascal-app/viewer` editor-agnostic |
|
||||
| [selection-managers](selection-managers.md) | Two-layer selection (viewer + editor), events, outliner |
|
||||
| [scene-registry](scene-registry.md) | Global node ID → Object3D map and `useRegistry` |
|
||||
| [spatial-queries](spatial-queries.md) | Placement validation (`canPlaceOnFloor`/`Wall`/`Ceiling`) for tools |
|
||||
| [node-schemas](node-schemas.md) | Zod schema pattern for node types, `createNode`, `updateNode` |
|
||||
| [events](events.md) | Typed event bus — emitting and listening to node and grid events |
|
||||
| [creating-rules](creating-rules.md) | How to add or update a page in this folder |
|
||||
|
||||
## Reading order for an architecture review
|
||||
|
||||
1. [layers](layers.md), [systems](systems.md), [renderers](renderers.md), [tools](tools.md), [viewer-isolation](viewer-isolation.md) — required every review.
|
||||
2. The remaining pages on demand, based on what the diff touches.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Creating Architecture Pages
|
||||
|
||||
*How to add or update a page under `wiki/architecture/`.*
|
||||
|
||||
The pages in `wiki/architecture/` are the canonical source of architectural rules. AI agents (Claude, Codex, Cursor, Gemini) read them on demand via `AGENTS.md`.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Pick a focused topic (one concept per page — layers, events, registry, …).
|
||||
2. Create `wiki/architecture/<slug>.md`. Slugs are short, kebab-case.
|
||||
3. Add an entry to `wiki/architecture/README.md` so the new page is discoverable.
|
||||
4. If the page should be read for PR reviews, link it from `.agents/skills/review-architecture/SKILL.md`.
|
||||
|
||||
## Page format
|
||||
|
||||
```markdown
|
||||
# Page Title
|
||||
|
||||
*One-line italic description of what this page covers.*
|
||||
|
||||
Applies to: `path/glob/**`.
|
||||
|
||||
Short intro paragraph.
|
||||
|
||||
## Section
|
||||
|
||||
Concrete guidance with code examples and rules.
|
||||
```
|
||||
|
||||
The italic description and `Applies to:` line replace the old Cursor frontmatter — they're plain markdown so every agent sees them.
|
||||
|
||||
## Good practice
|
||||
|
||||
- Keep a page focused on one concept. Split if it grows past ~500 lines.
|
||||
- Lead with the rule, follow with the example. Show the correct shape before listing prohibitions.
|
||||
- Reference real source files with a plain backtick path (e.g. `packages/core/src/schema/base.ts`).
|
||||
- Add a new page when the same mistake has been made twice — not preemptively.
|
||||
- Never duplicate content across pages. Link instead.
|
||||
|
||||
## Existing pages
|
||||
|
||||
See `wiki/architecture/README.md` for the current index.
|
||||
@@ -1,14 +1,12 @@
|
||||
---
|
||||
description: Typed event bus — emitting and listening to node and grid events
|
||||
globs: packages/core/src/events/**,packages/viewer/**,apps/editor/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Events
|
||||
|
||||
*Typed event bus — emitting and listening to node and grid events.*
|
||||
|
||||
Applies to: `packages/core/src/events/**`, `packages/viewer/**`, `apps/editor/**`.
|
||||
|
||||
The event bus (`emitter`) is a global `mitt` instance typed with `EditorEvents`. It decouples renderers (which emit) from selection managers and tools (which listen).
|
||||
|
||||
**Source**: @packages/core/src/events/bus.ts
|
||||
**Source**: `packages/core/src/events/bus.ts`
|
||||
|
||||
## Event Key Format
|
||||
|
||||
@@ -26,7 +24,7 @@ Example keys: `wall:click`, `item:enter`, `door:double-click`, `grid:pointerdown
|
||||
'click' | 'move' | 'enter' | 'leave' | 'pointerdown' | 'pointerup' | 'context-menu' | 'double-click'
|
||||
```
|
||||
|
||||
The `grid:*` events fire when the user interacts with empty space (no node hit). They are **not** emitted by a mesh — `useGridEvents(gridY)` (@apps/editor/hooks/use-grid-events.ts) manually raycasts against a ground plane and calls `emitter.emit('grid:click', …)`. Mount it in any tool or editor component that needs empty-space interactions.
|
||||
The `grid:*` events fire when the user interacts with empty space (no node hit). They are **not** emitted by a mesh — `useGridEvents(gridY)` (`apps/editor/hooks/use-grid-events.ts`) manually raycasts against a ground plane and calls `emitter.emit('grid:click', …)`. Mount it in any tool or editor component that needs empty-space interactions.
|
||||
|
||||
## NodeEvent Shape
|
||||
|
||||
@@ -76,7 +74,7 @@ useEffect(() => {
|
||||
}, [])
|
||||
```
|
||||
|
||||
See @apps/editor/components/editor/selection-manager.tsx for a full multi-type listener example.
|
||||
See `apps/editor/components/editor/selection-manager.tsx` for a full multi-type listener example.
|
||||
|
||||
## Rules
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
---
|
||||
description: Three.js layer conventions — which layer each object type lives on and why
|
||||
globs: packages/viewer/**,apps/editor/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Three.js Layers
|
||||
|
||||
*Three.js layer conventions — which layer each object type lives on and why.*
|
||||
|
||||
Applies to: `packages/viewer/**`, `apps/editor/**`.
|
||||
|
||||
Three.js `Layers` control which objects each camera and render pass sees. We use them to separate scene geometry, editor helpers, and zone overlays into distinct rendering buckets without duplicating scene structure.
|
||||
|
||||
## Layer Map
|
||||
@@ -54,4 +52,4 @@ The editor camera enables `EDITOR_LAYER` so tools and helpers are visible during
|
||||
- **`EDITOR_LAYER` belongs in `apps/editor`** — the viewer must never import it; editor behaviour is injected via props/children.
|
||||
- **Zone meshes must set `layers={ZONE_LAYER}`** so they are picked up by `zonePass` and excluded from `scenePass` depth buffers.
|
||||
- **Editor helper meshes must set `layers={EDITOR_LAYER}`** so they are invisible to the thumbnail camera and the viewer's render passes.
|
||||
- **Do not add new layers without updating this rule** and the post-processing pipeline accordingly.
|
||||
- **Do not add new layers without updating this page** and the post-processing pipeline accordingly.
|
||||
@@ -1,14 +1,12 @@
|
||||
---
|
||||
description: Node type definitions, Zod schema pattern, and how to create nodes in the scene
|
||||
globs: packages/core/src/schema/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Node Schemas
|
||||
|
||||
*Node type definitions, Zod schema pattern, and how to create nodes in the scene.*
|
||||
|
||||
Applies to: `packages/core/src/schema/**`.
|
||||
|
||||
All node types are defined as Zod schemas in `packages/core/src/schema/nodes/`. Each schema extends `BaseNode` and exports both the schema and its inferred TypeScript type.
|
||||
|
||||
**Sources**: @packages/core/src/schema/base.ts, @packages/core/src/schema/nodes/
|
||||
**Sources**: `packages/core/src/schema/base.ts`, `packages/core/src/schema/nodes/`
|
||||
|
||||
## BaseNode
|
||||
|
||||
@@ -82,9 +80,9 @@ updateNode(wall.id, { height: 2.8 }) // partial update, merges with existing
|
||||
|
||||
## Real Examples
|
||||
|
||||
- **Simple geometry node**: @packages/core/src/schema/nodes/wall.ts — `start`, `end`, `thickness`, `height`
|
||||
- **Polygon node**: @packages/core/src/schema/nodes/slab.ts — `polygon: [number, number][]`, `holes`
|
||||
- **Positioned node**: @packages/core/src/schema/nodes/item.ts — `position`, `rotation`, `scale`, `asset`
|
||||
- **Simple geometry node**: `packages/core/src/schema/nodes/wall.ts` — `start`, `end`, `thickness`, `height`
|
||||
- **Polygon node**: `packages/core/src/schema/nodes/slab.ts` — `polygon: [number, number][]`, `holes`
|
||||
- **Positioned node**: `packages/core/src/schema/nodes/item.ts` — `position`, `rotation`, `scale`, `asset`
|
||||
|
||||
## Rules
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
---
|
||||
description: Node renderer pattern in packages/viewer
|
||||
globs: packages/viewer/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Renderers
|
||||
|
||||
*Node renderer pattern in `packages/viewer`.*
|
||||
|
||||
Applies to: `packages/viewer/**`.
|
||||
|
||||
Renderers live in `packages/viewer/src/components/renderers/`. Each renderer is responsible for one node type's Three.js geometry and materials — nothing else.
|
||||
|
||||
## Dispatch Chain
|
||||
@@ -16,7 +14,7 @@ Renderers live in `packages/viewer/src/components/renderers/`. Each renderer is
|
||||
└─ <WallRenderer> — (or SlabRenderer, DoorRenderer, …)
|
||||
```
|
||||
|
||||
See @packages/viewer/src/components/renderers/scene-renderer.tsx and @packages/viewer/src/components/renderers/node-renderer.tsx.
|
||||
See `packages/viewer/src/components/renderers/scene-renderer.tsx` and `packages/viewer/src/components/renderers/node-renderer.tsx`.
|
||||
|
||||
## Renderer Responsibilities
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
---
|
||||
description: Scene registry pattern — mapping node IDs to live THREE.Object3D instances
|
||||
globs: packages/core/src/hooks/scene-registry/**,packages/viewer/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Scene Registry
|
||||
|
||||
*Mapping node IDs to live `THREE.Object3D` instances.*
|
||||
|
||||
Applies to: `packages/core/src/hooks/scene-registry/**`, `packages/viewer/**`.
|
||||
|
||||
The scene registry is a global, mutable map that links node IDs to their live `THREE.Object3D` instances. It avoids tree traversal and lets systems and selection managers do O(1) lookups.
|
||||
|
||||
**Source**: @packages/core/src/hooks/scene-registry/scene-registry.ts
|
||||
**Source**: `packages/core/src/hooks/scene-registry/scene-registry.ts`
|
||||
|
||||
## Structure
|
||||
|
||||
@@ -77,4 +75,4 @@ for (const id of selection.selectedIds) {
|
||||
}
|
||||
```
|
||||
|
||||
See @packages/viewer/src/components/viewer/selection-manager.tsx for the full sync pattern.
|
||||
See `packages/viewer/src/components/viewer/selection-manager.tsx` for the full sync pattern.
|
||||
@@ -1,11 +1,9 @@
|
||||
---
|
||||
description: Selection managers — two-layer architecture for viewer and editor selection
|
||||
globs: packages/viewer/src/components/viewer/selection-manager.tsx,apps/editor/components/editor/selection-manager.tsx
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Selection Managers
|
||||
|
||||
*Two-layer selection architecture: viewer manager (hierarchy) + editor manager (phase-aware).*
|
||||
|
||||
Applies to: `packages/viewer/src/components/viewer/selection-manager.tsx`, `apps/editor/components/editor/selection-manager.tsx`.
|
||||
|
||||
There are two selection managers. They are separate components, not the same component configured differently.
|
||||
|
||||
| Component | Location | Knows about |
|
||||
@@ -1,14 +1,12 @@
|
||||
---
|
||||
description: Placement validation for tools — canPlaceOnFloor, canPlaceOnWall, canPlaceOnCeiling
|
||||
globs: apps/editor/components/tools/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Spatial Queries
|
||||
|
||||
*Placement validation for tools — `canPlaceOnFloor`, `canPlaceOnWall`, `canPlaceOnCeiling`.*
|
||||
|
||||
Applies to: `apps/editor/components/tools/**`.
|
||||
|
||||
`useSpatialQuery()` validates whether an item can be placed at a given position without overlapping existing items. Every placement tool must call it before committing a node to the scene.
|
||||
|
||||
**Source**: @packages/core/src/hooks/spatial-grid/use-spatial-query.ts
|
||||
**Source**: `packages/core/src/hooks/spatial-grid/use-spatial-query.ts`
|
||||
|
||||
## Hook
|
||||
|
||||
@@ -100,7 +98,7 @@ const y = spatialGridManager.getSlabElevationForItem(levelId, position, dimensio
|
||||
|
||||
- **Always pass `[item.id]` in `ignoreIds`** when validating a draft item that already exists in the scene — otherwise it collides with itself.
|
||||
- **Use `adjustedY` from `canPlaceOnWall`** — don't use the raw cursor Y for wall-mounted items.
|
||||
- **Use `getScaledDimensions(item)`** (@packages/core/src/schema/nodes/item.ts) to account for item scale, not the raw `asset.dimensions`.
|
||||
- **Use `getScaledDimensions(item)`** (`packages/core/src/schema/nodes/item.ts`) to account for item scale, not the raw `asset.dimensions`.
|
||||
- Validate on every pointer move for live feedback (highlight ghost red/green). Only `createNode` / `updateNode` on pointer up or click.
|
||||
|
||||
See @apps/editor/components/tools/item/use-placement-coordinator.tsx for a full implementation.
|
||||
See `apps/editor/components/tools/item/use-placement-coordinator.tsx` for a full implementation.
|
||||
@@ -1,11 +1,9 @@
|
||||
---
|
||||
description: Core and viewer systems architecture
|
||||
globs: packages/core/src/systems/**,packages/viewer/src/systems/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Systems
|
||||
|
||||
*Core and viewer systems architecture.*
|
||||
|
||||
Applies to: `packages/core/src/systems/**`, `packages/viewer/src/systems/**`.
|
||||
|
||||
Systems own business logic, geometry generation, and constraints. They run in the Three.js frame loop and are never rendered directly.
|
||||
|
||||
## Two Kinds of Systems
|
||||
@@ -57,7 +55,7 @@ export function MySystem() {
|
||||
}
|
||||
```
|
||||
|
||||
Core and viewer systems are mounted inside `<Viewer>` alongside renderers. See @packages/viewer/src/components/viewer/index.tsx for the mount order.
|
||||
Core and viewer systems are mounted inside `<Viewer>` alongside renderers. See `packages/viewer/src/components/viewer/index.tsx` for the mount order.
|
||||
|
||||
**Systems are a customization point.** Any consumer of `<Viewer>` — the editor app, an embed, a read-only preview — can inject its own systems as children. This is how editor-specific behaviour (space detection, tool feedback) is added without touching the viewer package.
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
---
|
||||
description: Editor tools structure in apps/editor
|
||||
globs: apps/editor/components/tools/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Tools
|
||||
|
||||
*Editor tools structure in `apps/editor`.*
|
||||
|
||||
Applies to: `apps/editor/components/tools/**`.
|
||||
|
||||
Tools are React components that capture user input (pointer, keyboard) and translate it into `useScene` mutations. They live exclusively in `apps/editor/components/tools/`.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
`ToolManager` reads `useEditor` (phase + mode + tool) and mounts the active tool component. When the tool changes, the old component unmounts, cleaning up any transient state.
|
||||
|
||||
See @apps/editor/components/tools/tool-manager.tsx.
|
||||
See `apps/editor/components/tools/tool-manager.tsx`.
|
||||
|
||||
## Tool Categories by Phase
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
---
|
||||
description: Viewer must be editor-agnostic — controlled from outside via props and children
|
||||
globs: packages/viewer/**
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# Viewer Isolation
|
||||
|
||||
*Viewer must be editor-agnostic — controlled from outside via props and children.*
|
||||
|
||||
Applies to: `packages/viewer/**`.
|
||||
|
||||
`@pascal-app/viewer` is a standalone 3D canvas library. It must never know about editor-specific features, UI state, or tools. This keeps it usable in the read-only `/viewer/[id]` route and in any future embedding context.
|
||||
|
||||
## The Rule
|
||||
Reference in New Issue
Block a user