This file provides guidance to AI coding assistants β including Claude Code, GitHub Copilot, and similar tools β when working with code in this repository.
Hoist-react is the client-side component of the Hoist web application development toolkit, built
by Extremely Heavy Industries (xh.io). It is a library package (not a standalone app) published
as @xh/hoist and consumed by Hoist application projects. The server-side counterpart is
hoist-core.
- Language: TypeScript
- Framework: React with MobX for reactive state management
- Package manager: Yarn
IMPORTANT: Do not guess at hoist-react APIs, component props, or framework patterns. Hoist-react ships dedicated tools that provide structured access to all framework documentation and TypeScript type information. You MUST use these tools before modifying or extending hoist-react code to understand existing architecture, configuration patterns, and common pitfalls. The package READMEs and concept docs are the authoritative reference for how Hoist works -- skipping them risks producing code that conflicts with established patterns or misses built-in functionality.
Two interfaces are available. Both share the same underlying registries and produce identical output:
MCP Server (hoist-react) -- When working in the hoist-react repository, an MCP server is
configured via .mcp.json and is very likely already available. Use the hoist-search-docs,
hoist-list-docs, hoist-search-symbols, hoist-get-symbol, and hoist-get-members tools, plus
hoist://docs/{id} resources for direct document access.
CLI Tools -- For environments without MCP support, or when you prefer shell commands. These are
real bin entries in the hoist-react package.json β invoke them exactly as shown with npx:
# Documentation
npx hoist-docs search "grid sorting" # Search all docs by keyword
npx hoist-docs read cmp/grid # Read a specific doc by ID
npx hoist-docs list # List all available docs
npx hoist-docs conventions # Print coding conventions
npx hoist-docs index # Print the documentation catalog
# TypeScript symbols and types
npx hoist-ts search GridModel # Search for symbols and class members
npx hoist-ts symbol GridModel # Get detailed type info for a symbol
npx hoist-ts members GridModel # List all members of a class/interfaceUse search for discovery β it matches against symbol names, JSDoc content, and own member
names. Multi-word queries use AND logic (e.g. "panel modal" finds ModalSupportModel via its
JSDoc, "StoreRecord raw" finds StoreRecord via its raw property). Also searches public members
of every exported class and every exported *Config interface (e.g. GridConfig, StoreConfig)
by owner name, member name, and JSDoc β so a query for "groupSortFn" reaches both GridModel
and GridConfig. Use symbol and members when you already know the exact PascalCase name.
When multiple symbols share a name (e.g. View exists in both cmp/viewmanager and data/cube),
pass the file path to symbol or members to disambiguate β the tools will hint when this is
needed. Run npx hoist-docs --help and npx hoist-ts --help for full usage.
Recommended workflow: Start with the documentation index (hoist-docs index or hoist://docs/index)
to discover available docs. Use the "Quick Reference by Task" table to find the right doc for your
goal, then read the relevant README(s). Supplement with TypeScript symbol lookups for precise API
details. The docs provide architectural context and common pitfalls; the TypeScript tools provide
exact signatures, decorators, and member listings.
A Docker-based server providing GitHub API tools (issues, PRs, code search, etc.) via the
official github-mcp-server image. Configured in .mcp.json but not enabled by default β
it requires Docker and an authenticated GitHub CLI, which not every developer keeps running.
To enable:
- Install and start Docker.
- Install the GitHub CLI (
brew install gh) and authenticate withgh auth login. The server invokesgh auth tokenat startup to fetch a token from the macOS Keychain (orgh's credential store on other platforms), so no plaintext token needs to live in your shell environment. - Add
"github"toenabledMcpjsonServersin.claude/settings.local.json(local settings merge with the sharedsettings.jsonβ enabling locally does not affect other developers):{ "enabledMcpjsonServers": ["hoist-react", "github"] }
If Docker is not running or gh is not authenticated when the server is enabled, Claude Code
may show errors on startup β remove "github" from your local settings to resolve.
Fallback when not enabled: The gh CLI provides functionally equivalent access to the same
operations (gh pr view, gh issue list, gh api, gh pr create, etc.). Prefer gh over
crafting raw curl calls to the GitHub API.
A JetBrains MCP server is also configured in .mcp.json, providing tools for interacting with
the IntelliJ IDE (file navigation, code inspections, refactoring, terminal commands, etc.).
This server must be enabled within IntelliJ's settings and requires a running IDE instance to
connect. Add "jetbrains" to enabledMcpjsonServers in .claude/settings.local.json to
enable it for Claude Code.
yarn install # Install dependencies
yarn lint # Lint all code (JS/TS + SCSS)
yarn lint:code # Lint JavaScript/TypeScript only
yarn lint:styles # Lint SCSS only
npx tsc --noEmit # Type check (declarations only, no emit)This is a library β it has no dev server or standalone build. To run locally, use a wrapper
application project (e.g., Toolbox) that includes @xh/hoist as a dependency.
The framework is built around three core artifact types:
-
Models (
HoistModel) - State management and business logic classes. Properties are marked with MobX decorators to make them observable. Models form hierarchies reflecting app structure. -
Components (
hoistCmp) - Functional React components wrapped with Hoist support including MobX reactivity and model lookup. Created viahoistCmp.factory({}). -
Services (
HoistService) - Singleton classes for data access and app-wide business logic. Installed viaXH.installServicesAsync()and accessed asXH.myCustomService.
See /core/README.md for detailed coverage of all three artifact types.
XH (in core/XH.ts) is the top-level API entry point. It provides:
- Access to all Hoist services (e.g.,
XH.configService,XH.fetchService,XH.myCustomService) - App metadata (
XH.appCode,XH.appVersion) - Common operations (
XH.toast(),XH.confirm(),XH.handleException())
See /core/README.md for the full XH API and
/svc/README.md for built-in service details.
Hoist strongly encourages rendering components via element factories (created at component
definition time via the hoistCmp.factory util) over JSX. This minimizes XML-style markup and
keeps client side codebases anchored in standard TypeScript/JavaScript syntax:
// Element factory style - strongly preferred
panel({
title: 'Users',
items: [grid({model: gridModel})],
bbar: toolbar(button({text: 'Save'}))
})// JSX also fully supported - but rarely used by XH
< Panel
title="Users"
bbar={<Toolbar><Button text={'Save'}/></Toolbar>}
>
<Grid model={gridModel}/>
</Panel>Factories can take a config object for props, using the key item/items for children. A shortcut
form also exists where factories are passed children directly as arguments, when no other props
are required. Factories all support an omit prop for conditional rendering.
Important β items in, children out: item/items are Hoist's calling API. Inside a
render function, those values arrive as the standard React children prop (because the factory
spreads them as rest args to React.createElement). The canonical pattern when authoring a
container component is therefore to destructure children from props and pass them downstream as
items to an inner factory. See
Authoring a Container Component
in the core README for the full explanation, examples, and the $item/$items escape hatch for
components whose underlying API genuinely has its own items prop.
See /core/README.md for full element factory API including conditional
rendering with omit and factory creation.
All Hoist artifacts extend HoistBase, which provides:
addAutorun()/addReaction()- Managed MobX subscriptions (auto-disposed on destroy)makeObservable()- Called in constructors to set up MobX observables/actions/computeds@observableMobX decorator - Marks properties as observable state@actionMobX decorator - Marks methods that modify observable state@bindableHoist decorator - Marks properties as observable and generates setter methods automatically marked as@action- e.g.,setMyProp(value)for propertymyProp(Hoist custom decorator). Setter convention: If a class defines an explicit publicsetFoo()method, call it (it likely has additional logic). Otherwise for auto-generated@bindablesetters, prefer direct assignment (model.myProp = value) over calling the generated setter (model.setMyProp(value)).@computedMobX decorator - Marks getter properties as derived/computed state
@manageddecorator - marks child objects for automatic cleanup - apply to properties holdingHoistBaseinstances or arrays of such instances.destroy()- Lifecycle cleanup method.HoistBasesuperclass implementation auto-disposes managed subscriptions and child objects.
See /core/README.md for detailed HoistBase API, persistence support, and
common pitfalls.
- Methods returning Promises are suffixed with
Async(e.g.,loadUsersAsync) - Promise extensions:
catchDefault(),track(),timeout(),linkTo()
Always use Hoist's built-in input components (textInput, numberInput, select, picker,
checkbox, switchInput, dateInput, textArea, etc.) rather than raw HTML <input>,
<select>,
or <textarea> elements. Hoist inputs provide consistent styling, model binding, and proper
integration with the framework's theming and layout system. Raw HTML elements require manual
wrappers and custom SCSS that duplicate what Hoist already provides.
Components in /desktop/ and /mobile/ are platform-specific. Shared code lives in /cmp/,
/core/, /data/, and /svc/.
For the full conventions reference β import organization, class structure, component patterns,
null handling, async patterns, error handling, logging, and CSS naming β see
docs/coding-conventions.md. The principles below are the most
important guidelines to internalize:
- Don't Repeat Yourself β Extract shared logic into utilities, base class methods, or helpers. Balance DRY against readability β extract when a genuine, stable pattern exists, not prematurely.
- Clear, descriptive naming β Names should convey intent and read naturally. Be descriptive but
not verbose (
selectedRecord, notrortheCurrentlySelectedRecordFromTheStore). - Prefer lodash for collection/object utilities β it's null-safe, battle-tested, and aids
readability. Use native JS only when equally expressive (e.g.,
array.map(),array.filter()). - Keep code concise β Favor direct, compact expression over verbose or ceremonial patterns.
Use Hoist's own utilities (
withDefault,throwIf, element factories) to reduce boilerplate. - Named exports only β No default exports. Components export
[Component, factory]pairs from library code, factory only from application/impl code. nulloverundefinedβ Usenullas the "no value" sentinel. Check with== null(loose equality) for concise null-or-undefined testing.- No em dashes in code comments β Use
-(spaced hyphen) not em dashes (β) in.tscomments and JSDoc. Em dashes cause tooling issues and are reserved for prose.mddocs. Other Unicode characters (arrows, symbols, accented letters, etc.) are fine in code comments when they aid clarity.
Commit messages, PRs, and comments: Do not hard-wrap lines at a fixed column width in commit message bodies, pull request descriptions, or issue/PR comments β let the viewing tool handle display wrapping. However, do use line breaks for structure: separate logical points into bullet lists, use blank lines between paragraphs, and break after the subject line. Keep PR descriptions concise β XH developers review these regularly, so favor brief summaries over exhaustive detail. Bullet the key changes and let the diff and any upgrade notes speak for themselves.
Feature branch workflow: On feature branches, prefer multiple small commits over amending β PRs
are squash-merged into develop, so intermediate commits are collapsed automatically. Never
force-push a feature branch; if the branch falls behind develop, use a simple merge commit rather
than a rebase. Merge commits and extra commits are harmless on feature branches and are squashed out
on merge, while force-pushes risk losing work and complicate collaboration.
The project changelog is CHANGELOG.md at the repository root. New entries go under the topmost
-SNAPSHOT version heading, using emoji-prefixed section headers (e.g. ### π New Features,
### π Bug Fixes). Use past-tense, action-driven language and name specific classes, methods, and
config keys in backticks. Keep entries concise β one bullet per change, 1-3 lines max. Upgrade notes
provide granular detail when needed; the changelog should not. Hard-wrap changelog entries at 100
characters (unlike commit messages and PR descriptions, which should not be wrapped). See
docs/changelog-format.md for the full format reference including
section headers, voice guidelines, and breaking change requirements.
- MobX - Reactive state management
- ag-Grid - Data grid (requires separate license for enterprise features)
- Blueprint - UI component library
- Router5 - Client-side routing
- Highcharts - Charting (requires separate license)
Toolbox is XH's example application showcasing hoist-react patterns and components. It provides real-world usage examples of models, components, services, and other framework features.
- GitHub: https://github.com/xh/toolbox
- Local checkout:
../toolbox(relative to hoist-react root) - likely exists for Hoist library developers only. Note that the client-side code that uses hoist-react is in the../toolbox/client-app/srcdirectory - focus your attention there.
When working on hoist-react library code or documentation, reference Toolbox for practical examples of how features are used in applications. Note that the local checkout is specific to the Hoist development environment and would not be available to general application developers who have hoist-react as a dependency.