This document describes the technical architecture of Codeagogo, a macOS menu bar application for working with clinical terminology codes.
- Overview
- System Architecture
- Component Details
- Data Flow
- Concurrency Model
- Caching Strategy
- Error Handling
- Security Considerations
- Dependencies
- Design Decisions
Codeagogo is a macOS utility that provides global hotkeys for working with clinical terminology codes (SNOMED CT, LOINC, ICD-10, etc.) from any application.
| Hotkey | Feature | Description |
|---|---|---|
Control+Option+L |
Lookup | Display concept details for selected code |
Control+Option+S |
Search | Find concepts by term and insert into document |
Control+Option+R |
Replace | Annotate codes with ID | term | format |
Control+Option+E |
ECL Format | Pretty-print or minify ECL expressions |
Control+Option+V |
ECL Workbench | Monaco-based ECL editor with live evaluation |
Control+Option+H |
Shrimp | Open concept in Shrimp terminology browser |
- Menu Bar Application — Runs as a background process with menu bar presence
- Six Global Hotkeys — Lookup, Search, Replace, ECL Format, ECL Workbench, and Shrimp
- Multi-Code-System — SNOMED CT, LOINC, ICD-10, RxNorm, and configurable systems
- FHIR Integration — Queries FHIR R4 terminology servers
- Batch Operations — Fast bulk lookups via
ValueSet/$expand - ECL 2.x Support — Full Expression Constraint Language parsing and formatting via ecl-core
- SwiftUI Interface — Modern declarative UI framework
- Actor-Based Concurrency — Thread-safe operations using Swift actors
flowchart TB
subgraph App["Codeagogo Application"]
subgraph Presentation["Presentation Layer"]
MenuBar["Menu Bar<br/>Status Item"]
Popover["Popover View<br/>(SwiftUI)"]
SearchPanel["Search Panel<br/>(SwiftUI)"]
VisualizationPanel["Visualization Panel<br/>(SwiftUI + WebView)"]
ECLWorkbench["ECL Workbench<br/>(WKWebView + Monaco)"]
ECLRefPanel["ECL Reference Panel<br/>(SwiftUI)"]
Settings["Settings View<br/>(SwiftUI)"]
ProgressHUD["Progress HUD"]
end
subgraph Application["Application Layer"]
AppDelegate["App Delegate"]
LookupVM["Lookup ViewModel<br/>(@MainActor)"]
SearchVM["Search ViewModel<br/>(@MainActor)"]
VisualizationVM["Visualization ViewModel<br/>(@MainActor)"]
EvaluateVM["Evaluate ViewModel<br/>(@MainActor)"]
HotKeySettings["HotKey Settings<br/>(6 Singletons)"]
CodeSystemSettings["Code System Settings"]
end
subgraph Service["Service Layer"]
GlobalHotKey["Global HotKeys<br/>(Carbon, 6 keys)"]
SelectionReader["Selection Reader<br/>(AppKit + AX API)"]
ECLBridge["ECL Bridge<br/>(ecl-core via JSC)"]
SCTIDValidator["SCTID Validator<br/>(Verhoeff)"]
SNOMEDParser["SNOMED Expression Parser<br/>(Compositional Grammar)"]
DiagramRenderer["Diagram Renderer<br/>(SVG Generation)"]
subgraph OntoClient["Ontoserver Client"]
FHIRParser["FHIR Parser"]
BatchLookup["Batch Lookup<br/>(ValueSet/$expand)"]
Cache["Cache<br/>(Actor)"]
PropertyCache["Property Cache<br/>(Actor)"]
end
end
end
Server[("FHIR Terminology Server<br/>(Ontoserver)<br/>via HTTPS")]
MenuBar --> AppDelegate
Popover --> LookupVM
SearchPanel --> SearchVM
VisualizationPanel --> VisualizationVM
ECLWorkbench --> EvaluateVM
AppDelegate --> LookupVM
AppDelegate --> SearchVM
AppDelegate --> VisualizationVM
AppDelegate --> EvaluateVM
AppDelegate --> GlobalHotKey
LookupVM --> SelectionReader
LookupVM --> OntoClient
SearchVM --> OntoClient
VisualizationVM --> OntoClient
EvaluateVM --> OntoClient
VisualizationVM --> SNOMEDParser
VisualizationVM --> DiagramRenderer
DiagramRenderer --> SNOMEDParser
AppDelegate --> ECLBridge
ECLRefPanel --> ECLBridge
LookupVM --> SCTIDValidator
OntoClient --> Server
- Role: Application entry point (
@main) - Responsibilities:
- Define the SwiftUI App structure
- Configure the Settings scene
- Add Help menu commands for diagnostics
- Role: NSApplication delegate
- Responsibilities:
- Set up the menu bar status item
- Manage the popover and search panel lifecycle
- Register and handle six global hotkeys (Lookup, Search, Replace, ECL Format, ECL Workbench, Shrimp)
- Coordinate lookup, replace, ECL format, and ECL Workbench operations
- Validate concepts in background after ECL format/evaluate (
validateConceptsInBackground,validateConcepts,buildConceptWarnings) — inactive or unknown concepts trigger warning HUD - Show precedence warnings when formatting ECL with mixed AND/OR/MINUS operators
- Manage ECL Reference panel lifecycle
- React to hotkey setting changes via Combine
- Role: Lookup result display UI
- Responsibilities:
- Display concept lookup results (adapts for SNOMED CT vs other systems)
- Show inactive concept highlighting (orange warning)
- Provide copy-to-clipboard buttons
- Provide "Open in Shrimp" button to view concept in Shrimp browser
- Show loading and error states
- Role: Search and insert UI
- Responsibilities:
- Typeahead search interface
- Code system and edition selection
- Insert format selection (ID, PT, FSN, ID|PT, ID|FSN)
- Display search results with PT, FSN, ID, and edition
- Role: NSWindow management for search panel
- Responsibilities:
- Create floating panel window
- Position panel near cursor
- Handle panel show/hide
- Role: Concept visualization UI
- Responsibilities:
- Display WebView with HTML/SVG diagram
- Show loading and error states
- Handle download requests via JavaScript bridge
- Sanitize filenames for SVG/PNG exports
- Generate meaningful export names (e.g.,
73211009-diabetes-mellitus.svg)
- Role: NSWindow management for visualization panel
- Responsibilities:
- Create floating panel window (follows SearchPanelController pattern)
- Position panel to right of popover (or left if insufficient space)
- Handle panel show/hide and lifecycle
- Role: ECL Workbench UI (Monaco-based ECL editor with live evaluation)
- Responsibilities:
- Host a
WKWebViewloading the@aehrc/ecl-editorweb component (ecl-editor.standalone.js, ~922KB bundled app resource) - Provide a full Monaco editor with ECL syntax highlighting, bracket matching, and minimap
- FHIR-powered autocomplete (concept and description suggestions from terminology server)
- Inline diagnostics: parse errors and warnings displayed in the editor gutter
- Formatting via Shift+Alt+F; display term toggle via Shift+Alt+T; hover info on concept IDs
- Live evaluation with 1-second debounce; Cmd+Enter for immediate evaluation
- Resizable split between editor and results pane via drag handle
- Communicate between Swift and the web component via
WKScriptMessageHandler(postMessage bridge) - Monaco loaded from CDN (jsdelivr)
- Host a
- Resource:
ecl-editor.standalone.js— standalone build of the@aehrc/ecl-editornpm package, loaded as an app bundle resource into WKWebView
- Role: ECL evaluation results display (embedded in ECL Workbench)
- Responsibilities:
- Display total count of matching SNOMED CT concepts
- Show scrollable list of matching concepts
- Display semantic validation warnings banner (yellow) for inactive or unknown concepts found in the ECL expression
- Show loading and error states
- Role: NSWindow management for ECL Workbench panel
- Responsibilities:
- Create floating panel window (follows SearchPanelController pattern)
- Panel stays visible when switching apps (utility window level)
- Position panel near cursor
- Handle panel show/hide and lifecycle
- Role: ECL reference documentation UI
- Responsibilities:
- Display 50 knowledge articles from ecl-core across 6 categories (operators, refinements, filters, patterns, grammar, history)
- Expandable articles with Markdown content, code blocks, tables, and ECL examples
- Search/filter field to find articles by keyword
- Link to ECL specification
- Loaded via
ECLBridge.getArticles()which returnsKnowledgeArticlestructs
- Role: NSWindow management for ECL reference panel
- Responsibilities:
- Create floating panel window (follows SearchPanelController pattern)
- Panel stays visible when switching apps (utility window level)
- Handle panel show/hide and lifecycle
- Role: Application preferences UI
- Responsibilities:
- Configure six hotkeys via keystroke recorder (Lookup, Search, Replace, ECL Format, ECL Workbench, Shrimp)
- Configure FHIR endpoint URL
- Configure additional code systems
- Configure replace settings (term format, inactive prefix)
- Toggle debug logging
- Provide diagnostic export functionality
- Role: Keystroke recorder control
- Responsibilities:
- Display current hotkey with modifier symbols (⌃⌥⇧⌘)
- Enter recording mode on button click
- Capture keystroke and update settings
- Role: Progress feedback UI
- Responsibilities:
- Display progress message near cursor
- Show/hide during batch operations
- Show warning HUD (
showWarning()withisWarningstate) — yellow styling for semantic validation and precedence warnings
- Role: MVVM view model for lookups
- Responsibilities:
- Coordinate between UI and services
- Manage loading/error states
- Extract concept IDs from selected text (with SCTID validation)
- Extract existing
| term |patterns for toggle behavior - Trigger lookups and publish results
- Open concepts in Shrimp browser (both from popover button and hotkey)
- Annotations:
@MainActorfor UI safety - Protocols: Accepts
SelectionReadingandConceptLookupClientfor testability
- Role: MVVM view model for search
- Responsibilities:
- Manage search state and results
- Debounce search queries
- Format selected concept for insertion
- Handle code system and edition selection
- Role: MVVM view model for concept visualization
- Responsibilities:
- Fetch properties for concepts using
property=*parameter (lazy loading) - Extract concept IDs from normal form for definition status lookups
- Handle multiple parents for primitive concepts
- Fetch definition status and display names in parallel using TaskGroup
- Manage loading/error states
- Provide VisualizationData to view
- Fetch properties for concepts using
- Annotations:
@MainActorfor UI safety
- Role: MVVM view model for ECL evaluation
- Responsibilities:
- Parse and validate ECL expressions locally before querying server
- Evaluate ECL via terminology server
- Manage loading/error states
- Provide matching concept list to view
- Publish semantic validation warnings (
warningsproperty) for inactive or unknown concepts referenced in the ECL expression
- Annotations:
@MainActorfor UI safety
- Role: Hotkey configuration singletons
- Files:
HotKeySettings,SearchHotKeySettings,ReplaceHotKeySettings,ECLFormatHotKeySettings,EvaluateHotKeySettings,ShrimpHotKeySettings - Responsibilities:
- Store key code and modifiers for each hotkey
- Persist settings to UserDefaults
- Convert between NSEvent.ModifierFlags and Carbon modifiers
- Provide human-readable hotkey description via
KeyCodeFormatter
- Role: Multi-code-system configuration
- Responsibilities:
- Store enabled code systems (LOINC, ICD-10, etc.)
- Persist to UserDefaults
- Provide list of systems for lookup fallback
- Role: Replace hotkey configuration
- Responsibilities:
- Store term format preference (FSN vs PT)
- Store inactive prefix option
- Persist to UserDefaults
- Role: ECL evaluate configuration
- Responsibilities:
- Store maximum number of results to display (default 50)
- Persist to UserDefaults
- Role: FHIR endpoint configuration singleton
- Responsibilities:
- Store custom FHIR server URL
- Validate URL format
- Fall back to default endpoint for invalid URLs
- Persist settings to UserDefaults
- Role: System-wide hotkey registration
- Responsibilities:
- Register Carbon event handlers (supports multiple hotkeys via
idparameter) - Listen for hotkey events
- Invoke callback on hotkey press
- Support live hotkey updates without app restart
- Clean up handlers on deallocation
- Register Carbon event handlers (supports multiple hotkeys via
- Framework: Carbon (legacy but required for global hotkeys)
- Role: System text selection capture and manipulation
- Responsibilities:
- Snapshot current pasteboard contents
- Simulate Cmd+C to copy selection
- Read copied text from pasteboard
- Restore original pasteboard contents
- Paste text via simulated Cmd+V
- Select inserted text via Accessibility API or keyboard fallback
- Requirement: Accessibility permission
- Role: SNOMED CT ID validation
- Responsibilities:
- Validate SNOMED CT IDs using Verhoeff check digit algorithm
- Distinguish SNOMED CT codes from other numeric codes
- Role: Shrimp browser URL construction
- Responsibilities:
- Build Shrimp browser URLs from concept lookup results
- Handle code system-specific URL patterns (SNOMED CT, LOINC, ICD-10, RxNorm)
- Construct appropriate version URIs and ValueSet parameters
- Include dynamic FHIR endpoint in URLs
- Role: SNOMED CT compositional grammar parser
- Responsibilities:
- Parse
normalFormandnormalFormTerseproperty values - Support definition status operators (≡ for defined, ⊑ for primitive)
- Parse focus concepts (parents), refinements, attribute groups
- Handle concept references with pipe-delimited terms (e.g.,
73211009 |Diabetes mellitus|) - Support concrete values and nested expressions
- Preprocess normal form to remove line breaks and normalize whitespace
- Generate AST for diagram rendering
- Parse
- Role: SVG diagram generation
- Responsibilities:
- Generate HTML with embedded SVG following SNOMED CT Diagramming Specification
- Render SNOMED CT relationship diagrams with focus concepts, definition status symbols (≡/○), attributes, and role groups
- Generate LOINC/other system property lists with colored boxes
- Handle multiple parents with vertical junction lines
- Draw attribute groups (filled circles), ungrouped attributes (open circles)
- Render attributes (tan rounded rectangles) and values (blue rectangles)
- Pass concept ID and term to JavaScript for meaningful export filenames
- Provide zoom controls and SVG/PNG download functionality
- Fall back to text display if parsing fails
- Role: Bridge to ecl-core (TypeScript) running in JavaScriptCore
- Files:
ECLBridge.swift,ecl-core-bundle.js(bundled app resource) - Responsibilities:
- Format ECL expressions with 9 configurable options
- Parse ECL 2.x with structured error reporting (line, column, message)
- Validate SNOMED CT concept IDs (Verhoeff check digit)
- Extract concept references from ECL expressions
- Toggle between pretty and minified formats
- Provide ECL knowledge articles (
getArticles()returningKnowledgeArticlestructs) for the ECL Reference panel — 50 articles across 6 categories sourced from ecl-core
- Implementation: Loads ecl-core (bundled as a single JS file) into a
JSContext. Pure computation runs in-process; no external dependencies or Node.js required.
- Role: Check for new releases on GitHub
- Responsibilities:
- Query the GitHub Releases API (
/repos/aehrc/codeagogo/releases/latest) - Compare latest tag semver against the app's
MARKETING_VERSION - Publish
updateAvailablestate via@Publishedfor UI binding - Check on launch and every 24 hours via timer
- Persist last check date, latest version, and release URL in UserDefaults
- Query the GitHub Releases API (
- Consumers: AppDelegate (menu bar badge, menu item), SettingsView (update banner)
- Role: FHIR terminology server client
- Responsibilities:
- Query FHIR
CodeSystem/$lookupendpoint - Query with
property=*for visualization (lazy loading) - Batch lookup via
ValueSet/$expand(15x faster for bulk operations) - Search concepts via
ValueSet/$expandwith filter - Fetch available SNOMED CT editions
- Fetch available non-SNOMED code systems
- Lookup in configured code systems (parallel search)
- Parse FHIR Parameters responses including property values
- Manage in-memory caches (concept results and properties)
- Handle multi-edition fallback
- Implement retry with exponential backoff
- Send
User-Agent: Codeagogo/{version} (macOS)header for server log identification
- Query FHIR
- Role: Thread-safe result cache
- Responsibilities:
- Store lookup results with timestamps
- Implement TTL-based expiration (6 hours)
- Implement LRU eviction at capacity (100 entries)
- Track access patterns for LRU
- Role: Thread-safe property cache for visualization
- Responsibilities:
- Store property arrays with timestamps
- Implement TTL-based expiration (6 hours)
- Keyed by conceptId, system, and version
- Separate from ConceptCache to avoid pollution
struct ConceptResult {
let conceptId: String // Code identifier
let branch: String // Edition name (for SNOMED CT) or system name
let fsn: String? // Fully Specified Name (SNOMED CT only)
let pt: String? // Preferred Term / Display
let active: Bool? // Active/inactive status
let effectiveTime: String? // Version date
let moduleId: String? // Module identifier
let system: String? // Code system URI (for non-SNOMED)
var isSNOMEDCT: Bool // Computed: true if system is SNOMED CT
var systemName: String // Computed: human-readable system name
}struct SearchResult {
let code: String // Concept code
let display: String // Display term (PT)
let fsn: String? // FSN if different from display
let editionId: String? // Edition identifier
let editionTitle: String? // Human-readable edition name
}struct BatchLookupResult {
let ptByCode: [String: String] // Code → Preferred Term
let fsnByCode: [String: String] // Code → Fully Specified Name
let activeByCode: [String: Bool] // Code → Active status
}struct SNOMEDEdition {
let system: String // "http://snomed.info/sct" or "http://snomed.info/xsct"
let version: String // Edition URI (e.g., "http://snomed.info/sct/32506021000036107")
let title: String // Human-readable name
}struct ConceptMatch {
let conceptId: String // Extracted code
let range: Range<String.Index> // Position in source text
let existingTerm: String? // Existing `| term |` if present
let isSCTID: Bool // True if valid SNOMED CT ID (Verhoeff check)
}struct VisualizationData {
let concept: ConceptResult // Base concept info
let properties: [ConceptProperty] // All properties from property=*
let definitionStatusMap: [String: Bool] // conceptId → isDefined
let displayNameMap: [String: String] // conceptId → display name
var isSNOMEDCT: Bool // Computed from concept
}struct ConceptProperty: Identifiable {
let id: UUID
let code: String // Property code (e.g., "effectiveTime", "parent")
let value: PropertyValue // Property value (string, boolean, code, coding, integer)
let display: String? // Human-readable description
}enum PropertyValue {
case string(String)
case boolean(Bool)
case code(String)
case coding(FHIRParameters.Coding)
case integer(Int)
var displayString: String // Converts to human-readable string
}enum OntoserverError: LocalizedError {
case invalidURL(String) // URL construction failed
case conceptNotFound(String) // Concept not in any edition/system
case noEditionsFound // No SNOMED editions available
}flowchart TD
A[User selects text<br/>in any app] --> B[User presses hotkey]
B --> C[GlobalHotKey<br/>triggers callback]
C --> D[Selection Reader<br/>captures text]
D --> E[Extract Concept ID]
E --> F[LookupViewModel]
F --> G{Cache<br/>Check}
G -->|Cache Hit| H[Update UI]
G -->|Cache Miss| I[Ontoserver Client]
I --> J[Query International<br/>Edition]
J --> K{Found?}
K -->|Yes| L[Cache Result]
L --> H
K -->|No| M[Parallel Search<br/>All Editions]
M --> N[Return First Match]
N --> L
flowchart TD
subgraph OntoserverClient
A[1. Check Cache] --> B{Hit?}
B -->|Yes| C[Return cached result]
B -->|No| D[2. Query International Edition]
D --> E["GET /CodeSystem/$lookup<br/>?system=http://snomed.info/sct<br/>&version=.../900000000000207008<br/>&code={conceptId}"]
E --> F{Found?}
F -->|Yes| G[Cache and return]
F -->|No| H[3. Fetch All Editions]
H --> I["GET /CodeSystem<br/>?url=http://snomed.info/sct,xsct"]
I --> J[Parse available editions]
J --> K[4. Parallel Lookup]
K --> L["TaskGroup {<br/> for edition in editions {<br/> lookup(conceptId, edition)<br/> }<br/>}"]
L --> M[Return first successful result]
M --> N[5. Cache Result and Return]
end
flowchart TD
A[User clicks<br/>"Visualize" button] --> B[LookupViewModel]
B --> C[VisualizationPanelController]
C --> D[Create/Show Panel]
D --> E[VisualizationViewModel]
E --> F{Property<br/>Cache?}
F -->|Hit| G[Render Diagram]
F -->|Miss| H["OntoserverClient<br/>lookupWithProperties<br/>(property=*)"]
H --> I[Parse Properties]
I --> J[Extract concept IDs<br/>from normalForm]
J --> K["Parallel TaskGroup:<br/>Lookup each ID for<br/>definition status + display"]
K --> L[Build VisualizationData]
L --> M{SNOMED CT?}
M -->|Yes| N[SNOMEDExpressionParser<br/>Parse normalForm]
N --> O[DiagramRenderer<br/>Generate SVG]
M -->|No| P[DiagramRenderer<br/>Generate property list]
O --> Q[WebView displays HTML/SVG]
P --> Q
Q --> R[User exports SVG/PNG]
R --> S[JavaScript postMessage<br/>with concept ID + term]
S --> T[Sanitize filename<br/>Remove tags, special chars]
T --> U[Save panel with<br/>meaningful name]
The application uses Swift's modern concurrency model:
| Component | Isolation | Reason |
|---|---|---|
LookupViewModel |
@MainActor |
UI state updates |
EvaluateViewModel |
@MainActor |
UI state updates |
HotKeySettings |
@MainActor |
UI-bound singleton |
ConceptCache |
actor |
Thread-safe data access |
OntoserverClient |
None (uses async/await) | I/O-bound operations |
Edition lookups use TaskGroup for parallel execution:
try await withThrowingTaskGroup(of: ConceptResult?.self) { group in
for edition in editions {
group.addTask {
try await self.lookupInSystem(conceptId: conceptId,
system: edition.system,
version: edition.version)
}
}
// Return first successful result
for try await result in group {
if let result = result {
group.cancelAll() // Cancel remaining lookups
return result
}
}
}| Property | Value | Rationale |
|---|---|---|
| Type | In-memory (actor) | Thread-safe, no persistence needed |
| TTL | 6 hours | Balance freshness vs. API load |
| Max Size | 100 entries | Limit memory usage |
| Eviction | LRU (Least Recently Used) | Keep frequently accessed concepts |
struct CacheEntry {
let result: ConceptResult
let createdAt: Date // For TTL calculation
var lastAccessedAt: Date // For LRU tracking
}- Get: Check TTL, update access time, return result
- Set: Evict LRU if at capacity, store with timestamps
- Eviction: Remove entry with oldest
lastAccessedAt
// Network/API errors
enum OntoserverError: LocalizedError {
case invalidURL(String)
case conceptNotFound(String)
case noEditionsFound
}
// User input errors
enum LookupError: LocalizedError {
case notAConceptId
case accessibilityPermissionLikelyMissing
}For transient network failures:
| Attempt | Delay | Total Wait |
|---|---|---|
| 1 | 0s | 0s |
| 2 | 0.5s | 0.5s |
| 3 | 1.0s | 1.5s |
Retryable conditions:
- URLError: timeout, connection lost, DNS failure
- HTTP 5xx server errors
Non-retryable conditions:
- HTTP 4xx client errors
- URL construction failures
| Permission | Purpose | Scope |
|---|---|---|
| Accessibility | Read selected text via simulated Cmd+C | On-demand only |
| Network (Outgoing) | FHIR API queries | HTTPS only |
- No persistent storage of user data
- Clipboard restoration after reading
- HTTPS-only network communication
- No telemetry or analytics
- App Sandbox disabled — Required for Accessibility API access to other processes
- Selected text is only read when the user explicitly triggers a lookup
- Concept IDs are sent to the FHIR server (no personal data)
- Cache is cleared on app termination
| Framework | Usage |
|---|---|
| SwiftUI | User interface |
| AppKit/Cocoa | Menu bar, pasteboard, windows |
| Carbon | Global hotkey registration |
| WebKit | WKWebView for ECL Workbench (Monaco editor) and concept visualization |
| Foundation | Networking, data, utilities |
| Combine | Reactive updates for settings |
| os.log | Structured logging |
| Service | Purpose | Endpoint |
|---|---|---|
| CSIRO Ontoserver | FHIR terminology server | https://tx.ontoserver.csiro.au/fhir |
Tests are organized into three tiers using Xcode Test Plans, each with increasing scope and external dependencies:
| Tier | Test Plan | Scope | Dependencies |
|---|---|---|---|
| 1 — Unit | Codeagogo-Unit |
Pure logic, data models, parsers | None (headless) |
| 2 — Integration | Codeagogo-Integration |
Real FHIR server calls | Network + Ontoserver |
| 3 — UI | Codeagogo-UI |
XCUITest for Settings/MenuBar | GUI session |
Protocol-based dependency injection enables isolated testing of view models:
protocol SelectionReading { ... }
protocol ConceptLookupClient { ... }
protocol ConceptSearching: Sendable { ... }
// Production: uses real implementations
LookupViewModel()
// Tests: inject mocks
LookupViewModel(selectionReader: MockReader(), client: MockClient())Actor-based caching uses TestableConceptCache to expose internal state for assertions without breaking encapsulation in production.
ViewInspector (MIT, test target only) provides headless SwiftUI view testing for SettingsView and other views without @MainActor ObservableObject dependencies.
CodeagogoTests/
├── ConceptIdExtractionTests.swift # Regex extraction, SCTID validation
├── ConceptCacheTests.swift # TTL, LRU eviction, thread safety
├── OntoserverClientTests.swift # FHIR parsing, errors, retry, multi-system
├── ECLBridgeTests.swift # ECL bridge (ecl-core via JSC), hotkey settings
├── EvaluateTests.swift # ECL evaluation, settings, hotkey configuration
├── DiagramRendererUnitTests.swift # SVG generation, normal form rendering
├── LookupViewModelTests.swift # ID extraction, lookup coordination
├── VisualizationViewModelTests.swift # Property loading, state management
├── VisualizationModelsTests.swift # Visualization data models
├── SNOMEDExpressionParserTests.swift # Compositional grammar parsing
├── HotKeySettingsTests.swift # Hotkey configuration
├── FHIROptionsTests.swift # FHIR endpoint configuration
├── InstallMetricsTests.swift # Install tracking
├── ShrimpURLBuilderTests.swift # URL construction
├── ConceptResultTests.swift # ConceptResult model
├── ViewTests/ # View data model tests
│ ├── PopoverViewTests.swift # Lookup result display models
│ ├── SearchPanelViewTests.swift # Search result models
│ ├── SettingsViewTests.swift # SwiftUI view inspection
│ └── VisualizationPanelViewTests.swift # Visualization data models
└── ...
A Swift concurrency back-deploy runtime issue causes malloc crashes when LookupViewModel, SearchViewModel, VisualizationViewModel, or EvaluateViewModel are instantiated in new test source files. This is triggered by swift_task_deinitOnExecutorMainActorBackDeploy when the binary memory layout changes with new compilation units. Existing test files (e.g., LookupViewModelTests.swift) are unaffected. New view tests should test data models directly rather than instantiating these view models.
macOS does not provide a modern API for global keyboard shortcuts. The Carbon RegisterEventHotKey API remains the only supported way to register system-wide hotkeys that work in any application.
macOS restricts direct access to selected text across applications. The Accessibility API allows simulating keyboard events, making Cmd+C the most reliable cross-application method for capturing selections.
FHIR provides:
- Standardized response format
- Multi-edition support in a single endpoint
- Broader compatibility with terminology servers
- Better long-term maintainability
Swift actors provide:
- Compile-time thread safety guarantees
- No manual locking required
- Natural async/await integration
- Clear isolation boundaries
The LookupViewModel accepts optional dependencies:
init(selectionReader: SelectionReading? = nil,
client: ConceptLookupClient? = nil)This allows:
- Default production implementations
- Easy mock injection for testing
- No breaking changes to existing code