From 24900b658de75a730fce152ce2fd90a60688f89c Mon Sep 17 00:00:00 2001 From: Vladislav Panin Date: Mon, 27 Jul 2026 17:14:53 +0300 Subject: [PATCH] docs(claude): split guidance into per-folder files and add editor skills - Trim the root CLAUDE.md feature map to non-obvious facts only, delegating detail to per-folder guides - Add Types/ and VisualElements/Internal/ CLAUDE.md: TypeSelector's two field shapes, internal component conventions - Add unity-pipeline and editor-media-capture skills for driving the live Editor and shooting docs media - Document the multi-Editor `--project-path` requirement and `unity status` as the liveness check Co-Authored-By: Claude --- .claude/skills/editor-media-capture/SKILL.md | 82 ++++++++++ .claude/skills/unity-pipeline/SKILL.md | 141 ++++++++++++++++++ .../Unity/Editor/Scripts/Types/CLAUDE.md | 47 ++++++ .../Unity/Editor/Scripts/Types/CLAUDE.md.meta | 7 + .../Scripts/VisualElements/Internal/CLAUDE.md | 33 ++++ .../VisualElements/Internal/CLAUDE.md.meta | 7 + CLAUDE.md | 43 +++--- 7 files changed, 339 insertions(+), 21 deletions(-) create mode 100644 .claude/skills/editor-media-capture/SKILL.md create mode 100644 .claude/skills/unity-pipeline/SKILL.md create mode 100644 Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Types/CLAUDE.md create mode 100644 Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Types/CLAUDE.md.meta create mode 100644 Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/VisualElements/Internal/CLAUDE.md create mode 100644 Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/VisualElements/Internal/CLAUDE.md.meta diff --git a/.claude/skills/editor-media-capture/SKILL.md b/.claude/skills/editor-media-capture/SKILL.md new file mode 100644 index 00000000..8eefb98f --- /dev/null +++ b/.claude/skills/editor-media-capture/SKILL.md @@ -0,0 +1,82 @@ +--- +name: editor-media-capture +description: Shoot Unity Editor screenshots and GIFs for this package's documentation without the user touching Unity — floating Inspectors, TypeSelectorWindow dropdowns, prefab-mode states, and ffmpeg GIF assembly. Use when a task asks for docs media (PNG/GIF) of editor UI under Documentation/Images, or when scripting EditorWindow interaction headlessly. +user-invocable: false +allowed-tools: + - Bash +--- + +# Editor media capture + +Driving the Editor is the `unity-pipeline` skill's job; this file covers what it cannot do — +capturing *editor windows* (not the Game/Scene view) and scripting UI states. + +For a plain Game or Scene frame, stop here and use +`unity command screenshot --view game|scene` instead. Everything below is for editor chrome. + +## Capture without stealing focus + +```bash +screencapture -x -o -l out.png # specific window, works while Unity is inactive/occluded +``` + +- Find window IDs with a small Swift script over `CGWindowListCopyWindowInfo` (no `pyobjc` on this + machine). Re-query **before every shot** and pick the **highest** id: closed editor windows leave + blank native ghosts with the same size and title, which `screencapture` will happily capture + (~13 KB of nothing). +- `-o` drops the drop shadow. Crop the 28 pt (56 px @2x) macOS title bar with `ffmpeg`; `sips -c` + cannot crop with an offset. +- `screencapture -R x,y,w,h` takes **points** and outputs @2x Retina pixels. It composites by + z-order, so a floating `EditorWindow` sitting under the main window yields a gray rectangle. + `win.Focus()` raises it but activates the app once — avoid while the user is typing; window-ID + capture needs no focus. + +## Floating windows + +- `CreateInstance()` + `Show()`, then set `position` a **second** time — the first + assignment is ignored, and the getter returns stale values. Read real bounds from the window list; + do not trust the setter (macOS cascades floating windows). +- Title bar ≈ 28–30 pt: the capture rect is the content rect grown upward. + +## TypeSelectorWindow + +- It **survives without app focus**, and + `EditorWindow.SendEvent(Event.KeyboardEvent("down"|"return"))` drives its keyboard navigation + headlessly — the full root → namespace → select flow works with zero `cliclick`. +- The root page auto-shows a **Recent** section that breaks scripted down/return navigation. Clear + `TypeSelectorPreferences.RecentsKey` first and restore the user's entries afterwards — scripted + selections pollute Recents. Keys: `Aspid.FastTools.TypeSelector.{Favorites|Recents}.`, + JSON `{"Entries":[aqn,...]}`. +- A non-empty `currentAqn` opens the window already inside that type's namespace page (flat list, + breadcrumbs); empty starts at root. Favorites/Recent exist only on the root page and open + collapsed (in-memory state, not persisted). +- Anchor `ShowAsDropDown(screenRect, size)` to the **exact field rect** — it drops flush under + `screenRect`'s bottom, and a hand-guessed anchor leaves a visible gap. In a 430 pt floating + Inspector (GO header + Transform + component): value column `x+176`, row `y+154` from content + top, `248×17`. +- A dropdown opened without OS focus dies non-deterministically 0.5–4 s after opening. Capture each + state in its **own short eval** (Show → SendEvents fast → hold ≤2.5 s) with an outside burst loop + (5 shots @ 0.45 s), then composite the states onto the inspector capture with `ffmpeg overlay`. + `ShowPopup()` clones do not auto-close but usually render **blank** when the app is inactive — + dead end. +- If you must use `cliclick`: a single synthetic `c:` click on a picker row only hovers, it does + **not** activate — use `dc:` (double-click). `kp:esc` closes the dropdown. A picker anchored under + a field overlaps the rows below it, so clicks aimed there land on the picker's search bar. + +## Scene / asset staging + +- `AssetDatabase.ImportAsset` does **not** recurse into folders (children get no `.meta`) — pass + `ImportAssetOptions.ImportRecursive | ForceUpdate`. +- A scene instance of a prefab with a missing managed-reference type shows ``, not the notice. + Shoot missing-type UI in **Prefab Mode** (`PrefabStageUtility.OpenPrefab`). +- `SerializeReferenceSettings.ExcludedFolders` writes through to the **committed** + `ProjectSettings/SerializeReferenceSharedSettings.asset`. Restore it and check `git diff` after + using it to de-clutter Project References scans. +- Demo types for docs media live in `Aspid.FastTools/Assets/DevTests/Types/Scripts/DocsMedia/` under + the user-facing namespace `Game.Combat` — the namespace shows up in picker breadcrumbs, so no + `DevTests` naming there. + +## GIF assembly + +`ffmpeg` concat with a per-frame `duration`, then two-pass `palettegen`/`paletteuse`. +**Do not use `-fps_mode vfr`** — it silently collapsed a sequence into 6 identical frames. diff --git a/.claude/skills/unity-pipeline/SKILL.md b/.claude/skills/unity-pipeline/SKILL.md new file mode 100644 index 00000000..51396d26 --- /dev/null +++ b/.claude/skills/unity-pipeline/SKILL.md @@ -0,0 +1,141 @@ +--- +name: unity-pipeline +description: Drive this repo's running Unity Editor from the shell via the official `unity` CLI and the `com.unity.pipeline` package — recompile/test loop, C# eval, console logs, Game/Scene screenshots, the `sr_gate` SerializeReference check, and authoring new `[CliCommand]` commands. Use whenever a task needs to compile, test, inspect, or script the live Editor instead of inferring behaviour from source. +user-invocable: false +allowed-tools: + - Bash +--- + +# Unity Pipeline (live Editor control) + +Stack: `unity` CLI (`~/.unity/bin/unity`, normally already on `PATH`) plus the UPM package +`com.unity.pipeline`, pinned in `Aspid.FastTools/Packages/manifest.json`. The Editor exposes +~460 commands; the CLI just forwards to them, so no CLI update is needed when the package adds one. + +Generic CLI surface (editors, licenses, headless `build`/`test`, `mcp`) is covered by the vendor +`unity-cli` skill. **This file covers only what is specific to Aspid.FastTools.** + +## 1. Always target a project explicitly + +Several Editors run at once here — the main checkout plus `.claude/worktrees/shared-*`. A bare +`unity command …` fails with `COMMAND_FAILED / Multiple Unity Editor instances found`. + +```bash +P="$PWD/Aspid.FastTools" # from the repo (or worktree) root +unity command --project-path "$P" --format json +``` + +## 2. Health check — `status`, not `pipeline list` + +```bash +unity status --format json # port, project, version, PID, state per Editor +``` + +Reads the per-Editor lockfile instead of probing over HTTP: faster, and stale instances are +reported as `unreachable`. `unity pipeline list` is the wrong tool for this — a *closed* Editor +can still show `Running=true` there from a leftover lock. Use `pipeline list` only for package +install/version questions. + +`unity list --project-path "$P"` prints every command the Editor exposes with its parameter +schema — use it instead of guessing argument names. + +## 3. Edit → verify loop + +```bash +unity command set_autotick --enable true --project-path "$P" # do this first +unity command recompile --project-path "$P" +unity command recompile_status --project-path "$P" # poll: idle|triggered|compiling|completed|up_to_date +unity command run_tests --mode editor --filter --project-path "$P" +``` + +- **`set_autotick` first.** Unfocused Unity throttles its update loop, so a recompile or test run + started from the shell can hang indefinitely without it. +- **The server goes silent during domain reload** — commands return empty. Poll with retries; the + connection restores itself afterwards (no reinstall needed). +- `run_tests` right after another command can return `result=null` (runner still busy) — retry. +- Long runs: `--async_tests true`, then poll `test_status`; abort with `cancel_tests`. + +## 4. C# eval + +```bash +unity command eval 'return UnityEditor.EditorUtility.scriptCompilationFailed ? "COMPILE_FAILED" : "OK";' \ + --project-path "$P" --format json +``` + +- **The trailing `;` is mandatory** — without it the snippet fails to compile with `CS1002`. +- **The JSON response echoes your source back** inside `parameters`. Grep only the result field + (`grep -E '"result": *"[^"]*"'`), otherwise a string literal in your own code matches and, for + the snippet above, "COMPILE_FAILED" is reported even on success. +- `eval_file ` runs a `.cs` file through the same path. + +Compilation ground truth is `scriptCompilationFailed`, not the absence of console errors. + +## 5. Console and screenshots + +```bash +unity command get_console_logs --severity error --limit 50 --project-path "$P" +unity command screenshot --view game --output ./shot.png --project-path "$P" +``` + +`severity`: `all | log | warning | error`. Without `--output`, `screenshot` (and +`capture_game_view` / `capture_scene_view`) writes a timestamped PNG under +`/Temp/pipeline-screenshots/`. + +For docs media beyond a plain Game/Scene frame — floating Inspectors, `TypeSelectorWindow` +dropdowns, GIFs — use the `editor-media-capture` skill instead; those need window-ID capture and +focus tricks the pipeline commands do not cover. + +## 6. `sr_gate` — SerializeReference gate without a batchmode relaunch + +```bash +unity command sr_gate --scope full --project-path "$P" --format json +``` + +| Arg | Values | Default | +|---|---|---| +| `scope` | `missing` \| `required` \| `full` | `missing` | +| `warn_only` | force Warn severity — reports, `exitCode` 0 | `false` | +| `fail` | force Fail severity — `exitCode` 1 on violations | `false` | + +Returns `{success, scope, severity, violationCount, exitCode, violations[]}`, each violation +carrying `kind`, `assetPath`, `fieldPath`, `storedType`, `fileId`, `rid`. Source: +`Aspid.FastTools/Assets/DevTests/CliCommands/Editor/SerializeReferenceGateCommands.cs` — a thin +wrapper over `SerializeReferenceGateScanner`, so it reflects the same rules as the CI gate. + +## 7. Adding a command + +```csharp +using Unity.Pipeline.Commands; + +internal static class MyCommands +{ + [CliCommand("my_command", "What this does")] // MainThreadRequired=true by default + internal static object Run( + [CliArg("text", "Input", Required = true)] string text, + [CliArg("count", "Repeat count")] int count = 1) + => new { echoed = text, count }; // any object is serialised into `result` +} +``` + +- `[CliArg]` is optional (the C# parameter name is used); defaults come from the C# default value. +- Mutating commands take `confirm` + `dry_run` parameters by convention. Multi-field input goes + through a class implementing `IStructuredCommandInput`. +- `MainThreadRequired=false` only for thread-safe read-only work; `RuntimeOnly=true` marks a + Player-runtime command (hidden from the Editor listing). +- Discovery is via `TypeCache` — **a new command appears only after a recompile**. + +**Placement rule:** never inside `Packages/tech.aspid.fasttools` — it would ship to users and drag +the experimental `com.unity.pipeline` dependency with it. Put it in +`Aspid.FastTools/Assets/DevTests/CliCommands/Editor/` (asmdef +`Aspid.FastTools.DevTests.CliCommands.Editor`, referencing `Unity.Pipeline` plus the package +assemblies it needs). Package internals are already opened to it via `InternalsVisibleTo`. + +## Maintenance + +```bash +unity upgrade --channel beta --check # CLI; --rollback restores the previous binary +unity pipeline list-versions --format json # package versions in the registry +unity pipeline upgrade --project-path "$P" # bumps manifest.json — a committed file, so review the diff +``` + +The package is experimental (`*-exp.*`); treat a version bump as a real change, not a chore. diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Types/CLAUDE.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Types/CLAUDE.md new file mode 100644 index 00000000..e46a4fab --- /dev/null +++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Types/CLAUDE.md @@ -0,0 +1,47 @@ +# Types — Editor + +Editor side of `SerializableType` and `[TypeSelector]`: the drawers, the picker window, and the +constraint resolution behind them. Runtime contracts live in `Unity/Runtime/Types/`. + +## `[TypeSelector]` drives two different field shapes + +| Field shape | Meaning | Drawn by | +|---|---|---| +| `string` | Assembly-qualified name; also what backs `SerializableType` | this folder (`Drawers/`) | +| `[SerializeReference]` managed reference | Picking a type **instantiates** it | `../SerializeReferences/` | + +`TypeSelectorPropertyDrawer` dispatches on `SerializedProperty.propertyType`, so the same attribute +lands in two different code paths. **The managed-reference path is not in this folder** — look under +`Unity/Editor/Scripts/SerializeReferences/` for it. + +The candidate list defaults to the field's declared type; a base type narrows it — +`[TypeSelector(typeof(IMelee))]`. Correct usage is enforced at compile time by the analyzer's +`AFT*` rules, so a wrong constraint is a build error, not a silent empty picker. + +## Layout + +``` +Types/ +├── TypeSelectorConstraintResolver.cs ← resolves the attribute's constraint to a candidate set +├── TypeSelectorHelpers.cs / TypeUtility.cs / SerializableTypeUtility.cs +├── Drawers/ +│ ├── TypeSelectorPropertyDrawer.cs ← entry point, dispatches on propertyType +│ ├── SerializableTypePropertyDrawer.cs +│ ├── ComponentTypeSelectorPropertyDrawer.cs +│ ├── TypeIMGUIPropertyDrawer.cs ← static IMGUI body +│ └── TypeUIToolkitPropertyDrawer.cs ← static UIToolkit body +├── Selectors/ +│ ├── TypeSelectorWindow.cs ← the picker window +│ ├── TypeSelectorView*.cs ← partials: View / Rows / Input / Navigation / Generics +│ ├── HierarchyBuilder.cs, NamespaceNode.cs, TreeNode.cs, NavigationController.cs +│ ├── GenericTypeResolver.cs, TypeSelectorFilter.cs, TypeSelectorIconResolver.cs +│ └── Settings/ +│ ├── TypeSelectorSettings.cs ← project settings +│ ├── TypeSelectorPreferences.cs ← per-user EditorPrefs (favorites / recents) +│ └── TypeSelectorSettingsView.cs +├── VisualElements/ ← TypeField, InspectorTypeField +└── Extensions/ ← TypeExtensions +``` + +The `Drawers/` + `Selectors/` + `VisualElements/` split mirrors the sibling `Ids/` feature — see +`../Ids/CLAUDE.md`, which follows the same shape. diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Types/CLAUDE.md.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Types/CLAUDE.md.meta new file mode 100644 index 00000000..4a270651 --- /dev/null +++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/Types/CLAUDE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 412184219d22a4a589c95040a6be6f44 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/VisualElements/Internal/CLAUDE.md b/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/VisualElements/Internal/CLAUDE.md new file mode 100644 index 00000000..99b213b3 --- /dev/null +++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/VisualElements/Internal/CLAUDE.md @@ -0,0 +1,33 @@ +# Internal editor components + +Shared UIToolkit building blocks for this package's own editor UI. Everything here is `internal` — +in an `internal` class members are declared `internal` (or narrower), never `public`. + +## Component convention + +One subfolder per component under `Components/`, always the same four parts: + +``` +Components/AspidGradientButton/ +├── AspidGradientButton.cs ← the VisualElement +├── AspidGradientButtonPreset.cs ← {Name}Preset +├── AspidGradientButtonExtensions.cs ← fluent extensions +└── Styles/ ← structs binding USS custom properties +``` + +- **Every component loads `AspidStyles.DefaultStyleSheet` first**, before its own sheet. +- Enums belong on their `Style` struct as a nested type named `Type` — not at namespace level. +- Styling goes in USS; code only calls `.AddClass()`. Class-name and `--aspid-*` variable grammar + lives in `../../../Resources/UI/CLAUDE.md` — read it before touching either. + +## Shared pieces + +| Path | What | +|---|---| +| `Styles/AspidStyles.cs` | default stylesheet + shared USS constants | +| `Styles/StatusStyle.cs`, `ThemeStyle.cs`, `InlineStyle` | shared style helpers | +| `NavRing.cs`, `HoverSweep.cs` | keyboard nav ring and hover sweep, shared across window tabs | +| `DoubleClickTracker.cs` | double-click detection | + +`ICustomStyleExtensions` is **not** here — it ships in runtime, at +`Unity/Runtime/VisualElements/Extensions/ICustomStyle/`. diff --git a/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/VisualElements/Internal/CLAUDE.md.meta b/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/VisualElements/Internal/CLAUDE.md.meta new file mode 100644 index 00000000..25394c79 --- /dev/null +++ b/Aspid.FastTools/Packages/tech.aspid.fasttools/Unity/Editor/Scripts/VisualElements/Internal/CLAUDE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 01018bab5a753409186f75f659bc5596 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/CLAUDE.md b/CLAUDE.md index 226832b0..1a314665 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,13 +22,13 @@ The Unity package itself has no CLI build — Unity compiles it when the project | Assembly | Location | Purpose | |---|---|---| -| `Aspid.FastTools` | `Source/` | Pure C# type extensions, no Unity dependency | -| `Aspid.FastTools.Unity` | `Unity/Runtime/` | Runtime: Types, Enums, Ids, ProfilerMarkers, VisualElements — ships with player builds | -| `Aspid.FastTools.Unity.VisualElements.Math` | `Unity/Runtime/VisualElements/Extensions/INotifyValueChanged/Math/` | Satellite: `INotifyValueChanged` extensions for `float2/3/4` etc. | -| `Aspid.FastTools.Unity.Editor` | `Unity/Editor/Scripts/` | All editor-only tooling, excluded from builds | -| `Aspid.FastTools.Unity.Editor.SerializeReferences.Yaml` | `Unity/Editor/Scripts/SerializeReferences/Yaml/` | Asset-YAML parsing, isolated in its own assembly | +| `Aspid.FastTools` | `Source/` | Pure C#, no Unity dependency | +| `Aspid.FastTools.Unity` | `Unity/Runtime/` | Ships with player builds | +| `Aspid.FastTools.Unity.VisualElements.Math` | `Unity/Runtime/VisualElements/Extensions/INotifyValueChanged/Math/` | Satellite: `INotifyValueChanged` for `float2/3/4` etc. | +| `Aspid.FastTools.Unity.Editor` | `Unity/Editor/Scripts/` | Editor-only, excluded from builds | +| `Aspid.FastTools.Unity.Editor.SerializeReferences.Yaml` | `Unity/Editor/Scripts/SerializeReferences/Yaml/` | Asset-YAML parsing, isolated on purpose | -Plus: `Tests/Editor/` — Unity-side editor tests (Unity Test Runner); `Samples~/` — optional samples (UPM tilde convention, imported via Package Manager); `Unity/Editor/Resources/UI|Icons/` — editor stylesheets and icon assets. +Plus: `Tests/Editor/` (Unity Test Runner), `Samples~/` (UPM tilde convention — imported via Package Manager), `Unity/Editor/Resources/UI|Icons/`. **Assembly boundary rule:** `Unity/Runtime/` code must NOT reference `UnityEditor` — it ships with player builds. @@ -36,21 +36,18 @@ Plus: `Tests/Editor/` — Unity-side editor tests (Unity Test Runner); `Samples~ ### Feature map -| Feature | Location | Non-obvious bits | -|---|---|---| -| ProfilerMarkers | `Unity/Runtime/ProfilerMarkers/` | `this.Marker()` returns a call-site-unique `ProfilerMarker`; the source generator creates one per (class, method, line) | -| SerializableType | `Unity/Runtime/Types/` | Wraps `System.Type` via assembly-qualified name, lazy resolution; `SerializableType` adds generic constraints | -| TypeSelector | `Unity/Editor/Scripts/Types/` | `[TypeSelector]` drives two field shapes: a `string` (AQN, also backing `SerializableType`) and a `[SerializeReference]` managed reference (picking a type instantiates it; candidate list defaults to the field's declared type, base types like `[TypeSelector(typeof(IMelee))]` narrow it). `TypeSelectorPropertyDrawer` dispatches on `SerializedProperty.propertyType`; the managed-reference path lives under `SerializeReferences/`. Settings in `Types/Selectors/TypeSelectorSettings*`; usage validated by analyzer `AFT*` rules | -| SerializeReference tooling | `Unity/Editor/Scripts/SerializeReferences/` | `SerializeReferenceWindow` (menu `Tools/Aspid 🐍/FastTools/…`), tabs: Welcome / Asset References / Project References / Settings. Subsystems: `Windows/`, `Index/`, `Diagnostics/`, `Yaml/` (own asmdef) | -| Settings / Preferences | `Unity/Editor/Scripts/Settings/` | `AspidFastToolsPreferencesProvider` + `AspidSettingsUI`; per-feature settings live next to their feature, the window's **Settings** tab aggregates them | -| EnumValues\ | `Unity/Runtime/Enums/` | Serializable enum→value dictionary; handles `[Flags]` | -| Id Registries | `Unity/Runtime/Ids/` + `Unity/Editor/Scripts/Ids/` | `IdRegistry` (ScriptableObject) maps names to stable int IDs; each `IId` struct binds to exactly **one** registry (enforced by `IdRegistryResolver`); `IdStructGenerator` emits struct boilerplate. Editor internals: `Unity/Editor/Scripts/Ids/CLAUDE.md` | -| SerializedProperty extensions | `Unity/Editor/Scripts/SerializedProperties/` | Fluent chainable (`.SetValue()`, `.Apply()`), split across partial files | -| VisualElement extensions | `Unity/Runtime/VisualElements/Extensions/` | Fluent UIToolkit API; subdirectories by element type plus top-level partials. Editor-side command extensions in `Unity/Editor/Scripts/VisualElements/Extensions/` | -| Internal editor components | `Unity/Editor/Scripts/VisualElements/Internal/` | One subfolder per component: element class + `{Name}Preset` + fluent extensions + `Styles/` structs for USS bindings. Shared helpers in `Styles/` (`AspidStyles`, `StatusStyle`, `ThemeStyle`, `InlineStyle`); `ICustomStyleExtensions` lives in runtime (`Extensions/ICustomStyle/`). All components load `AspidStyles.DefaultStyleSheet` first; enums are nested `Type` on their `Style` structs | -| IMGUI scopes | `Unity/Editor/Scripts/IMGUI/` | Disposable `VerticalScope`/`HorizontalScope`/`ScrollViewScope` with `Rect` properties | -| Editor extensions | `Unity/Editor/Scripts/Extensions/` | `GetScriptName()` / `GetScriptNameWithIndex()` — respects `[AddComponentMenu]`, index suffix for duplicates | -| Welcome view | `Unity/Editor/Scripts/Welcome/` | **Welcome** tab of `SerializeReferenceWindow` + `WelcomeWindowStartup` (auto-show on first import); lists installable samples from `package.json` | +Feature folders under `Unity/Runtime/` and `Unity/Editor/Scripts/` are named after the feature (`Enums`, `Ids`, `ProfilerMarkers`, `Types`, `VisualElements`, `IMGUI`, `SerializedProperties`, `Settings`, `Welcome`, `SerializeReferences`, `Extensions`) — `ls` finds a feature faster than this file can list it. Only what the layout does *not* tell you: + +| Feature | Non-obvious bits | +|---|---| +| ProfilerMarkers | `this.Marker()` returns a call-site-unique `ProfilerMarker` — the source generator emits one per (class, method, line) | +| TypeSelector | One attribute, two field shapes — a `string` (AQN) and a `[SerializeReference]` managed reference. **The managed-reference path lives under `SerializeReferences/`, not `Types/`.** Details: `Unity/Editor/Scripts/Types/CLAUDE.md` | +| SerializeReference tooling | `SerializeReferenceWindow` (menu `Tools/Aspid 🐍/FastTools/…`), tabs Welcome / Asset References / Project References / Settings; subsystems `Windows/`, `Index/`, `Diagnostics/`, `Yaml/` (own asmdef) | +| Id Registries | Spans `Unity/Runtime/Ids/` + `Unity/Editor/Scripts/Ids/`. `IdRegistry` (ScriptableObject) maps names to stable int IDs; each `IId` struct binds to exactly **one** registry (enforced by `IdRegistryResolver`); `IdStructGenerator` emits the struct boilerplate. Editor internals: `Unity/Editor/Scripts/Ids/CLAUDE.md` | +| Settings / Preferences | Per-feature settings live next to their feature; `AspidFastToolsPreferencesProvider` + `AspidSettingsUI` and the window's **Settings** tab only aggregate them | +| Internal editor components | Strict four-part layout per component (element + `{Name}Preset` + fluent extensions + `Styles/`) — follow it when adding one. Conventions: `Unity/Editor/Scripts/VisualElements/Internal/CLAUDE.md` | +| VisualElement extensions | Runtime fluent API in `Unity/Runtime/VisualElements/Extensions/`; editor-side command extensions in `Unity/Editor/Scripts/VisualElements/Extensions/` | +| Welcome view | Not its own window — a tab of `SerializeReferenceWindow`, plus `WelcomeWindowStartup` (auto-show on first import); lists installable samples from `package.json` | ### Editor Code Conventions @@ -70,3 +67,7 @@ PostToolUse hooks (wired in `.claude/settings.json`): - `.claude/hooks/rebuild-generators-on-change.sh` — on `Edit`/`Write` to `*.cs` under `Aspid.FastTools.Generators/Aspid.FastTools.Generators/`, rebuilds the generator and redeploys the DLL into the Unity package. Tests and Sample are skipped — keep that scope when changing the hook. - `.claude/hooks/rebuild-analyzers-on-change.sh` — same for the analyzer submodule (Tests/Sample skipped): rebuilds and copies the DLL into the package. + +Skills in `.claude/skills/`: `build-generator` / `build-analyzer` (build + deploy the Roslyn DLLs), `sync-readmes`, `unity-pipeline` (drive the live Editor via the `unity` CLI + `com.unity.pipeline` — recompile/test loop, `eval`, `sr_gate`, authoring `[CliCommand]`s), `editor-media-capture` (docs screenshots/GIFs of editor windows). + +**Driving the Editor:** several Editors run at once (main checkout + `.claude/worktrees/shared-*`), so every `unity command` needs `--project-path`. Use `unity status` for liveness, not `unity pipeline list`. Details and gotchas live in the `unity-pipeline` skill.