Global Mapper Specification
Version: 2.0 Last Updated: February 15, 2026
Table of Contents
Part I: Foundation
Part II: Architecture
Part III: UI/UX
- Window Lifecycle
- Duplicates Tab
- Progress Overlays
- Families Tab
- Line Styles / Object Styles / Materials Tabs
- Shared Parameters Tab
- Global Controls
- Plan & Commit Workflow
- Performance
Part IV: Features
Part V: Quality & Implementation
Part I: Foundation
1. Overview
1.1 Purpose and Scope
This specification defines the complete behavioral, architectural, and quality requirements for the Global Mapper (GM) tool. It serves as the authoritative reference for:
- Developers: Implementation guidance with detailed behavioral specifications
- QA/Testers: Acceptance criteria and validation requirements
- Product Owners: Feature completeness and user experience standards
- AI Agents: Context for code generation and refactoring decisions
1.2 Tool Overview
Global Mapper is an advanced Revit plugin that enables bulk mapping and migration of:
- Families → Families (with type, parameter, material, object style mappings)
- Line Styles → Line Styles
- Object Styles → Object Styles
- Materials → Materials
- Shared Parameters → Project parameters or embedded family parameters
The tool provides similarity-based matching, manual overrides, deep scanning for detailed family analysis, and a plan-first commit workflow.
2. Architectural Principles
These are the foundational, non-negotiable constraints that govern all GM code.
2.1 Core Invariants
Kernel-Only, Ids-Only Data Model
- All domain data is stored as Revit element IDs only (integers)
- No names or descriptive strings in core data structures
- Names are resolved only when visible in UI, never during render loops
- Data is section-gated:
families,usage(line/object styles, materials),shared_params
Fail-Fast, No Fallbacks, No Silent Failures
- No fallback logic anywhere in codebase
- No broad exception catches (typed exceptions only at narrow boundaries)
- No defensive defaults to hide incorrect state
- Fix root causes, never add wrappers or UI patches
- Surface all errors via overlays, banners, or typed exceptions
// WRONG - swallows exception
try { File.Delete(file); } catch { }
// CORRECT - logs and propagates
try { File.Delete(file); }
catch (Exception ex)
{
_logger.LogError(ex, "Failed to delete cache file: {Path}", file);
throw;
}
Section-Gated Loading
- Data is built in sections: families, usage, shared_params
- Sections load lazily on first tab visit (not all at startup)
- Never return placeholder/fallback content if section is missing
- Throw explicit error or build section exactly once
Always-Fresh Cache Architecture
- Families, types, materials, usage, shared parameters are always queried fresh from Revit
- No snapshot caching of live data
- Deep scan cache only (global, name-keyed, cross-document portable)
- UI state persistence is separate from data (tab, search, scroll, filters)
DDD Layering (Domain → Application → Infrastructure)
- Domain layer: pure entities, value objects, no Revit types
- Application layer: orchestrators, lifecycle, queries, read models
- Infrastructure layer: Revit adapters, persistence repositories
- UI layer: binds only to Application interfaces
DBTools.GM.App/Domain/*
DBTools.GM.App/Application/*
DBTools.GM.App/Infrastructure/*
DBTools.UI/ViewModels/GM/* (consumes Application only)
Roslyn analyzers prevent forbidden cross-namespace usage.
First Paint, Then Work; Single Overlay Paradigm
- Window renders immediately (first paint) without running collectors
- After paint, initial work runs via dispatcher under progress overlay with cancellation
- Never double-overlay (only overlay long work)
- Commands disabled while busy (IsBusy affects CanExecute)
2.2 Data Model Constraints
Ids-Only Persistence
- All persisted data uses element IDs (integers)
- Names/descriptors are UI-time hydration only
- Deep scan cache uses name/signature composite keys for portability
Hydration on Demand
- Resolve names only for visible rows
- Target option lists are pre-built for current category only
- Nested details remain lazy (never pre-built)
Write Policy
- UI state writes only on window close (no mid-session writes)
- Deep scan cache writes immediately on scan completion
- No resume JSON during session (always-fresh eliminates need)
2.3 Error Handling Philosophy
Typed Exceptions at Boundaries
// Domain exceptions
GmKernelError (base)
├── GmSnapshotLoadError (snapshot parse/version mismatch)
├── GmCollectionError (adapter failures during bulk reads)
├── GmInvalidMappingError (invalid/unsupported plan operations)
└── GmApplyStageError (with Stage name and inner failure aggregation)
SafeExecute Boundary Pattern
All top-level UI entrypoints execute under ISafeExecutor:
// Entrypoint wrapper
src/DBTools.Core/Execution/SafeExecutor.cs
// Used by GM view models
src/Tools/Common/GM/Shell/UI/ViewModels/GmShellViewModel*.cs
Error Surfaces
- Notification Banner — Stacked manager (up to 3 visible per anchor window), fade in/out, auto-close; overflow can be expanded temporarily
- Progress Overlay Errors — Overlay shows during long ops, surfaces errors inline
- Output Window — Live window with search, copy/clear/export, level filters, duplicate grouping
- Error Boundary (Window-Level) — UnobservedTaskException handling, banner on background failures
2.4 REV3 Reconciliation Contracts (2026-02-15)
The following contracts are authoritative for REV3 behavior and must remain in sync across VM, services, and XAML templates.
Duplicates planner + readiness contract
- Duplicates planning is source-row centric (
DuplicateGroupRow) with explicitApply,Keep Target, andPersistSelection. Applydefaults off and is session-only (never restored from persisted UI state).- Keep-target options are domain/category scoped (
GetScopedTargetIds(...)); self-target marks keeper and forcesApply=false. - Commit-active duplicate mappings are exactly
row.IsReady. - Restored-saved advisory mappings are exactly
row.IsRestoredAdvisoryand remain visible in conflicts without blocking commit. - One-time duplicate mappings (
Apply && !PersistSelection) are excluded from success-mapping persistence writes.
Conflict detection/input contract
- Duplicate conflict input is built from normalized duplicate edges derived from current row state (
IsReady+IsRestoredAdvisory). - Blocking conflicts are commit-active only; restored-saved conflicts are advisory.
- Conflict UI has split semantics:
HasConflicts= blocking only (used to gate Review Plan).HasAnyConflicts= blocking + advisory (used to surface flyout and conflict details).
Category pipeline contract
- Family category identity is queried per family (
FamilyScanner.GetFamilyCategoryIdsAsync) and stored as realFamilyRecord.CategoryId. - Duplicates family grouping reads that category ID directly.
- Category label resolution order is curated-first, then kernel category-name cache (
EnsureCategoryNamesForIdsAsyncfallback). - Shared Parameters duplicate sub-category grouping remains parameter-type-display based (not Revit family categories).
Families parameters hydration contract (Issue 19)
- Expand path is fixed to:
ToggleFamilyRow→EnsureFamilyParamNamesAsync→RebuildParameterMatchesForFamily. - Scanner discovery remains
FamilyScanner.GetMappableParameterNamesForFamilyAsyncusingRevitCompat.IsUserMappable. - Parameters tab is always rendered; when scanning is unavailable or no mappable names are detected, the tab shows a centered empty/disabled state instead of hiding.
SP duplicate authoring bridge (Issue 12)
- Shared Parameter duplicate rows are actionable in Duplicates when
Apply=truewith a valid keep target. - Accepted SP duplicate selections synchronize into Shared Parameters authoring rows by
(TypeDisplay, ToName) -> FromNameand setApply=truefor the matched authoring row when no explicit user override exists. - Plan building no longer uses the BindingMap feasibility gate for SP duplicate processing; SP work is emitted through Shared Parameters authoring operations (
BindSharedParameter/EmbedSharedParameter). - Shared parameter value copy supports compatible coercions (
string<-> numeric,int<->double) and fails fast on unsupported conversions.
Source:
src/Tools/Common/GM/Shell/ViewModels/DuplicatesModels.cs:59Source:src/Tools/Common/GM/Shell/ViewModels/DuplicatesPaneViewModel.cs:373Source:src/Tools/Common/GM/Shell/ViewModels/DuplicatesPaneViewModel.cs:1915Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.UsageAndFamilies.cs:157Source:src/Tools/Common/GM/Shell/Services/CommitReviewWindowService.cs:46Source:src/Tools/Common/GM/Shell/Services/MappingConflictService.cs:29Source:src/Tools/Common/GM/Shell/Services/IMappingConflictService.cs:15Source:src/Tools/Common/GM/Shell/ViewModels/MappingPaneViewModel.cs:44Source:src/Tools/Common/GM/Domain/ProjectStateBuilder.cs:114Source:src/Tools/Common/GM/Features/Families/FamilyScanner.cs:77Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:279Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.MappingInitAndInfrastructure.cs:850Source:src/Tools/Common/GM/Shell/ViewModels/FamiliesPaneViewModel.cs:851Source:src/Tools/Common/GM/Shell/Services/PlanBuildingService.cs:337Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.MappingInitAndInfrastructure.cs:1069Source:src/Tools/Common/GM/Features/SharedParameters/SharedParameterAuthoringService.cs:358
Part II: Architecture
3. Domain Model
3.1 GMProjectState (Root Aggregate)
The kernel is the single source of truth for all domain data during a session.
namespace DBTools.GM.App.Kernel
public sealed class GMProjectState
{
// Snapshot reference (for resume)
public GMSourceSnapshot SourceSnapshot { get; init; }
// Families by ID
public IReadOnlyDictionary<int, GMFamilyRecord> Families { get; init; }
// Usage indices (materials, line styles, object styles)
public GMUsageIndex Usage { get; init; }
// Shared parameter ledger
public GMSharedParameterLedger SharedParameters { get; init; }
// Tab UI state
public GMTabStateCollection Tabs { get; init; }
}
3.2 GMFamilyRecord
public sealed class GMFamilyRecord
{
public int ElementId { get; init; } // FamilyId
public int? CategoryId { get; init; } // Optional category
public string? Name { get; init; } // Lazy via names service; UI-only cache
public int InstanceCount { get; init; }
// Child entities
public IReadOnlyDictionary<int, GMTypeRecord> Types { get; init; } // By SymbolId
// Usage references
public IReadOnlyCollection<int> Materials { get; init; }
public IReadOnlyCollection<int> ObjectStyles { get; init; }
public IReadOnlyCollection<int> LineStyles { get; init; }
// Parameters (name → descriptor)
public IReadOnlyDictionary<string, GMParameterDescriptor> Parameters { get; init; }
// Deep scan state
public GMDeepScanState DeepScanState { get; init; }
}
Parameter Inclusion Rules:
Parameters are filtered programmatically (no curated name lists). Include parameter if ALL of these are true:
Parameter.IsReadOnly == FalseDefinition.Nameis not null/whitespace- If
DefinitionisInternalDefinition, thenBuiltInParameter == INVALID(exclude Revit built-ins) - De-duplicate by name across instance/type scopes
- Annotate scope as
Instance,Type, orInstance+Type
3.3 GMTypeRecord
public sealed class GMTypeRecord
{
public int SymbolId { get; init; }
public int FamilyId { get; init; }
// Usage for this type
public GMTypeUsage Usage { get; init; } // Materials/styles referenced
// Sampled parameter values (for SP mapping UI)
public IReadOnlyDictionary<string, string?> ParameterValues { get; init; }
// Deep scan payload
public GMTypeDeepScan DeepScanPayload { get; init; }
}
3.4 GMUsageIndex
public sealed class GMUsageIndex
{
public IReadOnlyDictionary<int, GMUsageRecord> LineStyles { get; init; }
public IReadOnlyDictionary<int, GMUsageRecord> ObjectStyles { get; init; }
public IReadOnlyDictionary<int, GMUsageRecord> Materials { get; init; }
}
public sealed class GMUsageRecord
{
public int ElementId { get; init; } // Style or material ID
public IReadOnlyCollection<int> Families { get; init; } // Family IDs referencing this element
}
Canonical Uniqueness:
- One row per style/object style/material from usage section
- No duplicates (duplicates indicate hydration or binding bugs)
- Built once per session, cached in kernel
3.5 GMDeepScanState
public sealed class GMDeepScanState
{
public bool IsDeepScanned { get; init; }
public IReadOnlyList<int> SampleInstanceIds { get; init; }
public IReadOnlyList<string> ParameterNames { get; init; }
public IReadOnlyCollection<int> StyleRefs { get; init; }
public IReadOnlyCollection<int> MaterialRefs { get; init; }
}
Deep Scan Augmentation:
- Families discovered only via deep scan appear in usage lists with DS badge
- Usage count remains 0 if no instances in project
- DS badge distinguishes deep-scan-only from in-project usage
3.6 Section Gating Rules
Families Section:
- Built on initial load for default curated category
- Rebuilt on category change
- Never contains all categories at once (lazy per category)
Usage Section:
- Built on first visit to Line Styles, Object Styles, or Materials tab (or when plan building requires usage for material/style operations)
- Split into line vs object styles using
IStyleService - Cached for session (reused across tabs)
- Stored into kernel
Usage(SSoT) so planning can computeFamiliesAffectedfor style/material-only plans
Source:
src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:2106
Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.MappingInitAndInfrastructure.cs:318
Source:src/Tools/Common/GM/Shell/Services/KernelStateService.cs:314
Source:src/Tools/Common/GM/Domain/Planning/PlanningService.cs:300
Shared Parameters Section:
- Built on first visit to SP tab
- Curated groups only (hard-coded list)
- Cached for session
Lazy Loading Policy:
- Tabs other than active: lazily loaded on first visit (overlay if needed)
- Families and SP: reload on category change (overlay if needed)
- Expanded row details: always lazy (on "+" and/or after "Scan Family")
3.7 Element Uniqueness Guarantees
Families:
- One
GMFamilyRecordper uniqueFamilyIdin selected category - Types: One
GMTypeRecordperSymbolIdunder each family
Usage:
- One
GMUsageRecordper unique line style / object style / material ID - No duplicates across tabs (line styles ≠ object styles ≠ materials)
Shared Parameters:
- One group per SharedParameterFile group
- One definition per SP within group
Violation Handling:
- Duplicates indicate collector or hydration bug
- Must throw
GmCollectionErrorwith details - Never silently dedupe or use fallback
4. Cache Architecture
4.1 Always-Fresh Data Model
Live Data (Always Fresh from Revit):
- Families (for current category)
- Types (for loaded families)
- Materials (project + linked)
- Line Styles (project + linked)
- Object Styles (project + linked)
- Shared Parameters (from SharedParameterFile)
Querying:
- Every tab load queries Revit API fresh
- Session in-memory cache for instant tab switching within session
- No staleness verification needed (data is current by definition)
4.2 Deep Scan Cache (Global, Name-Keyed)
Persistence:
- File:
deep-scan-cache.jsonin the DBTools cache directory (global, NOT document-specific) - Format: JSON (portable payload; no host element IDs persisted)
Composite Key:
public sealed class DeepScanCacheKey
{
public string FamilyName { get; init; }
public int TypeCount { get; init; }
public string ParameterSignature { get; init; } // Hash of fast-scanned mappable parameter base-names
}
Payload:
public sealed class DeepScanCache
{
public DeepScanCacheKey Key { get; init; }
public IReadOnlyList<string> ParameterNames { get; init; }
public IReadOnlyList<GraphicsStyleKey> StyleKeys { get; init; } // portable style identifiers
public IReadOnlyList<string> MaterialNames { get; init; } // portable material identifiers
public IReadOnlyList<string> NestedFamilyNames { get; init; } // portable nesting identifiers
public DateTime CachedAt { get; init; }
}
Write Semantics:
- Deep scan completion writes immediately to
deep-scan-cache.json - Enables cross-session and cross-document reuse
Restore Semantics (Strict):
- Cache restore is all-or-nothing:
- Cache-key mismatch → cache miss
- Any cached style/material/nested-family that cannot be resolved cleanly to exactly one host element ID → cache miss
- No partial restore and no silent stale-data usage.
4.3 Cache Miss Policy (No Staleness Badge)
Mismatch Scenarios (treated as cache miss):
- Family renamed in external project
- Types added/removed (TypeCount mismatch)
- Parameters added/removed (ParameterSignature mismatch)
- Cached portable usage identifiers no longer exist or are ambiguous in the current document
UI Behavior:
- No separate staleness badge; instead, mismatches force a rescan when the user triggers deep scan.
- Corrupt/invalid cache entries are dropped during load, and a one-time "Deep Scan Cache Notice" alert is shown.
4.4 UI State Cache (Document-Scoped)
Persistence:
- File:
.gmstate_{documentKey}.jsonper document - Format: JSON with UI preferences
Content:
public sealed class GmUiState
{
public string DocumentKey { get; init; }
public int Version { get; init; } = 1; // Strict version checking
// Tab state
public int SelectedTabIndex { get; init; }
public int? SelectedCategoryId { get; init; }
// Filters
public bool FilterUnused { get; init; }
public bool FilterApplied { get; init; }
public bool FilterUnscanned { get; init; }
public string? SearchText { get; init; }
public int SimilarityThreshold { get; init; }
public string SortMode { get; init; } // "Similarity" | "Alphabetical"
// Scroll positions
public double? FamiliesScrollOffset { get; init; }
public double? StylesScrollOffset { get; init; }
public double? MaterialsScrollOffset { get; init; }
public double? SPTypesScrollOffset { get; init; }
public double? SPParamsScrollOffset { get; init; }
// Expanded state
public IReadOnlyList<int> ExpandedFamilies { get; init; }
public int? SelectedSPTypeSymbolId { get; init; }
// SP selections
public IReadOnlyList<SPSelection> SPSelections { get; init; }
}
public sealed class SPSelection
{
public int SymbolId { get; init; }
public IReadOnlyList<SPParamSelection> Params { get; init; }
}
public sealed class SPParamSelection
{
public string Name { get; init; }
public bool Apply { get; init; }
public bool IsHostLevel { get; init; }
public bool? IsTypeBinding { get; init; }
public string? MapFromName { get; init; }
}
Write Policy:
- Only on window close (no mid-session writes)
- Exception: User-initiated "Save Now" if added (explicit, not automatic)
Version Checking:
CurrentVersion = 1(increment on schema change)- On load, if version mismatch → throw
GmSnapshotLoadException - UI proceeds with fresh state (no fallback to old schema)
5. Error Handling
5.1 No Fallbacks Policy
Absolute Prohibitions:
No fallback logic anywhere in codebase
// WRONG var names = _kernel?.FamilyNames ?? new Dictionary<int, string>(); // CORRECT if (_kernel == null) throw new InvalidOperationException("Kernel must be initialized before building plan context."); var names = _kernel.FamilyNames;No broad catches — typed exceptions only
// WRONG try { ... } catch (Exception) { /* silent */ } // CORRECT try { ... } catch (GmCollectionError ex) { _logger.LogError(ex, "Failed to collect families for category {CategoryId}", categoryId); throw; }No silent failures — user must see what went wrong
// WRONG try { File.Delete(file); } catch { } // CORRECT try { File.Delete(file); } catch (Exception ex) { _logger.LogError(ex, "Failed to delete cache file: {Path}", file); throw; }No defensive defaults — fix root causes
No wrapper shortcuts — address problems at source
5.2 Typed Exceptions
Exception Types (standalone, no inheritance required):
Each exception type is a standalone sealed class with an error code for programmatic handling.
See KernelExceptions.cs for complete implementation.
namespace DBTools.GM.Kernel;
// Error codes for kernel exceptions (1xxx=Build, 2xxx=Mapping, 3xxx=Invariant, 4xxx=Snapshot, 5xxx=Collection/Apply)
public enum GmKernelErrorCode { ... }
// Kernel build failures (name resolution, shared params, etc.)
public sealed class GmKernelBuildException : Exception
{
public GmKernelErrorCode Code { get; }
}
// Invalid mapping operations
public sealed class GmInvalidMappingException : Exception
{
public GmKernelErrorCode Code { get; }
}
// Snapshot load failures
public sealed class GmSnapshotLoadException : Exception
{
public GmKernelErrorCode Code { get; }
}
// Invariant violations (invalid IDs, negative counts)
public sealed class GmInvariantViolationException : Exception
{
public GmKernelErrorCode Code { get; }
}
// Adapter/collection failures when retrieving data from Revit
public sealed class GmCollectionError : Exception
{
public GmKernelErrorCode Code { get; }
public string CollectorName { get; }
}
// Apply stage failures with aggregated inner exceptions
public sealed class GmApplyStageError : Exception
{
public GmKernelErrorCode Code { get; }
public string StageName { get; }
public IReadOnlyList<Exception> InnerFailures { get; }
}
5.3 SafeExecute Integration
Pattern:
// All top-level UI entrypoints execute under ISafeExecutor
await _runner.RunAsync("GM Apply - Full Plan", async () =>
{
// ... application logic that may throw typed exceptions
});
Behavior:
- Catches all exceptions from inner lambda
- Logs to centralized logger
- Shows banner notification to user
- Never masks or silently swallows
5.4 Overlay Error Surfacing
When Used:
- Initial tab load after paint
- Category change (Families, SP)
- Deep scan
- Refresh/rebuild operations
Behavior:
- Shows titled steps with ticks
- Throttled progress updates
- Cancel button sets
cancel_requested(collectors honor it) - On error: show error message in overlay, do NOT close window
Never:
- Double-overlay (check
IsBusybefore showing) - Close window on error (surface via overlay, let user decide)
Part III: UI/UX
6. Window Lifecycle
6.1 Entry and First Paint
Behavior:
- Entry: Global Mapper button clicked → window opens (modal)
- First Paint: Show window immediately WITHOUT running collectors
- Loaded Event:
- Restore UI state from
.gmstatefile (tab, search, sort, threshold, filters, scroll offsets) - Use dispatcher to start initial loads AFTER paint completes
- Restore UI state from
Critical: Window renders before any data collection begins.
6.2 Initial Load Sequence
After First Paint:
Dispatcher-posted initial work under progress overlay:
"Loading Families..." "Building usage indices..." "Resolving names..."Default tab (or restored tab from UI state) loads first
Other tabs lazy-loaded on first visit
_initial_load_completeflag set when default tab readyBottom status line updates continuously with:
Global: section readiness (Families/Usage/Shared Parameters)Active: tab-local state (loading/visible/apply-selected context)
Gating:
SelectionChangedevents ignored until_initial_load_complete = true- Commands disabled while
IsBusy = true
6.3 Tab Lazy Loading
Policy:
- Families Tab: Load on first visit (or immediately if default tab)
- Line Styles / Object Styles / Materials: Build usage section on first visit, then cache
- Shared Parameters: Build SP ledger on first visit, then cache
Category Change:
- Families tab: Reload families for new category (show overlay)
- SP tab: Reload family/type tree for new category (show overlay)
Never:
- Reload tabs on revisit (data cached in session)
- Show overlay on tab switch if data already cached
- Show static "No data" placeholders during first-load transitions (use explicit loading copy first)
6.4 Close Behavior
On Window Close:
- Deep scan cache: Already written mid-session (global
deep-scan-cache.jsonfile) - UI state: Write
.gmstate_{documentKey}.jsonwith all UI preferences - Session cache: Cleared (in-memory only, not persisted)
No mid-session writes for UI state.
7. Duplicates Tab
The Duplicates tab is the FIRST tab in the Global Mapper window.
7.1 Purpose
Identifies potential duplicate families, styles, materials, and shared parameters in the project using similarity algorithms. Users can accept suggested consolidations which become mappings applied during the Apply workflow.
7.2 Detection Service
Trigger:
- Runs on tab activation or manual "Run Scan" button
- Detection result is cached per session
- Threshold/sort/search rebuild visible rows from cache (no re-detection)
Categories Analyzed:
- Families — Name similarity + parameter overlap
- Line Styles — Weight, color, pattern comparison
- Object Styles — Weight, color, pattern comparison
- Materials — ARGB, transparency, name comparison
- Shared Parameters — Type display + normalized name equality buckets (authoring-bridge flow)
Output:
- Source-centric rows with a single explicit keep-target selection per source
- Candidate details with scores (0-100) and rationale text
7.3 Similarity Algorithms
Naming Similarity (INamingSimilarityService):
double Similarity(string a, string b); // Returns 0.0-1.0
string Normalize(string name); // Case-insensitive, trim whitespace
bool TryGetNumericSuffixPair(string a, string b, out int aSuffix, out int bSuffix);
Algorithm:
- Numeric suffix priority: "Type 1" vs "Type 2" → high similarity (common duplicate pattern)
- Dice coefficient (bigram) fallback: General string similarity for non-numeric names
- Normalization: Case-insensitive, whitespace-trimmed comparison
Parameter Similarity (IParameterSimilarityService):
int ComputeScore(IReadOnlyDictionary<string, string> left, IReadOnlyDictionary<string, string> right);
Algorithm:
- Jaccard index for parameter key overlap
- Ignores Revit built-in parameters
- Used for Families category scoring
Visual Property Similarity (Styles/Materials):
- Line Styles: Line weight, color RGB, line pattern
- Object Styles: Same as Line Styles
- Materials: ARGB values, transparency percentage, name similarity
7.4 UI Structure
Summary Header (data-upfront):
- Total, accepted, unresolved counts
- By-kind counts (Families, Styles, Materials, Shared Parameters)
- Top offenders (highest suggestion density)
- Recommended next action text
- Actions:
Run Scan,Accept Visible
Main Grid (DuplicateGroupRow, source-centric):
| Column | Description |
|---|---|
| Kind | Category (Families, Line Styles, etc.) |
| Source | Source element name |
| Suggestions | Count of candidate keep targets above threshold |
| Highest Score | Best match percentage |
| Keep Target | Single target to keep for this source |
| Status | Needs review, Ready, Accepted, Ignored, Informational |
| Actions | Accept source, clear keep selection |
Details Pane (DuplicatePairRow):
| Column | Description |
|---|---|
| Keep | Indicator for currently selected keep target |
| From | Source display name |
| Candidate | Candidate keep target display |
| Score | Similarity percentage (0-100) |
| Rationale | Explanation of match reason |
| Actions | Set candidate as keep target |
7.5 Commands
Run Scan:
- Triggers detection for all categories
- Shows overlay: "Detecting duplicates..."
- Populates DuplicateGroups collection
Set Keep Target:
- Updates single keep target on the source row
- Syncs candidate
Keepindicator in details pane
Accept Source:
- Accepts selected keep target for one source row
- Applies row locks to affected rows on other tabs
- Updates duplicate selection persistence (
_duplicateSelections)
Accept Visible:
- Batch accept for all currently visible source rows with keep selection
Ignore Candidate:
- Marks candidate ignored for that source and updates persisted duplicate selection
7.6 Row Locking
When a duplicate mapping is accepted:
- Source row on target tabs receives
MappingOrigin = Duplicate - Origin description/lock badge explains duplicate source and target
- User can unlock/override from the row lock interaction or by changing Duplicates selection
Lock Sources:
- Families tab selections (existing)
- Duplicates tab accepted mappings (this feature)
Locking is ROW-level only. There is no tab-level locking.
7.7 Persistence
Resume Snapshot Includes:
public IDictionary<string, int[]>? DuplicateGroupMembers { get; set; } // key: kind:sourceId
public List<DuplicateSelection>? DuplicateSelections { get; set; }
public List<SharedParameterDuplicateSelection>? SharedParameterDuplicateSelections { get; set; }
public Dictionary<string, double> ScrollOffsets { get; set; } // includes "Duplicates"
Write Policy:
- Persisted on window close only (single JSON)
- No mid-session writes
Restore Behavior:
- Rehydrate duplicate group membership, duplicate selections, and shared-parameter duplicate selections
- Invalid group entries dropped with warning log
- No silent fallbacks
8. Progress Overlays
8.1 When Shown
Always Show Overlay:
- Initial tab load after first paint
- Category change (Families, SP)
- Deep scan
- Apply plan execution
- Any "long" collector (>500ms expected)
Never Show Overlay:
- Tab switch if data already cached
- Name resolution for visible rows (fast lookup)
- Search/filter/sort (instant, no API calls)
8.2 Behavior
Display:
- Titled steps with tick marks
- Progress text: "Loading Families..." → "Resolving names..." → "Complete"
- Throttled updates (avoid UI spam)
Cancellation:
- Cancel button sets
cancel_requestedflag - Collectors honor flag and exit gracefully
- Partial results discarded (fail-fast, no half-loaded state)
Error Handling:
- On error: show error message in overlay (do NOT close window)
- "Retry" button if operation is idempotent
- "Close" button dismisses overlay and returns to main UI
8.3 Never Double-Overlay
Rule: Only ONE overlay at a time
Enforcement:
- Check
IsBusyflag before showing overlay - If already busy, queue operation or reject with message
- Commands disabled via
CanExecutewhileIsBusy = true
8.4 Throttled Progress Updates
Pattern:
// Update progress every N items or 200ms, whichever comes first
var progress = new Progress<string>(msg => BusyText = msg);
foreach (var chunk in items.Chunk(100))
{
// Process chunk
((IProgress<string>)progress).Report($"Processing {processedCount}/{totalCount}...");
}
Rationale: Avoid overwhelming UI thread with rapid updates.
9. Families Tab
9.1 Category Dropdown
Data Source:
- Hard-coded curated category list (single source of truth)
- Exactly as currently implemented (no dynamic expansion)
- Mapped to actual Revit CategoryId via
Category.GetCategoryorSettings.Categories
Implementation:
src/Tools/Common/GM/Features/Usage/Revit/Services/CategoryService.cs
Default Selection:
- Visual default: "Annotation Symbols" (first curated label)
- Never blank when rows are loaded
9.2 Loading Behaviors
Initial Load:
- After first paint, load families for default curated category ("Annotation Symbols")
- Show overlay with steps:
"Collecting families..." "Counting instances..." "Resolving names..." - Results appear with dropdown visually selected
Category Change:
- User selects different category from dropdown
- Show overlay (lazy load is acceptable)
- Reload families for selected category only
- Pre-build target option lists for current category
9.3 Row Structure
Columns:
- Expand (
+icon) — Shows details (Types, Object Styles, Materials, Parameters) - Quantity —
instance_countfor family ID (0 if no instances) - Apply — Checkbox (enables this family for mapping plan)
- Saved — Badge (if mapping preference saved for this family)
- Deep Scan Badge — single
DSbadge when family has been deep scanned (partial-scan warnings are shown in tooltip text, not a second badge variant) - Source — Family name (resolved from kernel name cache)
- Target — Combobox with scored options (identity excluded)
- Search/Sort/Filters — Applied across all rows
9.4 Target Combobox Behaviors
Options Source:
- Built from peer families in same category (identity excluded)
- Pre-built at tab load for current category (cached for session)
- Switching into combobox should be instant (no delay)
Similarity Mode:
- Each option shows colored label (green → orange → red)
- Similarity percentage appended:
"Target Family Name (85%)" - Sorted high → low by score
- Threshold influences default selection only (never override manual selection)
Alphabetical Mode:
- Options sorted alphabetically
- No similarity colors/scores (default styling)
- No threshold filtering
Manual Selection Preservation:
- Manual selection is sticky across re-sorts and threshold changes
- Only reset if target disappears from options (e.g., family deleted)
9.5 Unscanned Family Demotion
Unscanned families appear beneath horizontal separator in target combobox:
- Greyed out and disabled until scanned
- Above separator: Ready options (selectable, colored in similarity mode)
- Below separator: Unscanned options (disabled, requires deep scan)
After Scan:
- Move to above separator
- Enable selection
- Apply normal scoring/coloring
9.6 Expand Details
With Instances (quantity > 0):
Shows:
- Types (from
symbol_idsin kernel) - Object Styles (usage-first; fast hydration by walking a single placed instance)
- Materials (usage-first; fast hydration by walking a single placed instance)
- Parameters (name mapping rows + example-value previews; deep-scan data is used when available, but deep scan is never auto-triggered)
Expansion flow is hybrid:
- First pass ensures cached usage indices + fast parameter discovery.
- If
quantity > 0, GM augments expanded-row Object Styles/Materials by walking a single placed instance (including view-owned dependents like filled regions and group members) and updating the reverse indices used by the Families tab row-details. - Deep scan is user-initiated only (never automatic during expansion). If deep scan data already exists, it may still be used as augmentation, but expansion does not trigger scanning.
Expanded-row tab rendering:
- Types and Parameters tabs are always rendered.
- Object Styles and Materials tabs are always rendered, but disabled (muted/italic header) when their nested grids would be empty.
- Parameters tab supports three states:
- Scanner unavailable → disabled + message
- No mappable names detected → empty-state message
- Names present → grid enabled
Zero-Qty Gating (quantity = 0):
Shows:
- "Scan Family" button
- Hint text: "This family has no instances. Deep scan required to view details."
- Details panel disabled until scan completes
After scan:
- Details unlock
- Types, Object Styles, Materials, Parameters appear
- DS badge added to top-level row
Source:
src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:2328
Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:2828
Source:src/Tools/Common/GM/Features/Usage/UsageIndexService.cs:148
9.7 Nested Target Comboboxes
Child grids within expanded family rows have target comboboxes:
Types:
- Options from target family's symbols
- Same sort/threshold semantics as top-level
Object Styles:
- Options from full universe of object styles
- Identity excluded
Materials:
- Options from full universe of materials
- Identity excluded
Parameters:
- Options from the target family's mappable parameter names (fast scanner when available; deep scan can augment when already present).
- Sentinel options:
- Blank (no mapping selected)
<Do Not Map>(explicitly opt out of mapping a row)
- Parameter rows are scope-explicit:
- Source parameter name rows are normalized into separate
(Instance)and(Type)rows (no combined(Instance+Type)row). - Instance-scope sources only offer instance-compatible targets; type-scope sources only offer type-compatible targets.
- Source parameter name rows are normalized into separate
Parameter conflict kinds (blocking):
- ParamDuplicateTarget — More than one applied parameter row in a family maps to the same target parameter.
- ParamTypeWriteAffectsExistingTargetInstances — Any applied
(Type)parameter mapping requires a policy decision when the selected target type already has instances.
Type-write policy flow (for (Type) parameter mappings):
- If the target type has no instances, type-scope parameter copies are allowed without a policy.
- If the target type already has instances, GM requires a per-family policy before commit:
KeepTargetValues→ skip type-scope writesOverwriteTargetValues→ overwrite target type values from the source
- Policy is stored per family and persists as a saved-mapping entry (
kind=param-type-policy).
Execution note (important):
- Parameter migration operations (
OperationKind.MapParameter) are executed during type replacements, adjacent to theReplaceTypework (not as a separate pre-pass). - Type-scope writes execute once per type replacement pair; instance-scope writes execute per affected instance.
- Operation results are still recorded under the
Parametersstage for reporting.
Lazy Loading:
- Do NOT pre-build nested options (only top-level pre-built)
- Build on expand or target change
Display/Edit Parity:
- Nested target columns use the same display/edit pattern as top-level targets:
- Read-only text rendering in normal grid state
SimilarityComboBoxwhen editing
- Similarity mode shows colored labels with percentage suffixes; alphabetical mode shows plain labels
Parent Target Change Behavior:
- When a parent family target changes while the row is expanded, nested target rows (Types/Parameters/Object Styles/Materials) repopulate automatically
- Rows manually edited by the user remain unchanged
- Saved mappings and externally-originated/locked selections remain unchanged
Injected/restored options (DS badge):
- When a saved mapping restores a target option that is not in the current option universe, GM injects that option into the list and marks it
DSwith a tooltip (visible in the ComboBox item template).
Source:
src/Tools/Common/GM/Domain/Conflict.cs:87
Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:1914
Source:src/Tools/Common/GM/Shell/ViewModels/MatchingModels.cs:88
Source:src/Tools/Common/GM/Shell/Views/GmShellWindow.xaml:51
Source:src/Tools/Common/GM/Features/Mapping/Writers/TypeChangeService.cs:451
9.8 Parameter Pruning Rules
Include parameter if ALL of these are true:
Parameter.IsReadOnly == FalseDefinition.Nameis not null/whitespace- If
DefinitionisInternalDefinition, thenBuiltInParameter == INVALID(exclude Revit built-ins)
De-duplication:
- De-duplicate by base name, but emit separate rows for each scope present:
Name (Instance)Name (Type)
No curated name lists (programmatic filters only).
Source:
src/Tools/Common/GM/Features/ParameterNameDiscoveryPolicy.cs:19
Source:src/Tools/Common/GM/Features/Families/FamilyScanner.cs:252
Source:src/Tools/Common/GM/Features/DeepScan/DeepScanServiceAdapter.cs:122Source:src/Tools/Common/GM/Shell/Services/ParameterNameHelper.cs:32
Source:src/Tools/Common/GM/Shell/ViewModels/FamiliesPaneViewModel.cs:781
Source:src/Tools/Common/GM/Shell/Views/GmShellWindow.xaml:1859
Source:src/Tools/Common/GM/Shell/Views/GmShellWindow.xaml:2012
9.9 Cross-Tab Apply Lock
When Families tab sets Apply on Object Style or Material mapping:
- Corresponding top-level row on Object Styles or Materials tab reflects this
- Row is locked (Apply checkbox disabled with "F" badge)
- Un-apply ONLY from Families tab (not from locked tab)
Visual Indicator:
- Blue "F" badge next to Apply checkbox
- Tooltip: "Locked by Families tab mapping"
9.10 Preview Pane Toggle
Within expanded family details, when selecting:
- Object Style row → Show/hide preview pane (swatch + style line)
- Material row → Show/hide preview pane (color swatch + properties)
Behavior:
- Preview appears on right side of window
- Toggle via checkbox or auto-show on selection
9.11 Instance Navigation
Families and Shared Parameters both support instance navigation.
- Families tab:
- Expanded row
Typespanel provides prev/next instance cycling, Auto-zoom, and Zoom - Cycling refreshes parameter example values in the expanded
Paramsgrid - Zoom and Auto-zoom run with cross-view search and immediately activate/show the sampled instance
- Expanded row
- Shared Parameters tab:
- Type details provide the same prev/next + zoom navigation pattern for sampled instances (cross-view search enabled)
Source:
src/Tools/Common/GM/Shell/ViewModels/FamiliesPaneViewModel.cs:305
Source:src/Tools/Common/GM/Shell/ViewModels/FamiliesPaneViewModel.cs:405
Source:src/Tools/Common/GM/Shell/ViewModels/SharedParametersPaneViewModel.cs:467
9.12 Quantity vs Views Containing Pane
Quantity Column:
- Reflects
instance_countfrom kernel - 0 means no instances in project
Views Containing Selected Family (Right Pane):
- Populated ONLY when
quantity > 0 - Shows all views containing selected family instances (EXCLUDES templates)
- Double-click view → navigates and zooms to instance
Consistency Rule:
- If quantity = 0 → pane should be EMPTY
- If quantity > 0 but pane empty → bug (count miscalculation)
10. Line Styles / Object Styles / Materials Tabs
10.1 Row Sources
Data:
- One row per style/object style/material from usage section of kernel
- Canonical uniqueness: NO duplicates (violation = hydration bug)
- Object-style source/target IDs may be either GraphicsStyle IDs or Category IDs; commit-time replacement resolves both forms to a canonical category/style reference.
- Plan building normalizes selected style IDs to projection
GraphicsStyleIDs before operations are emitted, preventing mixed category/style ID variants from producing unsupported style operations. - Object Styles tab scope is Detail Items subcategories only (non-line). Non-detail object styles are treated as out-of-scope and not eligible for planning.
- Usage hydration enforces preview-universe filtering per tab (materials, line styles, object styles) so out-of-scope keys are dropped before rows are built.
- Style scope resolution and normalization are shared between planning and style writer execution to keep accepted-pair classification identical at commit time.
- Style apply-time scope resolution must execute with a gate-acquired active document (no ambient
RevitAppContextdependency inApplyAsyncresolution). - When scope fallback is used (usage-backed line/object acceptance), logs must include fallback reason and source/target resolver reasons.
- Material mappings are pre-validated against host-document material existence before planning emits operations.
- Family reload calls (
LoadFamily) for style/material/nesting/shared-parameter flows must execute viaIRevitCallGatewith no host-document transaction open (hostDoc.IsModifiable == false).
Source:
src/Tools/Common/GM/Features/Mapping/Writers/StyleChangeService.cs:565Source:src/Tools/Common/GM/Shell/ViewModels/UsagePaneViewModel.cs:298Source:src/Tools/Common/GM/Features/Previews/StyleService.cs:83Source:src/Tools/Common/GM/Features/Usage/UsageIndexService.cs:184Source:src/Tools/Common/GM/Features/Mapping/StyleScopeResolver.cs:1Source:src/Tools/Common/GM/Shell/Services/PlanBuildingService.cs:142Source:src/Tools/Common/GM/Features/Mapping/Writers/StyleChangeService.cs:49
Quantity:
- Line Styles: Usage count (number of families using this style)
- Object Styles: Families count (number of families using this object style)
- Materials: Families count (number of families using this material)
Zero Quantities:
- Visible (not filtered out)
- Deep-scan-only families may contribute to usage with 0-qty (DS badge shown)
10.2 Target Options
Options Source:
- Peer rows in current tab (identity excluded)
- Same universe as source rows
Similarity Mode:
- Colored labels (green → orange → red)
- Similarity percentages:
"Target Name (92%)" - Sorted high → low
Alphabetical Mode:
- Default styling (no colors)
- No percentages
- Sorted lexicographically
Threshold:
- Influences default selection only
- Never overrides manual selection
- Materials/Styles rows are NOT filtered by threshold (only option selection affected)
10.3 Validation (No Duplicates)
Enforcement:
- Collector source must provide unique IDs
- Hydration/binding must not duplicate rows
Violation Handling:
- If duplicates detected → throw
GmCollectionError - Never silently dedupe
10.4 Sorting Modes
Similarity:
- Rows sorted by highest similarity to target (if target selected)
- Or by usage count descending
Alphabetical:
- Rows sorted by source name A-Z
- Threshold ignored
Threshold Influence:
- Threshold slider affects option list default selection only
- Row visibility unaffected (all rows shown regardless of threshold)
10.5 Preview and Usage Lists
Selection Behavior:
Line Styles:
- Select row → preview updates (swatch and style line rendering)
- No "Families Using This ..." pane (line styles are host-document-only)
Object Styles:
- Select row → preview updates (swatch and style line rendering)
- "Families Using This Object Style" pane populates with family names
- Double-click family → navigate to view and show instance
- Checking Apply on the style row links Apply on affected family rows
- Unchecking Apply removes that link; family Apply remains only if still linked elsewhere or manually selected
- Scope is Detail Items object styles only; unsupported style pairs are dropped during plan build (warning banner) before commit
- During family apply, if target subcategory is missing in the family document, GM creates it under the resolved parent, syncs core appearance (color/weight/pattern), then rewrites references and reloads the family
Materials:
- Select row → preview updates (color swatch, texture, properties)
- "Families Using This Material" list populates
- Double-click family → navigate to view and show instance
- Checking Apply on the material row links Apply on affected family rows
- Unchecking Apply removes that link; family Apply remains only if still linked elsewhere or manually selected
- During family apply, if target material is missing in a family document, GM creates it and syncs core material properties before rewriting source references and reloading the family
- "Families Using This Material" apply indicators are driven by canonical usage-link state (
OS/MAT) and remain accurate even when a family is not currently visible in the Families tab list/filter scope - Unsupported material pairs (source/target not resolvable in the host document) are dropped during plan build (warning banner) before commit
Families Tab Link Indicators:
- Linked family rows display source badges:
OS,MAT, orOS+MAT - Badge reflects active usage-link sources, independent from manual family Apply toggles
- Expanded family nested Object Styles/Materials grids show per-row link badges (
LKwithOSorMAT) for the exact source rows currently driving the family Apply linkage - Usage-linked Apply is treated as impact signaling for family-owned mappings:
- Type replacements, parameter maps, and create-type operations are only planned when the family row is explicitly user-owned (manual Apply, user-edited, saved-origin, or external-origin), not from usage auto-link alone.
Source:
src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:908
Source:src/Tools/Common/GM/Shell/ViewModels/MatchingModels.cs:190
Source:src/Tools/Common/GM/Shell/Views/GmShellWindow.xaml:1615
Source:src/Tools/Common/GM/Shell/Views/GmShellWindow.xaml:2123
Source:src/Tools/Common/GM/Shell/Views/GmShellWindow.xaml:2407
Source:src/Tools/Common/GM/Shell/Services/PlanBuildingService.cs:231
Source:src/Tools/Common/GM/Shell/Services/PlanBuildingService.cs:86
10.6 DS Badges
When Shown:
- Row has any DS-scanned families in usage list
- Badge: "DS" icon or text
- Tooltip: "Includes deep-scanned families"
Deep Scan Augmentation:
- Families discovered ONLY via deep scan appear in "Families Using This ..." lists (Object Styles, Materials)
- DS badge distinguishes them
- Usage count remains accurate (0 if no instances)
11. Shared Parameters Tab
11.1 Layout
Left Panel:
- Groups from SharedParameterFile
- Definitions within selected group
- Browse-only (no mutations)
Right Panel (Main Grid):
- Families → Types → Parameters tree for selected curated category
- Family rows expand/collapse via
+command routing on the SharedParameters pane viewmodel - Single-selection semantics (only one type expanded at a time)
11.2 Group and Category Selectors
Group Dropdown:
- Populated from SharedParameterFile (or pre-collected snapshot)
- Default: First group alphabetically
- Never blank
Category Selector (Tree, Level 0):
- Hard-coded curated category labels (Walls, Doors, Mechanical Equipment, Windows)
- Always visible (even when count is 0)
- Label includes count like
Mechanical Equipment (12); 0-count categories are dimmed - Category content is loaded for the currently selected category (tree children are populated for selected only)
Loading:
- First visit to the tab lazily loads SP groups and prewarms authoring snapshots for all curated categories
- Ensures category counts are correct immediately
- Makes switching categories instant (cache hit)
- After initial lazy-load completes, category/group changes rebuild authoring without invoking the progress overlay
Source:
src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.MappingInitAndInfrastructure.cs:755
Source:src/Tools/Common/GM/Shell/ViewModels/SharedParametersPaneViewModel.cs:487
Source:src/Tools/Common/GM/Shell/ViewModels/SharedParametersPaneViewModel.cs:1672
11.3 Main Grid Scoping
Data Sources:
shared_paramssection: powers left panel (groups/definitions)familiessection: powers family/type tree (right grid) for selected category
Scoping:
- Grid shows ONLY families in selected curated category
- Each row: Family → Types → Parameters
Empty Grid:
- If no families in curated category → show clear banner
- "No eligible families in selected category"
- Keep categories curated (do NOT expand dynamically)
11.4 Type Details Expansion
Header (top row, right of SP Group dropdown):
- Instance Navigator: Left/right arrows (cycles instances)
- Instance Count: "3/27"
- Auto-zoom: Toggle for cycling behavior
- Zoom: Navigates and highlights current instance
Parameter Grid (below header):
Section 1: Existing Parameters
- Type and instance parameters for selected type
- Current values from sampled instances (sampling cap applies)
- NOT editable (read-only display)
Separator
Section 2: Shared Parameters from Selected Group
- Each SP row:
- Apply Checkbox: Enables mapping
- End-State Toggle:
Host-levelvsEmbed-in-family - Map From Dropdown: Existing parameters with values (scored by name similarity + coverage)
- Current Value: From visible instance
- Proposed Value: From "Map From" parameter
- Diff Styling: Old struck-through, new in red/bold when different
11.5 Instance Navigation
Instance navigation is available on the Shared Parameters tab only.
Behavior:
- Navigator appears inside SP type rows when instances sampled
- Prev/Next arrows cycle through instances
- Current and Proposed values update live on cycle
- Proposed turns red/bold when different from Current
Families Tab:
- Does NOT have instance navigation
- Shows quantity and "Views Containing" list only
11.6 Scoring and Defaults for "Map From"
Algorithm:
- Combine name similarity (same as Families tab matching) with value coverage
- Coverage = % of sampled instances with non-empty values
- Highest combined score preselected
Manual Override:
- User can change "Map From" selection
- Selection preserved across refreshes
11.7 Visual Indicators
Badges:
- Host-level bound: Badge if SP already exists as project parameter
- Embedded: Badge if SP already embedded in family
- Deep Scan: DS badge if deep scan data available for this family
- No
DS!variant; partial scan state is exposed via tooltip copy
- No
Gating Label:
- "Deep Scan required" shown when reading embedded schema needed (e.g., unplaced types)
11.8 Apply Semantics
Host-Level:
- Bind SP to Category (Type or Instance per SP definition)
- Copy values from selected "Map From" parameter
- Batch operation (all checked SPs in one transaction)
- Plan-time validation requires selected symbol IDs to resolve in kernel and align with current owner-family context
Embed-in-Family:
- Open family in background
- Add SP per definition (Type or Instance)
- Set default values
- Reload family
- Copy values from "Map From" parameter
- Plan-time validation rejects stale/mismatched symbol-family selections before commit is opened
No "Remove Source" Option:
- Source parameter retained after copy
- User must manually delete if desired
Source:
src/Tools/Common/GM/Shell/Services/PlanBuildingService.cs:489
Source:src/Tools/Common/GM/Features/SharedParameters/SharedParameterAuthoringService.cs:141
11.9 Persistence
On Close (UI State):
- Selected SP type (
SelectedSPTypeSymbolId) - SP Types and Parameters scroll offsets
- Per-type parameter selections:
- Apply checkbox state
- Host-level vs Embed toggle
- "Map From" selection by name
NOT Persisted:
- Nested scroll positions inside type details
- Sampled instance values (always fresh on load)
12. Global Controls
12.0 Status and Empty-State UX
Bottom Status Line:
- Single-line format:
Global: ... || Active: ... Globalsummarizes Families/Usage/Shared Parameters readinessActivesummarizes the currently selected tab state
Empty-State Messaging Contract (all tabs):
- Before first load completes: show explicit loading language (
Loading...) - After load completes with zero rows: show contextual
No ...messaging - Use high-contrast theme text brushes (primary title + secondary guidance)
Source:
src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:867
Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:1035
Source:src/Tools/Common/GM/Shell/Views/GmShellWindow.xaml:1469
Source:src/Tools/Common/GM/Shell/Views/GmShellWindow.xaml:4619
12.1 Sort Mode
Options:
- Similarity: Colored labels with percentages, sorted high→low, threshold-aware
- Alphabetical: Default styling, lexicographic sort, threshold ignored
Applies To:
- ALL target option lists (top-level and nested)
- Families, Line Styles, Object Styles, Materials tabs
Behavior:
- Switching mode updates ALL visible comboboxes instantly
- Manual selections preserved (only re-sorted, not cleared)
12.2 Threshold Slider
Purpose:
- In similarity mode: influences default selection only
- Options below threshold NOT auto-selected
- Manual selections NEVER overridden by threshold change
Range: 0-100 (similarity percentage)
Debounced: 200ms debounce to avoid UI spam
Applies To:
- Families, Line Styles, Object Styles, Materials tabs
- Top-level AND nested target option lists
Does NOT Apply To:
- Row visibility (all rows shown regardless of threshold)
- Alphabetical mode (threshold ignored)
12.3 Search
Behavior:
- Filters SourceName for active tab
- Case-insensitive substring match
- Real-time (no debounce needed for simple string filter)
Applies To:
- Families, Line Styles, Object Styles, Materials, SP tabs
12.4 Filters
Available Filters:
Hide Unscanned (Families only)
- Hides families without deep scan data
- Checkbox toggle
Show Applied Only
- Shows rows that are applied manually, externally-originated, or usage-linked from Object Styles/Materials
- Applies to all tabs
Source:
src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.MappingInitAndInfrastructure.cs:255
Source:src/Tools/Common/GM/Shell/ViewModels/UsagePaneViewModel.cs:674
Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:988
Persistence:
- Filter states saved in UI state cache
- Restored on window reopen
12.5 Clear Scans
Purpose:
- Clears global
deep-scan-cache.jsoncache file - Forces re-scan of all families
When to Use:
- User knows families have changed significantly
- Deep scan data appears outdated or incorrect
Behavior:
- Deletes
deep-scan-cache.jsonfile - DS badges removed from all rows
- Next deep scan rebuilds cache
- Clears in-memory deep-scan usage caches and refreshes usage/family/SP views immediately
- Shows success banner: "Deep scan cache cleared."
Source:
src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:3322
12.6 Clear Preferences
Purpose:
- Clears saved mapping preferences (saved target selections and related origin markers)
Behavior:
- Clears in-memory commit success mappings and persisted saved mappings for the current document key
- Rebuilds usage + family match rows so saved-origin badges are removed
- On reopen, restored saved mappings seed target dropdowns only;
Applystaysfalseby default until explicit user action - Resets saved Shared Parameter row selections (
Apply,Map From, edited flags) during clear - Persists an explicit empty saved-mappings snapshot when mappings are cleared (overwrites stale mapping files)
- Suppresses the next window-close saved-mappings write to prevent immediate re-persist of just-cleared mappings
- Saved-mappings write failures are logged and surfaced via a user-visible error banner (no silent persistence failures)
- Shows success banner: "Cleared saved mappings for this document."
Does NOT Clear:
- Deep scan cache
- UI state (tab, search, filters)
Saved mapping kinds + keying (owner-family scoped):
GM persists commit-success mapping targets as SavedMappingEntry records. Keying is kind-specific; the following kinds are explicitly scoped by OwnerFamilyId:
| Kind | Meaning | Key fields |
|---|---|---|
nested-style |
Expanded-family Object Style mapping | (Kind, FromId=StyleId, OwnerFamilyId=FamilyId) |
nested-material |
Expanded-family Material mapping | (Kind, FromId=MaterialId, OwnerFamilyId=FamilyId) |
param-name |
Expanded-family Parameter name mapping | (Kind, FromId=StableStringId(SourceParamName), OwnerFamilyId=FamilyId) |
param-type-policy |
Expanded-family type-write policy for (Type) parameter migration |
(Kind, FromId=FamilyId, OwnerFamilyId=FamilyId) |
Source:
src/Tools/Common/GM/Features/SavedMappings/SavedMappingEntry.cs:31
Source:src/Tools/Common/GM/Features/SavedMappings/SavedMappingEntry.cs:38
Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.MappingInitAndInfrastructure.cs:391
Source:src/Tools/Common/GM/Shell/ViewModels/FamiliesPaneViewModel.cs:486
Source:src/Tools/Common/GM/Shell/ViewModels/FamiliesPaneViewModel.cs:781
Source:
src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:1879Source:src/Tools/Common/GM/Shell/Services/UiStatePersistenceService.cs:62Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.MappingInitAndInfrastructure.cs:742Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:2017Source:src/Tools/Common/GM/Shell/Services/UiStatePersistenceService.cs:90Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:2066Source:src/Tools/Common/GM/Shell/ViewModels/UsagePaneViewModel.cs:676Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.MappingInitAndInfrastructure.cs:487
12.7 Clear Saved UI State
Purpose:
- Clears persisted GM UI state for the current document key.
Behavior:
- Deletes per-document UI snapshot from cache
- Suppresses the next close-cycle UI save so the cleared state is not immediately recreated
- Shows success banner: "Cleared saved UI state. Next open will use defaults."
Source:
src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:1932Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.MappingInitAndInfrastructure.cs:742
13. Plan & Commit Workflow
13.1 Plan Construction
User selections are tracked in live dictionaries throughout the session. When the user clicks Review Plan:
- BuildPlanAsync() normalizes all checked rows into categorized operations
- Plan operations are grouped by category and validated
Usage-Link Planning Guardrail:
- Families auto-checked by Object Styles/Materials usage links are not automatically considered opted-in for family-owned operations.
- Type replacements, parameter maps, and create-type operations require explicit ownership on the family row:
- manual family Apply, or
- user-edited, saved-origin, or externally-originated row state.
- Material/style replacements remain host-level mappings and are still planned directly from Usage tab selections.
Shared Parameter Planning Semantics:
- Shared parameter operations are built from SP tab definition rows with
Apply=trueand a selected SP category. - SP operation planning is independent of Families tab Apply ownership gating.
Source:
src/Tools/Common/GM/Shell/Services/PlanBuildingService.cs:80
Plan Categories:
| Category | Description |
|---|---|
| Parameters | Parameter value migrations |
| TypeCreation | New types requested from expanded-family type rows (Create=true) |
| HostReplacements | Host-level element replacements |
| NestedFamilies | Nested family swaps |
| TypeReplacements | Family type replacements |
| StyleReplacements | Line/Object style replacements |
| MaterialReplacements | Material replacements |
| SharedParameters | SP bindings and value copies |
Validation:
- Self-maps rejected (source == target)
- Zero/negative IDs rejected
- Invalid operations throw
GmInvalidMappingError
13.2 Commit Review Window
When opened, Commit Review builds and validates the plan only. No document edits run until a commit action is selected.
Display Content:
- Operation breakdown by category with counts
- Plan summary and validation status before commit
- Bottom-edge status bar with live metrics:
Rules — Planned / Applied / Failed / SkippedFamilies — Planned / Attempted / Failed
- Normalized details showing from → to mappings
- Error indicators for any validation failures
- Failed Operations uses a single canonical table surface summarized per mapping rule with per-row:
- Stage, Operation, Source, Target
Message(aggregated error classifications + per-family context)
- Domain Summary shows per-stage succeeded/failed/skipped/affected totals and includes family/timing metrics when available.
- Report DataGrids use centered column headers and right-click context actions:
Copy SelectedCopy All- Copied row format:
Header: Value | Header: Value ...
- Expanders with no meaningful content are hidden (no placeholder clutter).
User Actions:
- Close: Closes review without changing the model
- Apply: Begins a staged
TransactionGroup, executes apply + verify inside the group, and leaves the group open (pending accept/rollback). - Commit Review is modal for the entire apply/accept/rollback session to keep the transaction group open safely.
- After apply completes, Commit Review stays open and shows results:
- Rollback: Rolls back the transaction group (discard all changes from this apply session)
- Accept + Continue: Assimilates the transaction group, closes Commit Review, and returns to GM (runs refresh pipeline and persists applied mappings)
- Accept + Close GM: Assimilates the transaction group, closes Commit Review, and closes the GM tool (honors close even on partial failure)
- Retry Apply: Shown when apply applied nothing and failures were detected. Retries using the existing plan.
- Post-apply banner feedback:
- Success: green summary banner with Applied/Failed/Skipped counts
- Failure: banner beginning with "Apply completed." (includes Failed/Skipped diagnostics) or "Apply failed." when an exception occurs
- Commit Review stays open on failed apply so users can inspect operation errors before choosing Retry/Accept/Rollback.
- Accept + Continue performs an immediate in-session refresh:
- evicts current Families category snapshot
- re-scans families for the active category
- refreshes usage indices when usage mappings are affected (or usage is already loaded)
- restores expanded family/details state so instance cyclers continue to function on refreshed rows
Implementation:
src/Tools/Common/GM/Shell/ViewModels/CommitReviewViewModel.cs
src/Tools/Common/GM/Shell/Services/CommitReviewWindowService.cs
src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.UsageAndFamilies.cs
src/Tools/Common/GM/Shell/Services/FamiliesCategoryCacheService.cs
Source:
src/Tools/Common/GM/Shell/Services/CommitReviewWindowService.cs:24
Source:src/Tools/Common/GM/Shell/Views/CommitReviewWindow.xaml:69
Source:src/Tools/Common/GM/Shell/Views/CommitReviewWindow.xaml:73
Source:src/Tools/Common/GM/Shell/Services/CommitReviewWindowService.cs:61
Source:src/Tools/Common/GM/Shell/ViewModels/CommitReviewViewModel.cs:274
Source:src/Tools/Common/GM/Shell/ViewModels/CommitReviewViewModel.cs:327
Source:src/Tools/Common/GM/Shell/Views/CommitReviewWindow.xaml.cs:60
Source:src/Tools/Common/GM/Shell/Views/CommitReviewWindow.xaml:318
Source:src/Tools/Common/GM/Features/Mapping/Writers/ChangeApplyEngine.cs:69
13.3 Execution Order
On commit, operations execute in strict order through ChangeApplyEngine:
- Create Types — Duplicate source symbols using
NewTypeNamefrom row-level create entries - Host Replacements — Replace host-level elements
- Nested Families — Swap nested family references
- Type Replacements — Replace family instances by type (parameter migrations execute during each type replacement)
- Style Replacements — Apply host-document replacements and family-document rewrites for style references, then reload affected families
- Material Replacements — Apply host-document replacements and family-document rewrites for material references, then reload affected families
- Shared Parameters — Bind/Embed shared parameters and copy values
Transaction Management:
- Writer failures are captured per operation and the apply run continues across remaining stages (non-fail-fast)
- Group-owned family-document references (members inside groups) are skipped (cannot edit group members) and reported as warnings (non-fatal)
- Unhandled exceptions/cancellation stop the commit flow; undo entry granularity depends on the underlying writer transaction boundaries
- Family reload phases run outside host transactions (gate-only) because Revit rejects
LoadFamilywhen the host document is modifiable.
Source:
src/Tools/Common/GM/Features/Mapping/Writers/StyleChangeService.cs:203Source:src/Tools/Common/GM/Features/Mapping/Writers/MaterialChangeServiceWriter.cs:209Source:src/Tools/Common/GM/Shell/ViewModels/CommitReviewViewModel.cs:747Source:src/Tools/Common/GM/Features/Mapping/Writers/StyleChangeService.cs:565Source:src/Tools/Common/GM/Features/Mapping/Writers/NestingChangeService.cs:290Source:src/Tools/Common/GM/Features/SharedParameters/SharedParameterAuthoringService.cs:318Source:src/Tools/Common/GM/Features/Mapping/MappingApplyResult.cs:169Source:src/Tools/Common/GM/Features/Mapping/MappingService.cs:382Source:src/Tools/Common/GM/Features/Mapping/Writers/ChangeApplyEngine.cs:166Source:src/Tools/Common/GM/Features/Mapping/MappingService.cs:413Source:src/Tools/Common/GM/Shell/Startup.cs:57Source:src/Tools/Common/GM/Features/Mapping/Writers/TypeCreationService.cs:30Source:src/Tools/Common/GM/Features/Mapping/Writers/TypeChangeService.cs:190Source:src/Tools/Common/GM/Features/Mapping/Writers/TypeChangeService.cs:259
Progress Overlay:
- Shows current step name
- Throttled progress updates
- Cancel button sets
cancel_requestedflag
Source:
src/Tools/Common/GM/Shell/Services/CommitReviewWindowService.cs:68
Source:src/Tools/Common/GM/Shell/ViewModels/CommitReviewViewModel.cs:229
Source:src/Tools/Common/GM/Shell/ViewModels/CommitReviewViewModel.cs:489
Source:src/Tools/Common/GM/Shell/Views/CommitReviewWindow.xaml:111
Source:src/Tools/Common/GM/Shell/Views/GmShellWindow.xaml:4595
13.4 Row Locking
Individual rows are locked when their target is involved in another mapping.
Lock Sources:
- Families Tab: When a family row's nested mapping (Object Style, Material) is set to Apply, the corresponding row on the Object Styles or Materials tab is locked
- Duplicates Tab: When a duplicate mapping is accepted, the source row on the target tab is locked
Lock Behavior:
IsLocked = trueon affected row- Apply checkbox disabled
- Tooltip explains lock source: "Locked by Families tab (change there)" or "Locked by Duplicates tab (change there)"
Implementation:
// ApplyRowLocks() at GmWindowViewModel.MappingInitAndInfrastructure.cs
private void ApplyRowLocks()
{
// Families-derived locks
// Duplicates-derived locks
// Sets IsLocked and LockTooltip on individual rows
}
Critical: Locking is ROW-level only. There is no tab-level locking.
13.5 Error Handling
Typed Exceptions:
GmInvalidMappingError— Invalid plan operationsGmApplyStageError— Execution failures with stage name and inner failures
Error Surfaces:
- Commit Review shows validation errors before commit
- Progress overlay shows execution errors with details
- Notification banner summarizes outcome
Fail-Fast:
- First error aborts transaction
- User sees error message with option to retry or cancel
- Partial changes rolled back
14. Performance
14.1 Virtualization
Requirement:
- DataGrids use virtualization for large row counts (1000+ rows)
- TextBlock (read-only) shown by default while scrolling
- Switches to ComboBox when cell enters edit mode
Rationale:
- Avoids UI hangs during scrolling with large datasets
- Pre-building comboboxes for all rows is prohibitively expensive
Example (WPF):
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<TextBlock Text="{Binding TargetName}" />
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<ComboBox ItemsSource="{Binding TargetOptions}" ... />
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
14.2 Pre-building Target Options
Policy:
Pre-built:
- Top-level target option lists for current category only (fast startup priority, <1000 families)
- Families tab: when category selected, pre-build options for all families in that category
- Materials/Styles tabs: pre-build on first visit
NOT Pre-built:
- Nested detail options (Types, Parameters, Styles within expanded family rows)
- Other curated categories (lazy load on category change)
Rationale:
- Balances startup time with responsiveness
- Pre-building ALL categories would be memory-intensive and slow
Background Pre-warming:
- Background pre-warming on idle thread is not feasible due to Revit API UI thread constraint
- First category switch warms cache; subsequent switches are instant
- This is an accepted trade-off for correctness and architectural cleanliness
14.3 Lazy Loading Policy
Section Gating:
Families Section:
- Built on first Families tab visit (or immediately if default tab)
- Rebuilt on category change
Usage Section:
- Built on first visit to Line Styles, Object Styles, or Materials tab
- Cached for session (reused across these three tabs)
- Style usage canonicalization normalizes GraphicsStyle/Category raw IDs to projection-style IDs per concrete category (no name-based cross-category collapse)
- Canonicalization resolves raw category IDs (including built-in negative category IDs) before positive-ID filtering so valid object-style usage is not dropped.
- Line/Object style scope classification uses ancestor-category matching so nested category chains still classify correctly
Shared Parameters Section:
- Built on first visit to SP tab
- Cached for session
Expanded Row Details:
- Always lazy (never pre-built)
- Build on "+" expand or "Scan Family" button
- Nested target options built on target change
14.4 Option List Caching
Session Cache:
- Target option lists cached in memory for session
- Instant tab switching (no re-query)
- Cleared on window close
Invalidation:
- Category change: rebuild options for new category
- Clear Scans: rebuild options affected by deep scan data
14.5 Visible-Row-Only Name Resolution
Strategy:
- Resolve names for top N visible rows immediately (batch: 250 IDs)
- Additional rows resolve as they scroll into view (if hooked to scroll event)
- Or use id fallback text until explicit "Resolve All Names" action
Implementation:
// Batch resolution (limit 250)
src/Tools/Common/GM/Shell/UI/ViewModels/GmShellViewModel*.cs
ResolveNamesAsync, EnsureNamesForIdsAsync
RebuildMaterialStyleViews, RebuildTypeRows, RebuildFamilyList
// Scroll handlers
src/Tools/Common/GM/Shell/UI/Views/GmWindow.xaml(.cs)
OnFamiliesScrollChanged, OnStylesScrollChanged, etc.
14.6 Throttling
Overlay Updates:
- Progress steps tick at chunk boundaries
- Throttled to ~200ms minimum between updates
- Avoid UI spam
Threshold Debounce:
- Threshold slider debounced 200ms
- Avoid rebuilding options on every pixel drag
14.7 Usage Index Performance Contracts
Sampling Cap:
QuerySampleInstanceIdsBySymbolcollects at mostConstants.MaxUsageSamples(2500) instances- Model-owned instances sampled first (document-wide collector), then view-owned
- One instance per
FamilySymbol—SymbolGeometryis shared across all instances of same type - Early exit when cap reached
Geometry Extraction:
- Single
get_Geometry()call per sampled instance - Options:
ComputeReferences = false,IncludeNonVisibleObjects = true,View = ActiveView - No secondary "model geometry" pass —
IncludeNonVisibleObjectscaptures all subcategory refs
Style Canonicalization:
BuildStyleCanonicalizationMapruns ONCE per usage build (not per chunk)- Simple mapping: raw GraphicsStyle/Category ID → canonical projection GraphicsStyle ID
- Recorder/classifier preserve raw + canonical IDs only (no broad alias fan-out)
Deep Scan Isolation:
BuildStyleUsageAsyncNEVER triggers deep scan- Deep scan is manual only (Section 15.1)
- Families with zero placed instances have no usage data until manually scanned
Parameter Scanning:
FamilyScanner.QueryMappableParamNamesForFamilyscans ONE instance per family- Parameter definitions are family-level — all instances share identical definitions
- Type parameters from
FamilySymbolobjects; instance parameters from single sample instance
Part IV: Features
15. Deep Scan
15.1 Trigger Policy
Manual Only:
- Never auto-triggered
- User clicks "Scan Family" button or "Deep Scan" from context menu/toolbar
- Deep scan cache hydration is separate: GM may restore previously-scanned results from cache at startup without opening the family.
Rationale:
- Deep scan opens family in background (expensive operation)
- User should control when this happens
15.2 Execution Flow
Steps:
- Show overlay: "Preparing scan..."
- Collect host-side scan data (no family open yet):
- Parameter names (from loaded symbols + sample instances when available)
- Sample instance IDs + per-type instance counts (bounded)
- Open family via
EditFamily(Revit API call) and collect deep usage data:- Portable usage identifiers (cache-safe across documents):
- Materials by name
- Object Styles by
GraphicsStyleKey(category-path key) - Nested families by family name
- In-session host element IDs (where resolvable) for kernel/UI use
- Portable usage identifiers (cache-safe across documents):
- Close family document
- Update kernel
GMDeepScanState - Write to
deep-scan-cache.jsoncache file (mid-session write) - Update UI: badges, details unlock, augment usage lists
Cancellation:
- User can cancel during scan
- Partial results discarded
15.3 Cache Persistence
File: deep-scan-cache.json (global, NOT document-specific)
Key (Composite):
{
"FamilyName": "Wall-Basic",
"CategoryId": -2000011,
"TypeCount": 5,
"ParameterSignature": "abc123def456"
}
Payload:
{
"ParameterNames": ["Width", "Height", "Material", ...],
"StyleKeys": [{ "ParentBuiltInCategoryId": -2000011, "ParentName": null, "Name": "Thin Lines" }, ...],
"MaterialNames": ["Concrete", "Steel", ...],
"NestedFamilyNames": ["Nested A", "Nested B", ...],
"CachedAt": "2025-11-21T10:30:00Z"
}
Write Timing:
- Immediately on scan completion (mid-session write)
Cross-Session Reuse:
- Name-based keys enable global cache
- Different documents can share deep scan data for same family
15.4 Cache Reuse Policy (Strict Restore)
Deep scan cache does not display a separate staleness badge. Instead, cache reuse is conservative:
- Cache key mismatch (FamilyName/CategoryId/TypeCount/ParameterSignature) → cache miss → deep scan runs when user requests it
- Cache restore is strict: if any cached portable identifier (style/material/nesting) is missing or ambiguous in the current document → cache miss
- Cache restore is attempted as a startup hydration for eligible 0-instance, unscanned families. If strict restore rejects cached data, GM logs a warning and keeps the family unscanned until the user deep scans it.
- Clicking Scan Family always performs a fresh deep scan and overwrites any existing cache entry.
Source:
src/Tools/Common/GM/Shell/Services/DeepScanOrchestrationService.cs:128
Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.MappingInitAndInfrastructure.cs:1144
15.5 Badge Indicators
DS Icon:
- Shown on top-level family row when deep scan completed
- Tooltip: "Deep scanned on {date}"
15.6 Augmentation of Usage Lists
Families discovered ONLY via deep scan (no instances in project) appear in:
- "Families Using This Object Style" lists
- "Families Using This Material" lists
Styling:
- DS badge distinguishes deep-scan-only families
- Usage count remains 0 (not inflated)
16. Type Mapping
16.1 Create New Type
UI:
- Per-row
Createcheckbox in the expanded-family Types grid - Per-row
New Type Nameinline editor - No separate header-level create button (single creation flow)
Behavior:
- User checks
Createon a type row. - If
New Type Nameis empty, GM seeds it from the row's source type label. - Type row
Applyis derived automatically from either:- a valid selected target type, or
Create=truewith a non-emptyNewTypeName.
- Clearing
CreateclearsNewTypeNameand removes pending create-type intent from the live plan.
16.2 Inline Rename
UI:
New Type NameTextBox on each type row (enabled only whenCreate=true)
Behavior:
- User edits the inline name value.
- Name is stored in
MatchingRow.NewTypeName. - Planning emits
CreateTypeoperations from live-planCreateTypesentries.
Source:
src/Tools/Common/GM/Shell/Views/GmShellWindow.xaml:1812
Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:1182
Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:1196
Source:src/Tools/Common/GM/Shell/Services/LivePlanState.cs:127
Source:src/Tools/Common/GM/Shell/Services/PlanBuildingService.cs:136
16.3 Plan/Apply/Review Integration
Planning:
- Live-plan
CreateTypesentries (SourceId,NewName) drive create operations - Operation kind:
OperationKind.CreateType
Apply:
- Transaction:
new Transaction(doc, "Create Type") FamilySymbol.Duplicate(name)→ returns ElementId- Must call
doc.GetElement(id)to get symbol
Review:
- Created types shown in Commit Review with "New" indicator
- Count included in summary: "5 types created"
17. Preview Pane
17.1 Toggle Behavior
Within expanded family details:
- Selecting Object Style row → show preview (swatch + style line)
- Selecting Material row → show preview (color swatch + texture)
Toggle:
- Checkbox or auto-show on selection
- Positioned on right side of window
17.2 Preview Content
Object Styles:
- Swatch (color/pattern)
- Line weight
- Line pattern
- Sample line rendering
Materials:
- Color swatch
- Texture preview (if applicable)
- Shininess, transparency properties
- Sample material rendering
18. Sorting, Filtering, Threshold
18.1 Similarity Mode
Visual Styling:
- Colored labels: Green (high similarity) → Orange (medium) → Red (low)
- Percentages appended:
"Target Name (92%)" - Sorted high → low by score
Threshold Behavior:
- Options below threshold NOT auto-selected
- Manual selections NEVER overridden
- Lowering threshold can clear auto-matches (NOT manual selections)
Applies To:
- ALL target comboboxes (top-level and nested)
- Families, Line Styles, Object Styles, Materials tabs
18.2 Alphabetical Mode
Visual Styling:
- Default styling (no colored labels)
- NO percentages appended
- Sorted lexicographically A-Z
Threshold Behavior:
- Threshold slider has NO effect (ignored)
- All options visible regardless of score
Applies To:
- ALL target comboboxes (top-level and nested)
18.3 Manual Selection Preservation
Critical Rule:
Manual selections are sticky across:
- Sort mode changes (Similarity ↔ Alphabetical)
- Threshold changes
- Refresh operations
Only Reset If:
- Target disappears from options (e.g., family deleted, category changed)
Visual Marking:
- Manual selections visually distinguished (bold or icon)
- Auto-selections use default styling
18.4 Nested Target List Behaviors
Nested target comboboxes (Types, Parameters, Styles, Materials within expanded family rows) must:
Respect Sort Mode:
- Similarity: colored labels, percentages, high→low
- Alphabetical: default styling, A-Z, no scores
Respect Threshold:
- Similarity mode: threshold affects default selection
- Alphabetical mode: threshold ignored
- For nested Types rows, auto-selected defaults below threshold clear to a blank sentinel (no selection)
<Do Not Map>remains the first visible dropdown option and is only set by explicit user selection
Preserve User Selections:
- Manual, saved-mapping, and externally-originated selections are sticky across sort/threshold changes
Update on Target Change:
- When top-level target family changes on an expanded family row, rebuild nested options/defaults from the new target
- Rebuild applies to Types, Parameters, Object Styles, and Materials nested lists
- Auto-selected rows are recomputed; manual/saved/external rows are preserved
Source:
src/Tools/Common/GM/Shell/ViewModels/FamiliesPaneViewModel.cs:1094
Source:src/Tools/Common/GM/Shell/ViewModels/FamiliesPaneViewModel.cs:1126
Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.UsageAndFamilies.cs:388
Source:src/Tools/Common/GM/Shell/ViewModels/ShellViewModel.CoreAndCommands.cs:1196
Source:src/Tools/Common/GM/Shell/Views/GmShellWindow.xaml:110
Part V: Quality & Implementation
19. Known Issues & Roadmap
19.1 Completed Features
The following features are fully implemented:
- Families Parameter Pruning — Programmatic filters in
RevitCompat.IsUserMappable - Nested Target Comboboxes — Four nested collection types (Types, Parameters, Materials, Object Styles)
- Unscanned Family Demotion —
MatchingOption.Separatorwith greyed-out styling - Deep-Scan Augmentation of Usage Lists — DS-only families merged with "(DS)" badge
- Cross-Tab Apply Lock —
ApplyRowLocks()withIsLockedproperty - Nested Type Create Flow — per-row
CreateNewType+NewTypeNamepipeline (single UI path) - Parameter Mapping
<Do Not Map>Sentinel —MatchingOption.DoNotMapas first option - Nested List Sorting/Threshold — Debounced refresh with selection preservation
- Deep Scan Overlay Bug Fix —
!IsBusycheck prevents double overlay
19.2 Remaining Implementation Tasks
HIGH PRIORITY:
Deep Scan Global Cache
- Status: Implemented — global
deep-scan-cache.jsonfile with portable payload and strict restore policy - Impact: Deep scan results can be reused across sessions and across documents when portable identifiers resolve cleanly
- Implementation files:
src/Tools/Common/GM/Features/DeepScan/DeepScanCache.cssrc/Tools/Common/GM/Features/Caching/DeepScanCacheStrategy.cssrc/Tools/Common/GM/Shell/Services/DeepScanOrchestrationService.cs
Line Styles Tab Stability
- Issue: Selecting Line Styles can error and close window
- Likely causes: SelectionChanged executing before initial load, missing descriptor, VM contract violations
- Tasks:
- Gate SelectionChanged until
_initial_load_completeAND sender is real row - Enforce VM contract post-enrichment; surface failures via overlay
- Validate descriptor presence at collection time
- Gate SelectionChanged until
MEDIUM PRIORITY:
Preview Pane Toggle
- Status: Preview panels exist but are always visible when data available
- Task: Add user-controlled toggle button/checkbox in family details
SP Grid Empty State
- Status: Generic "wrong category" banner exists
- Task: Add specific "No shared parameters" banner
Broad Exception Cleanup
- Task: Remove or narrow broad try/except to typed, narrow boundaries
- Task: Verify no residual legacy module references
LOW PRIORITY:
Families Details Linking
- Status: Implemented (parent target change now refreshes nested target lists for expanded rows)
Nested Threshold Verification
- Status: Documented (nested target comboboxes follow global sort/threshold semantics)
19.3 Implementation Roadmap
Phase 1: Foundation
- Add Application-layer query wrappers
- Refactor
PlanningOrchestratorto acceptIGmPlanningServicedependency - Repository hardening: Add
SnapshotVersion, strict parsing
Phase 2: UI Refactor
- UI refactor (
GmWindowViewModel):- Constructor: accept only Application services
- Remove adapter/service deps
- Use kernel for families, usage, styles, materials
- Report window: consume
IMappingExecutionServicefrom main VM
Phase 3: DI and Cleanup
- DI consolidation in shell: compose adapters → Application services → VM
- Clean deletions: remove UI callsites referencing Infrastructure/Services
Phase 4: Validation
- Build solution; verify no UI project references Infrastructure namespaces
- Manual sanity pass: Families tab, Usage tabs, Deep scan, SP authoring, Mapping report
20. Quality Acceptance Criteria
20.1 Perfection Standards
Core Philosophy:
"Deliver a near-perfect, clean, spec-compliant GM codebase that we could ship to 1,000,000,000 users with confidence."
What "Perfection" Means:
- Spec → Code Traceability: Every clause maps to concrete code
- One Way to Do a Thing: Canonical, testable paths (no duplicate logic)
- Clean Layering: Collectors produce unique sets; controllers bind once
- Readable, Minimal, Modular: No dead blocks or drift
- Zero Tolerance for Silent Failures: All errors surfaced explicitly
20.2 Green Checks Only
Acceptance Policy:
ALL of the following must be true before merge:
- All tests pass (no warnings)
- No duplicate curated lists outside single source
- No duplicate top-level rows
- Revisit any tab → no overlay reload
- Sort-mode switch updates Display/brush across all visible rows
- Families details: Types/OS/Materials/Parameter Names without DS when Qty>0
- SP tab: group default = first alphabetical; category = first curated
20.3 Behavioral Validation
Manual Testing Scenarios:
Kernel Determinism:
- Given same Revit document and filters → two builds produce identical kernel
- Changing category filter only replaces Families (preserves Usage)
No Duplicate Reads Per Tab:
- Navigate between tabs → verify no additional adapter calls beyond name previews
- Use adapter call counters
Mapping Planning Correctness:
- Self-maps and zero/negative IDs rejected with
GmInvalidMappingError - Parameter migrations execute during type replacements (adjacent to type swaps); operation results are recorded under
ApplyStages.Parameters
- Self-maps and zero/negative IDs rejected with
Placement Taxonomy:
- Adapter placements normalize into { NotHosted, FaceBased, WorkPlaneBased } only
Snapshot Round-Trip:
- Save → close → reopen → verify UI state restored (category, filters, scroll, kernel snapshot)
- Corrupt snapshot throws
GmSnapshotLoadException, UI proceeds with fresh build
Deep Scan UX Integration:
- Run deep scan → badges update, availability changes, no usage indices re-read
END OF SPECIFICATION