Skip to content

MemberJunction Developer Guides

This folder is the home for cross-cutting, “read this before you build that” reference docs. Package-specific docs live in packages/<Pkg>/CLAUDE.md or packages/<Pkg>/README.md and are linked from here when relevant.

If you’re about to start work in one of the areas below, read the guide first — these documents capture patterns that have already been litigated.

This file is the complete index of MJ development guides. The root CLAUDE.md points here rather than duplicating the list, so this is the one place to look — and the one place to update when you add a guide.

  • Building Applications on MemberJunction — The hub guide for using MJ as a first-class application development platform. Explains the metadata-driven, schema-to-app model, the unified-TypeScript / isomorphic object model, AI-native app patterns, and links out to the authoritative README/guide for every layer (data modeling, CodeGen, entities, API, UI, Actions, AI, deployment). Start here if you’ve got data in MJ and want to build on it.
  • Framework Comparison — Objective comparison of MJ against Next.js/Vercel, Supabase, Rails, Django, and a hand-rolled Node+ORM+SPA stack: where each shines, where MJ differs, and how to choose. Companion to the app-building guide.
  • Agent Framework Comparison — Objective comparison of MJ’s AI agent framework and infrastructure against LangGraph, CrewAI, AG2/AutoGen, the vendor agent SDKs, Semantic Kernel, LlamaIndex, and the TypeScript-native frameworks: orchestration paradigms, payload/state governance, HITL, model gateway, observability, memory, permissions, and deployment (self-host or managed via MJ Central). Companion to the app-stack comparison above.
  • BaseEntity Server-Side Patterns — Use before writing a new server-side entity subclass under MJCoreEntitiesServer. Covers the persisted-embedding pattern (Save() + EmbedTextLocal + engine cache sync), cross-record invariants via ValidateAsync (NOT DB triggers), and FK cleanup before delete. Reference implementations: MJAIAgentNoteEntityServer, MJTagEntityServer, MJTagScopeEntityServer. Lift the recipes from there — don’t reinvent.
  • Soft Deletes Guide — How DeleteType='Soft' works end to end: CodeGen-managed __mj_DeletedAt column, filtered base views, soft-delete spDelete, interaction with AllowRecordMerge.
  • UUID Comparison Guide — Critical patterns for comparing UUIDs across SQL Server (uppercase) and PostgreSQL (lowercase):
    • Always use UUIDsEqual() instead of === for UUID comparisons
    • Use NormalizeUUID() for Set/Map key operations
    • Angular template binding patterns
    • Automated enforcement tests
  • Caching & Real-Time Synchronization Guide — Multi-tier server caching, RunView cache behavior, BypassCache, BaseEntity event-driven invalidation, Redis pub/sub, real-time browser sync. Read before touching caching or write-after-write data flow. Also documents the BaseEngine integration (ObserveProperty, DataChange$) that gives reactive UIs over entity caches for free.
  • Keyset Pagination Guide — Deep pagination via RunViewParams.AfterKey (keyset/seek) instead of StartRow, for background jobs and bulk processing that iterate all records of a large entity. Keyset stays O(log N) per page regardless of depth. Single-column PK only — throws AfterKeyNotSupportedError for composite-PK entities. Reference implementations: ScheduledGeocodingAction, VectorBase, EntityVectorSyncer.
  • Remote Operations Guide — The typed, provider-routed Remote Operations primitive — BaseRemotableOperation<TInput,TOutput> (in @memberjunction/core) invoked from one call site on both client (marshalled over GraphQL) and server (in-process). Framed as MJ’s 4th data primitive alongside BaseEntity (CRUD), RunView (dynamic set reads), and RunQuery (stored queries). Covers: when to use it vs. an Action vs. a bespoke resolver; the three authoring modes driven by an MJ: Remote Operations row’s GenerationTypeManual (CodeGen emits the typed base, you write the InternalExecute subclass), AI (RO-4: MJRemoteOperationEntityServer has an LLM author the body from Description against the ambient input/provider/user/context contract + a JSONType Libraries declaration, gated by CodeApprovalStatus), and Default; the RemoteOperationGeneratorBase CodeGen emitter (@memberjunction/codegen-lib) → remote_operations.ts; the RouteOperation power-tool seam (IRemoteOperationProvider on ProviderBase); the auth chain (API-key scope ∥ user permissions + the RemoteOperationEngineBase Active/Approved metadata gate + per-op Authorize); and LongRunning progress — attached onProgress works both in-process AND over the wire (a per-call RemoteOperationProgress subscription channel published from the resolver); only detached fire-and-forget remains partial. Read before hand-rolling a resolver+client for a typed capability the browser and server both invoke, OR before adding a new operation (declare a metadata row, don’t hand-write the base). Complements (does not replace) the Transport-Layer guide and the Actions boundary. For a visual before/after with mermaid diagrams (the layers Remote Operations removes, from two real migrations), see the companion Remote Operations Showcase (lives with the @memberjunction/core package that defines the primitive).
  • Transport-Layer Architecture Guide — The canonical engine → resolver → GraphQL client → thin UI layering (plus the optional Action layer for agentic/workflow/low-code invocation) for any custom server-side capability the browser or an agent invokes — clustering, search, classify, LLM calls, “run this pipeline” buttons. Covers:
    • Why business logic lives in the framework-agnostic engine exactly once, and what each adapter layer must NOT do
    • Step-by-step: build the engine → thin TypeGraphQL resolver (ResolverBase + per-request user) → typed GraphQL<Feature>Client in @memberjunction/graphql-dataprovider → thinnest Angular wrapper (never inline gql) → optional Action that calls the engine directly
    • A decision table for which layers you actually need (and when to just use the generated entity CRUD layer instead)
    • JSON-string-field pattern for complex payloads, client/engine type decoupling, and reference implementations (GraphQLClusterClient, SearchKnowledgeResolver, etc.)
    • Read this before hand-writing any new resolver or GraphQL client. Not for plain entity CRUD — that’s already generated.
  • Record Set Processing & Record Processes Guide — MJ’s single hardened substrate for “do X to a set of an entity’s records” — and the saved, metadata-defined Record Process layer on top of it. Two layers: the substrate (@memberjunction/record-set-processor engine + -base seams) is a composition of three pluggable seams — Source (ArraySource/ViewSource/ListSource/FilterSource/KeysetSource, cursor-paginated + resumable) × Processor (per-record work → RecordResult) × Tracker (GenericProcessRunTrackerMJ: Process Runs/Process Run Details, or NoOpTracker/custom) — wrapped by an engine that owns batching, bounded concurrency, token-bucket rate-limiting, an error-rate circuit breaker, a budget gate, progress, a pause/cancel handshake, resume, and per-record isolation; and the Record Process (MJ: Record Processes row → the RecordProcessExecutor facade) with four work types (FieldRules — declarative field rules with a dry-run preview, self-writing; Action / Agent / Infer — wrapped by WriteBackProcessor when an OutputMapping is set), scopes (stored ScopeType View/List/Filter/SingleRecord + a runtime RecordProcessScopeOverride records/view/list/filter for “run against the current grid selection”), dry-run (the first-class ProcessRun.DryRun flag), and three triggers (OnDemand / OnChange / Schedule). The UI lives in @memberjunction/ng-record-process-studio (the Bulk Operations studio — editor + visual FieldRules builder + reactive history) and @memberjunction/ng-entity-action-ux (the in-grid RecordProcessRunnerUX runner); the typed API surface is the RecordProcess.RunNow + control Remote Operations. Read before building any bulk/set-iterating operation, adding a work type, or touching the substrate — don’t re-implement batching/resume/audit; compose the seams or declare a Record Process. Folder overview: packages/RecordSetProcessor/README.md.
  • Migration → CodeGen End-to-End Workflow — The workflow every schema change follows, whatever its type (new tables, new columns, constraint changes, column modifications, extended properties): write DDL → apply it → run CodeGen → consolidate the output into one replayable migration file. Explains why a single consolidated file matters (guaranteed replay order, atomic review, consistent state on fresh installs) and the separator convention for appending CodeGen output. Companion to migrations/CLAUDE.md, which holds the authoring rules themselves.
  • PG Migration Glossary — The vocabulary of the dual-platform (SQL Server → PostgreSQL) migration release process: curated metadata, metadata-sync migrations, the PG ledger and why it is immutable, marker files, migrate-only deployments, gapped databases, baselines, reseed migrations, and superseded updates. Read before discussing or debugging a PG release gap — issue #3253 (196 lost record deletions, a 126-byte marker, and a baseline dumped from a gapped database) is a lot easier to follow once these terms are pinned down.
  • PostgreSQL Schema Casing Guide — Why entity ClassName/CodeName and GraphQL type names go lowercase-broken on PostgreSQL (unquoted DDL folds schema names to lowercase, so __mj_BizAppsCommonmjbizappscommon…Entity instead of the published mjBizAppsCommon…Entity → TS2724 build break), and how MJ fixes it with a case-stable SchemaInfo.CanonicalSchemaName (sourced from mj-app.json schema.name via the OpenApp record, backfilled by CodeGen’s metadata-sync proc) preferred via COALESCE/?? in BOTH the vwEntities SQL view and the runtime GraphQL prefix. Net-zero on SQL Server. Read before touching schema-prefixed identifiers, the OpenApp install path, or spUpdateSchemaInfoFromDatabase — and note that proc lives in TWO synced copies (SS baseline/migration + PG metadataSupportObjects.ts). Includes the remediation runbook for existing PG installs (seed the OpenApp row).
  • Unified Permissions Guide — How MJ answers “can this user do this?” across every resource type (agents, artifacts, dashboards, queries, collections, entity rows, and anything you add) through one normalized model. Covers the three-concerns mental modelAuthorizations (named capability/feature gates via AuthorizationEvaluator), Entity Permissions / RLS (row-level CRUD filtering via getRowLevelSecurityWhereClause), and the unified PermissionEngine (per-record sharing/access) — and how to pick the right layer; the PermissionProviderBase contract + normalized vocabulary (PermissionAction = Read/Create/Update/Delete/Share/Execute/Admin, GranteeType, NormalizedPermission); the PermissionEngine aggregator that reads the MJ: Permission Domains catalog and ClassFactory-instantiates each @RegisterClass(PermissionProviderBase, …) provider (adding a domain is data + a class, never an engine edit); the 9 shipping domains + backing storage; the two access paths (cached runtime helper like AIAgentPermissionHelper, open-by-default, hot path — vs. the unified provider, closed-by-default, Sharing Center/audit) and why they differ; a recipe to add permissions to a new resource type (worked example); and mermaid diagrams (decision flow, provider fan-out, add-a-domain). Read before gating any action, building a sharing UI, auditing access, or adding a new permissioned resource.
  • Magic Link Access Guide — How to share an app-scoped, passwordless session with external users (MJ-issued RS256 magic links). Covers enabling the feature, the two-layer model (framework mechanism vs. per-deployment scenario config vs. runtime-provisioned users), and the recipe for defining an external-access scenario via metadata (restricted role + entity permissions + application role). Read before wiring up external/guest access — external user accounts are runtime-provisioned, but the role + permissions that scope them are version-controlled metadata.
  • Search Overview Guide — Decision tree across MJ’s search/lookup APIs — EntityByName/EntityByID (definition lookup), SearchEntity/SearchEntities (per-entity ranked hybrid search, see ENTITY_SEARCH_GUIDE), FullTextSearch (multi-entity DB-level FTS, see FULL_TEXT_SEARCH_GUIDE), and SearchEngine.Search (cross-source unified search with scopes, see SEARCH_SCOPES_AND_RAG_GUIDE). Read this first when you need to find records / definitions / cross-source matches — picking the wrong API can mean wasted round-trips or missed semantic matches.
  • Entity Search Guide — Per-entity ranked hybrid search via SearchEntity / SearchEntities. Reached from the Search Overview decision tree above.
  • Search Scopes & RAG+ Guide — Implementation guide for Search Scopes + agent RAG+ architecture: cross-source unified search with scopes via SearchEngine.Search. Companion to plans/search-scopes-rag-plus.md.
  • Content Segmentation GuideSegmentation decides what gets embedded; the embedding model only decides how. How MJ chunks content before vectorization via the pluggable BaseSegmenter strategies (StructuralText, SemanticText, Transcript, FixedWindow) in @memberjunction/ai-segmentation. Covers strategy selection, why the embedding and tagging chunk sites keep separate token budgets, writing a new strategy, and the multimodal model — one native vector per media chunk plus Description/Transcript as readable text. Read before changing how any pipeline chunks content, or before ingesting audio/video/images.
  • Content Autotagging Guide — The Knowledge Hub pluggable autotagging pipeline: providers, keyword extraction, taxonomy bridging.
  • Taxonomy & Tagging Guide — How the tag taxonomy itself is shaped, scoped, governed, grown, embedded, reviewed, and pruned. Companion to the autotagging guide.
  • Duplicate Detection & Merge Guide — Configuring and running intelligent duplicate detection on an entity, reviewing the AI’s verdicts, and merging. The principle: vectors filter, reasoning validates — a fast embedding/vector pass finds candidate near-duplicates, then an optional gated LLM pass judges the high-probability ones (Merge / NotDuplicate / Uncertain). The LLM never originates matches. Covers the cost/quality lever (ReasoningThreshold) and how to swap the reasoning model. Written for both users configuring it and PS teams implementing it for a client.
  • External Data Sources Guide — Backing an MJ Entity or Query with a remote system (Snowflake / MongoDB / external PostgreSQL / …) read live through a pluggable driver — no replication into the MJ DB (Linked-Server-style). Covers the engine → provider-dispatch → router → driver layering, configuring an ExternalDataSourceType + ExternalDataSource + credentials, read-only enforcement (ReadOnlyExternalBaseEntity), TTL caching, the RLS-refusal / injection-safe-filter / unsupported-param (AfterKey/aggregates/search) guards, connection auth self-heal, how to add a driver, and known limitations. Read before touching anything under packages/ExternalDataSources/ or wiring an entity/query to a remote source. Distinct from Integrations (scheduled pull-sync into MJ) and Materialization (plans/query-entity-materialization.md, persist hot results into MJ tables).
  • Agent Memory Guide — The complete agent-memory architecture — note lifecycle (Provisional → Active → Archived), injection (strategies, recency-wins precedence, scoping), in-flight memoryWrites with its framework guard pipeline, and the Memory Manager’s hardening/consolidation/decay phases. Includes a configuration reference and troubleshooting queries. Read before touching anything under agent notes/examples or the memoryWrites capability.
  • Agent Skills & Plan Mode Guide — Two BaseAgent-framework capabilities that ship together (one migration). Skills (MJ: AI Skills) = reusable capability bundles (Instructions + bundled Actions + bundled sub-agents) an agent activates mid-run via a progressive-disclosure catalog (only name+description in the prompt until activation); resolved by AIEngineBase.GetSkillsForAgent through a three-layer gate (AIAgent.AcceptsSkills None/All/Limited × AISkill.Status × per-grant MJ: AI Agent Skills.Status); activation appends Instructions + widens the tool surface via a 'specific'-scoped ActionChange/SubAgentChange targeting the activating agent (applies at any depth, never cascades — a 'root' scope would be a bug for sub-agents). Permissions use full agent parity: a dedicated MJ: AI Skill Permissions table (User xor Role grantee × View/Run/Edit/Delete) with two access paths over one table — the cached, open-by-default AISkillPermissionHelper (@memberjunction/ai-engine-base, the runtime gate) and the closed-by-default AISkillPermissionProvider (@RegisterClass(PermissionProviderBase,'MJAISkillPermissionProvider'), the unified PermissionEngine/Sharing-Center view) — grantee-exclusivity enforced by MJAISkillPermissionEntityServer.Validate(); sharing gated by the Can Share Skills authorization (the old AI Skills Resource-Type sharing was retired). AIEngineBase.GetSkillsForAgent(agent, user?) takes an optional user to intersect the agent gate with the user’s Run permission, so the model’s skill catalog is permission-filtered. Users invoke a skill by typing /skill-name in the composer (mirrors @agent/#entity; picker filtered by the helper, chips use AISkill.IconClass/Color); selected IDs thread as ExecuteAgentParams.requestedSkillIDs (same client→resolver→runtime chain as planMode) and BaseAgent.preActivateRequestedSkills activates them at run start only if they survive the guard (agent-accepted ∩ user-permitted). Portable via SKILL.md (SkillMarkdownConverter + SkillImportExportService + the AISkill.ExportMarkdown/ImportMarkdown Remote Operations — names not IDs, unresolved bundle members become non-fatal warnings); see the Unified Permissions Guide for the two-access-path pattern. Plan Mode = a per-request HITL gate (AIAgent.SupportsPlanMode capability default-ON/opt-out × ExecuteAgentParams.planMode per-request default-OFF, root-agent-only) that blocks Actions/Sub-Agent until the agent presents a 'Plan' step and a human approves it via the existing MJ: AI Agent Requests pause/resume flow; rejection forces a re-plan because resumeAgent re-enables planMode only for Plan-step-originated resumes. Both 'Skill' and 'Plan' are non-terminal steps — deliberately NOT in the DB-CHECK-constrained AIAgentRun.FinalStep union (mirror the existing 'ClientTools' cast), but they ARE in AIAgentRunStep.StepType. v5.45 governance & observability: self-activation additionally requires the double activation gateAISkill.ActivationMode × AIAgent.SkillActivationMode, both 'Auto'/'RequestedOnly' defaulting to 'RequestedOnly' (resolved by GetAutoActivatableSkillsForAgent; the /skill requested path ignores ActivationMode but honors all availability gates); AIAgent.RequirePlanMode forces plan mode on every root run (SupportsPlanMode moot); runs stamp AIAgentRun.PlanMode; in plan mode, skill activations are legal only before approval (post-approval → Retry demanding a re-plan); every step touched by a skill records AIAgentRunStep.Skills (JSON Array<AgentSkillInvocation> — activation type, provenance-of-authority gate values, agent-stated reason from skills:[{name, reason}]; Actions/Sub-Agent steps carry skill attribution with native-grant precedence = NULL). Read before touching anything under Skills, AcceptsSkills/SupportsPlanMode/ActivationMode/RequirePlanMode, the skill-step/plan-step loop wiring, skill observability, or SKILL.md.
  • User Routines Guide — The user-owned “agents on my schedule” layer (P1.5, 5.45) — MJ: User Routines/Recipients/Runs, the single 1-minute dispatcher scheduled job (UserRoutineDispatcherDriver: claim-by-advancing-NextRunAt before running, ConcurrencyMode=Skip, bounded concurrency, per-routine isolation, runs AS the owner), pure schedule math in UserRoutineProcessor (shared with MJUserRoutineEntityServer so save-path and dispatcher agree), Scheduled vs Monitoring (OnChange via result hash), activation windows (StartAt/EndAt), Template-driven notifications (metadata-seeded default), linkage-only telemetry (AgentRunID/PromptRunID/ActionExecutionLogID — no duplicated token/cost), RequestedSkillIDs pre-arming, the non-startup UserRoutineEngine, the ng-user-routines widget set + conversations bottom-sidebar entry (gated by ShowRoutines prop AND entity-Read permission). Read before touching anything under routines, the dispatcher, or the conversations routines section.
  • Real-Time Co-Agents Guide — The live, low-latency agent stack — the Realtime agent type and Voice Co-Agent (one co-agent voices any target agent via the stable invoke-target-agent tool), the triple-registry plugin architecture (server/client realtime-model drivers + interactive-channel plugins, all ClassFactory + metadata resolved), client-direct vs server-bridged topologies, AIAgentSession lifecycle/janitor, interactive channels (the live Whiteboard), progress narration, observability, and the security model. Read before touching anything realtime / voice / agent-session / channel. Companion to plans/ai-agent-sessions.md.
  • Realtime Bridges Guide — The pluggable media-transport seam that connects the one realtime agent engine to external endpoints — Zoom/Teams/Slack/Meet/Webex/Discord meetings and Twilio/Vonage/RingCentral telephony — carrying bidirectional, media-agnostic tracks (audio/video/screen, full duplex). Covers: the AIBridgeEngineBase (@memberjunction/ai-bridge-base, metadata cache) / AIBridgeEngine (@memberjunction/ai-bridge-server, composition-not-inheritance coordination + the bridge.OnMedia → session.SendInput / session.OnOutput → bridge.SendMedia transport seam) pair; the BaseRealtimeBridge driver family (sibling to BaseRealtimeModel) and how to add a new bridge driver (subclass + @RegisterClass(BaseRealtimeBridge, '<X>Bridge') + capability-gated virtuals, LoopbackBridge as the worked example); the IBridgeProviderFeatures capability model (engine gates the flag, RequireFeature throws as defense-in-depth via BridgeCapabilityNotSupportedError); the platform-agnostic TurnTakingPolicy (passive/active/hybrid); the 5 entities + their *EntityServer invariants; and the roadmap (Phase 0/1 shipped, 2+ planned). Read before touching anything bridge / meeting / telephony / media-transport, or before adding a bridge driver.
  • Remote Browser Channel Guide — The in-house realtime channel where an agent drives a real, live browser while it talks (sales demo, support walkthrough, trainer agent — demonstrate then “your turn, you try”). Built on the principle that every backend exposes the same primitive (a CDP endpoint), so the browser work lives once, generically, in @memberjunction/computer-use (enriched additively with selector-aware actions, screencast, MouseMove, accessibility/element perception) and the Remote Browser layer just maps vocabulary + manages session lifecycle. Covers: the layer cake (computer-useremote-browser-base universal contracts + RemoteBrowserEngineBase registry → remote-browser-cdp shared CdpRemoteBrowserSession kit + lossless mapRemoteBrowserAction → 5 thin backends → remote-browser-server RemoteBrowserEngine/RemoteBrowserChannel); the AIRemoteBrowserProvider registry + IRemoteBrowserProviderFeatures capability gating (two-layer, like bridges); control modes (AgentOnly/ViewOnly/Collaborative) vs control strategies (ComputerUse default vs NativeAI/Stagehand, capability-gated); goal-driven control (§9 — set a high-level goal instead of granular clicks: browser_AchieveGoalExecuteRemoteBrowserGoalRemoteBrowserEngine.AchieveGoal → pure dispatchRemoteBrowserGoal strategy switch → RunComputerUseGoal on the session’s OWN adapter; model-blind credentials via {{label}} context injection resolved at the CDP keystroke boundary; Collaborative pause-on-takeover; vision-model auto-selection + the MJProgressComputerUseEngine startup binding); and how to add a backend (subclass BaseCdpRemoteBrowserProvider, implement AcquireSession + a 3-method ICdpSessionBackend, @RegisterClass(BaseRemoteBrowserProvider, '<X>RemoteBrowser'), seed a row). Read before touching anything under packages/AI/RemoteBrowser/ or before adding a browser backend.
  • Realtime Session Capture & Recording Guide — How a voice session is captured: per-turn transcript with start/end timing + speaker identity, and the optional audio recording. Covers the create-on-start/update-on-complete turn lifecycle, recording on both topologies (server-bridged mixer and client-direct browser capture via RealtimeAudioRecorder), the seekable 16-bit PCM WAV + capture-time peaks.json waveform sidecar, agent-audio mixing (AttachRemoteStream / OnRemoteMediaStream), and streaming playback via mj-storage-media-playerCreateMediaAccessToken → the GET /media/:fileId Range route. Read before touching persistRealtimeTranscript, the recording capture/store, or playback.
  • Conversations UX Stack Guide — The 3-layer architecture for every chat surface in MJ — @memberjunction/conversations-runtime (pure-TS engine: agent dispatch, default-agent resolution, mentions, bridge, streaming, client tools, sessions observability) ↔ adapters (INotificationAdapter / IActiveTaskTracker / ISessionsAdapter) ↔ @memberjunction/ng-conversations (Angular widget) ↔ your app. Covers: when to use each layer, the slot system (6 slots: header / agentPresence / emptyState / messageRenderer / messageExtra / demonstrationSurface with project / wrap / subclass modes), Before/After cancelable events (beforeAgentTurn, beforeToolInvoked, beforeResponseFormSubmitted with event.Cancel = true enforced; sessionStarted / sessionChannelStateChanged / sessionEnded informational), persona inputs ([showAgentCharacter] + agentCharacterConfig), --mj-chat-* design tokens, default-agent resolution chain (explicit → app-scoped → global → code-const Sage fallback), sessions adapter bridging to PR #2787’s VoiceSessionService, multi-provider scoping, runtime pre-warming via @RegisterForStartup. Read before building any chat surface (overlay, full workspace, embedded panel) OR before forking the widget — slots + events almost certainly cover the use case.
  • Predictive Studio Guide — How MJ trains predictive models on a client’s own data (member retention/renewal, lapse/lead scoring) and scores records with them — core MJ, not an OpenApp, composed onto existing substrates. Covers the 4-layer architecture (data → feature → model → inference); the self-managing Python sidecar (MLSidecar in @memberjunction/predictive-studio-sidecar — the sqlglot-ts bundled-microservice pattern: managed child-process spawn on an ephemeral port is the default, Docker-free; remote-URL mode for scaled deployments; npm run setup:python; the /train+/predict+/health contract defined once in @memberjunction/predictive-studio-core); the FeatureAssemblyExecutor correctness backbone (one code path × three contexts; the raw-vs-preprocessing fit-once/apply-everywhere anti-skew split with fitted_preprocessing travelling with the model; first-class point-in-time as-of assembly; the LeakageGuardEnforcer deny-list + post-train single-feature-dominance flag → plain-language warning + blocked promotion); training (TrainingEngine → immutable, versioned MJ: ML Models distinct from MJ: AI Models, with a locked holdout for honest metrics + full lineage); scoring (MLModelInferenceProcessor — a new 'ML Model' Record Set Processing work type registered via @RegisterClass without forking the substrate; ephemeral by default, write-back via OutputMapping; on-demand + scheduled); the generic ExperimentExperimentSessionExperimentSessionIteration primitive + the ExperimentOrchestrator wave loop (leaderboard / pruning / budget gate, run through RSP waves — reusable beyond ML); the MJ: ML Algorithms / Use Cases / Use Case Rankings 6×7 guidance matrix; the (planned) Remote Operations + Actions + Model Development Agent; the lazy-loaded Studio dashboard (PredictiveStudioDashboardComponent + PredictiveStudioEngine + 6 panels + embedded mj-conversation-chat-area copilot); a train+score walkthrough; and the live integration test (PS_INTEGRATION=1). Read before touching anything under packages/AI/PredictiveStudio/**, the MJ: ML * / MJ: Experiment* entities, the Predictive Studio dashboard, or before adding a trained-model / feature-assembly / experiment-search capability.
  • Dashboard Best Practices — Comprehensive patterns for building MJ dashboards including:
    • Architecture and naming conventions
    • State management with getter/setters
    • Engine class patterns (no Angular services for data)
    • User preferences and local caching
    • Page Chrome — the shared <mj-page-layout> + <mj-page-header> + <mj-page-body> trio that every Explorer dashboard uses, with slot rules ([meta]/[actions]/[toolbar]) and documented exceptions
    • Layout patterns, permission checking, and more
  • Explorer Chrome Conventions — The full rulebook for MJ Explorer’s shared chrome — slot rules ([meta] is state, [actions] is verbs, [toolbar] is secondary controls), filter UI decision tree, and the canonical exception list. Sub-pages of left-nav shells use <mj-page-header-interior> (Section 10) — a two-row card with [Title] + [Subtitle] inputs and the same slot conventions as <mj-page-header> — NOT their own <mj-page-header> (which would produce a doubled-header). Read before doing chrome work or deciding to deviate. (Lives in plans/, not this folder.)
  • Forms Architecture Guide — How MJ renders/edits entity records across all surfaces from one set of forms — full-page tabs, modal dialogs, and slide-in panels. Covers:
    • The 4-layer architecture (MjEntityFormHostComponent → presentation shells → MJFormPresenterService), all in @memberjunction/ng-base-forms with zero Explorer/Router coupling
    • How generated, custom (*Extended), and interactive (EntityFormOverride) forms coexist, plus the variant picker
    • EntityFormConfig — per-instance control over toolbar / related-entity sections / collapsibility / width / in-form navigation, applied without regenerating any form
    • Imperative (forms.open({...})) and declarative (<mj-form-dialog> / <mj-form-slide-in>) usage
    • Read this before building any bespoke “edit a record in a dialog/slide-in” component — the generic capability almost certainly covers it.
  • Lazy Loading Guide — How MJExplorer’s code-split lazy loading works:
    • Adding new dashboard components (zero config — just @RegisterClass + feature module)
    • Making a package lazy-loadable (add subpath exports to package.json)
    • Adding new feature modules with subpath exports
    • How the auto-generated lazy config is produced by mj codegen manifest --lazy-config
    • Troubleshooting lazy loading issues
  • Navigation and Routing Guide — How the shell owns URL state, back/forward navigation, adding URL-synced sub-navigation to a component.
  • Optimistic-UI Save Pattern — ⚠️ Status: proposal for review, not yet an adopted convention. Documents a pattern several Angular surfaces adopted independently (render the user’s action immediately, reconcile after Save() resolves) plus a proposed framework hook (EntitySaveOptions.OnValidated) that generalizes it. Includes two competing implementations — generic vs. inline — for reviewers to compare before any codebase-wide sweep. Read for context on perceived-latency work on chat/list/settings surfaces; do not treat as settled guidance until the proposal is resolved.
  • Media Player package — The generic mj-media-player (zero-MJ-dep audio/video player: transport, real waveform scrubber, playback speed, ±skip, time-synced transcript, multi-track video grid) and the MJStorage-bound mj-storage-media-player wrapper (resolves an MJ: Files id to a permission-gated, Range-streamed /media URL with server-supplied waveform peaks). Used by the artifact audio/video viewers + previews and the realtime session-review overlay.
  • Integration Testing Quickstart — The integration-test tier — real provider stack (live DB + GraphQL, real cache managers/engines), headless, sitting between unit tests (mocked) and browser/computer-use regression (“mock the top layer, keep everything else real”). Covers: the one-library-two-front-ends architecture (@memberjunction/testing-integration check bundles on one IntegrationCheckRegistry, consumed identically by the tsx suite scripts / run-all.ts aggregator AND the metadata-driven “Integration Test” TestType dispatched by IntegrationTestDriver through mj test); the dedicated-process rule (the instrumented cache MUST be LocalCacheManager’s first caller — MJ_INTEGRATION_TEST=1 on the CLI; server-transport suites refuse to run inside a live MJAPI); the two proof techniques (UniqueFilter cold-cache determinism with zero mutation + InstrumentedLocalStorageProvider per-category counters); tiers/gating (deterministic / mutation / live-model via RUN_MUTATION_TESTS / RUN_AGENT_TESTS, plus PS_INTEGRATION flows); every way to run it (npm run test:integration, per-suite tsx, mj test run/suite, the smoke test, the cross-server rig, the golden diff, the CI PR gate) and all four authoring methods (add a check to a bundle · new bundle + Test row + suite membership · metadata-only composition · standalone script). Read before running or extending the integration suites, or touching anything under packages/TestingFramework/testing-integration/.
  • Angular Testing Guide — How to test MJ Angular components. MJ runs Vitest everywhere (Jest is deprecated). Two complementary styles: class-level tests (instantiate with new, exercise pure logic — getters, navigation methods, @Output emission — fast, no DOM; these already exist and stay) and DOM-level tests (render into a headless jsdom DOM with TestBed + ComponentFixture, set @Inputs, dispatch DOM events, assert on rendered output — the half of a component’s contract that lives in the template: @if gating, bindings, (click) wiring, conditional classes, a11y attributes, which class-level tests cannot see). Includes a decision tree for which style to write, and when to reach for live Playwright/e2e instead (WebRTC and real-media paths). Rollout plan: plans/testing/angular-dom-testing-rollout.md.

The complete/ subdirectory holds guides whose subject matter has been fully implemented and absorbed into the codebase. Kept for historical context — newer guides should go in the top-level folder.


A topic earns a guide in this folder when:

  1. It spans multiple packages or touches the framework as a whole — package-internal guidance belongs in that package’s CLAUDE.md.
  2. It captures non-obvious patterns a developer would not derive by reading the code alone (gotchas, conventions, “why we do it this way”).
  3. The patterns have been validated in production — speculative designs belong in plans/, not here.

When adding a guide, also:

  • Add an entry to this README under the appropriate section. This README is the single index — root CLAUDE.md links here rather than maintaining a parallel list, so there is nowhere else to update.
  • If the guide is a hard requirement for a specific subtree, add a pointer from that subtree’s CLAUDE.md (e.g. packages/Angular/CLAUDE.md) so it loads when someone works there.