Skip to main content

presolve_compiler/
lib.rs

1//! Core compiler data structures for the first `Presolve` learning slice.
2//!
3//! This crate deliberately does **not** parse TSX yet. It records a source summary,
4//! spans, obvious declarations, and diagnostics. That gives the project a stable
5//! place to learn compiler fundamentals before choosing a real parser backend.
6
7pub mod action_authority;
8pub mod action_field_lowering;
9pub mod application_publication;
10pub mod application_semantic_model;
11pub mod asm_validation;
12pub mod authored_semantics;
13pub mod binding_table;
14pub mod capability_projection;
15pub mod capture_escape;
16pub mod codec_protocol;
17pub mod compilation_unit;
18pub mod compiler_pass;
19pub mod component_composition;
20pub mod component_diagnostics;
21pub mod component_graph;
22pub mod component_inheritance_lowering;
23pub mod component_initialization;
24pub mod component_instance;
25pub mod component_instance_scope;
26pub mod component_invocation;
27pub mod component_ir;
28pub mod component_ir_optimization;
29pub mod component_scope;
30pub mod composition_typing;
31pub mod computed_getter_lowering;
32pub mod computed_instance_slots;
33pub mod computed_projection;
34pub mod computed_value;
35pub mod consumer;
36pub mod context;
37pub mod context_declaration_candidate;
38pub mod context_dependency;
39mod context_designator;
40pub mod context_diagnostics;
41pub mod context_evaluation;
42pub mod context_inspection;
43pub mod context_lifetime;
44pub mod context_ownership;
45pub mod context_projection;
46pub mod context_resolution;
47pub mod context_resume;
48pub mod context_resume_projection;
49pub mod context_typing;
50pub mod context_update;
51pub mod control_flow;
52pub mod document_template;
53pub mod effect;
54pub mod effect_capability;
55pub mod effect_diagnostics;
56pub mod effect_field_lowering;
57pub mod effect_inspection;
58pub mod effect_projection;
59pub mod effect_resume;
60pub mod environment_input;
61pub mod environment_ownership;
62pub mod environment_publication;
63pub mod environment_read_lowering;
64pub mod environment_read_ownership;
65pub mod explain;
66pub mod expression_graph;
67pub mod file_route_publication;
68pub mod form;
69pub mod form_binding;
70pub mod form_definition_lowering;
71pub mod form_diagnostics;
72pub mod form_field;
73pub mod form_field_definition_lowering;
74pub mod form_inspection;
75pub mod form_ir;
76pub mod form_ir_optimization;
77pub mod form_ownership;
78pub mod form_projection;
79pub mod form_reset;
80pub mod form_serialization;
81pub mod form_submission;
82pub mod form_submission_host;
83pub mod form_submission_projection;
84pub mod form_tracking;
85pub mod form_validation;
86pub mod form_validation_definition_lowering;
87pub mod form_validation_plan;
88pub mod function_summary;
89pub mod html_codegen;
90pub mod incremental_projection;
91pub mod instance_context;
92pub mod intermediate_representation;
93#[cfg(any(test, all(feature = "wasm", target_arch = "wasm32")))]
94mod language_service;
95pub mod layout_composition;
96pub mod layout_graph;
97pub mod lazy_action_chunks;
98pub mod legacy_decorator_lowering;
99pub mod metaframework_handoff;
100pub mod model;
101pub mod module_graph;
102pub mod opaque;
103pub mod ordinary_html_codegen;
104pub mod ordinary_template_instance;
105pub mod ordinary_template_integrity;
106pub mod page_codegen;
107pub mod persistent_cache;
108pub mod platform;
109pub mod production_audit;
110pub mod production_bootstrap;
111pub mod production_chunk_graph;
112pub mod production_cleanup;
113pub mod production_constant_pool;
114pub mod production_deduplication;
115pub mod production_diagnostics;
116pub mod production_elimination;
117pub mod production_module_emitter;
118pub mod production_optimization;
119pub mod production_reachability;
120pub mod production_reports;
121pub mod production_runtime_artifact;
122pub mod production_scheduler;
123pub mod production_validation;
124pub mod project_discovery;
125pub mod provider;
126pub mod purity_effect;
127pub mod resource;
128pub mod resource_projection;
129pub mod resume_activation;
130pub mod resume_anchor;
131pub mod resume_boot;
132pub mod resume_boundary;
133pub mod resume_capture;
134pub mod resume_chunk;
135pub mod resume_diagnostics;
136pub mod resume_explain;
137pub mod resume_identity;
138pub mod resume_instance;
139pub mod resume_liveness;
140pub mod resume_manifest;
141pub mod resume_plan;
142pub mod resume_restore;
143pub mod resume_schema;
144pub mod route_graph;
145pub mod route_loader;
146pub mod route_loader_projection;
147pub mod route_metadata;
148pub mod route_server_action;
149pub mod runtime_codegen;
150pub mod runtime_component;
151pub mod runtime_component_artifact;
152pub mod runtime_computed;
153pub mod runtime_computed_artifact;
154pub mod runtime_context;
155pub mod runtime_context_artifact;
156pub mod runtime_effect;
157pub mod runtime_effect_artifact;
158pub mod runtime_effect_instance;
159pub mod runtime_form_artifact;
160pub mod runtime_form_registry;
161pub mod runtime_opaque_artifact;
162pub mod runtime_resource_artifact;
163pub mod semantic_capability;
164pub mod semantic_graph;
165pub mod semantic_id;
166pub mod semantic_package;
167pub mod semantic_package_runtime;
168pub mod semantic_provenance;
169pub mod semantic_reference;
170pub mod semantic_type;
171pub mod server_action_projection;
172pub mod service;
173pub mod shared_chunk_candidate;
174pub mod slot;
175pub mod slot_binding;
176pub mod slot_content;
177pub mod slot_field_lowering;
178pub mod slot_projection;
179pub mod state_initializer_lowering;
180pub mod state_instance_storage;
181pub mod state_projection;
182pub mod structural_component;
183pub mod structural_occurrence_identity;
184pub mod summarize;
185pub mod symbol_table;
186pub mod template_graph;
187pub mod template_manifest;
188pub mod template_semantics;
189pub mod tooling_products;
190pub mod tooling_reader;
191pub mod tooling_schema;
192pub mod tsx_binding_projection;
193pub mod v2_authoring_lowering;
194pub mod v2_authority_request;
195pub mod v2_authority_response;
196pub mod validation_projection;
197pub use tooling_products::{
198    build_tooling_artifact_graph_v1, build_tooling_build_trace_v1,
199    build_tooling_compile_cost_report_v1, decode_tooling_artifact_graph_v1,
200    decode_tooling_build_trace_v1, decode_tooling_compile_cost_report_v1,
201    decode_tooling_query_snapshot_v1, tooling_artifact_graph_json_v1, tooling_build_trace_json_v1,
202    tooling_compile_cost_report_json_v1, tooling_query_snapshot_json_v1, ToolingArtifactGraphV1,
203    ToolingBuildTraceStageV1, ToolingBuildTraceV1, ToolingCompileCostReportV1,
204    ToolingProductValidationErrorV1, ToolingQueryDiagnosticSecondaryV1,
205    ToolingQueryDiagnosticSeverityV1, ToolingQueryDiagnosticV1, ToolingQueryRangeV1,
206    ToolingQueryReferenceKindV1, ToolingQueryReferenceV1, ToolingQuerySemanticKindV1,
207    ToolingQuerySemanticRecordV1, ToolingQuerySnapshotSourceUnitV1, ToolingQuerySnapshotV1,
208    ToolingTraceIdentityV1, ToolingTraceOutcomeV1, ToolingTraceStageKindV1,
209    ARTIFACT_GRAPH_TOOLING_SCHEMA_V1, BUILD_TRACE_TOOLING_SCHEMA_V1,
210    COMPILE_COST_TOOLING_SCHEMA_V1, QUERY_SNAPSHOT_TOOLING_SCHEMA_V1,
211};
212pub mod watch;
213pub mod workspace;
214
215#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
216mod wasm_binding;
217
218pub use action_authority::{
219    build_action_authority_v1, ActionAdmissionV1, ActionAuthorityV1, ActionCaptureCoverageV1,
220    ActionEnvironmentV1, ActionFactV1, ActionRecordV1, ACTION_AUTHORITY_SCHEMA_VERSION,
221};
222pub use action_field_lowering::{
223    action_field_sites_v1, lower_action_fields_v1, ActionFieldLoweringErrorV1,
224    ActionFieldLoweringV1, ActionFieldSiteV1, ResolvedActionFieldV1,
225};
226pub use application_publication::{
227    application_publication_manifest_json_v1, build_application_publication_product_from_asm_v1,
228    build_application_publication_product_v1, validate_application_publication_request_v1,
229    ApplicationPublicationArtifactV1, ApplicationPublicationErrorV1,
230    ApplicationPublicationManifestV1, ApplicationPublicationProductV1,
231    ApplicationPublicationProfileV1, ApplicationPublicationRequestErrorV1,
232    ApplicationPublicationRequestV1, ApplicationPublicationSourceV1,
233    ValidatedApplicationPublicationRequestV1, APPLICATION_PUBLICATION_COMPILER_CONTRACT_V1,
234    APPLICATION_PUBLICATION_MANIFEST_SCHEMA_VERSION,
235};
236pub use application_semantic_model::{
237    build_application_semantic_model, build_application_semantic_model_for_unit,
238    build_application_semantic_model_for_unit_with_packages,
239    build_application_semantic_model_from_component_graph,
240    build_file_route_application_semantic_model_for_route_with_packages,
241    build_file_route_application_semantic_model_for_route_with_packages_and_v2_authoring,
242    build_file_route_application_semantic_model_for_unit_with_packages,
243    build_file_route_application_semantic_model_for_unit_with_packages_and_v2_authoring,
244    ApplicationSemanticModel, FileRouteApplicationModelErrorV1, SemanticEntity, SemanticEntityKind,
245};
246pub use asm_validation::{validate_application_semantic_model, AsmValidationDiagnostic};
247pub use authored_semantics::{
248    compose_authored_semantics_v1, normalize_authored_semantics_v1,
249    AuthoredSemanticCandidateKindV1, AuthoredSemanticCompositionErrorV1,
250    AuthoredSemanticNormalizationErrorV1, AuthoredSourceRangeV1,
251    CanonicalAuthoredDeclarationKindV1, CanonicalAuthoredDeclarationV1,
252    CanonicalAuthoredSemanticModelV1, CanonicalIntrinsicKindV1, DerivedAuthoredEvidenceV2,
253    ResolvedAuthoredSemanticCandidateV1, ResolvedIntrinsicIdentityV1,
254    CANONICAL_AUTHORED_SEMANTICS_SCHEMA_VERSION,
255};
256pub use binding_table::{
257    build_binding_table, build_binding_table_with_packages, BindingDiagnostic, BindingTable,
258    ExportBinding, ImportBinding, ImportBindingTarget, ModuleBindingTable,
259};
260pub use capture_escape::{
261    build_capture_escape_graph_v1, CaptureEscapeCoverageV1, CaptureEscapeErrorV1,
262    CaptureEscapeFactV1, CaptureEscapeFactsV1, CaptureEscapeGraphV1, CaptureEscapeKindV1,
263    FunctionCaptureEscapeV1, ResumeCaptureAdmissionV1, CAPTURE_ESCAPE_SCHEMA_VERSION,
264};
265pub use codec_protocol::{
266    build_codec_protocol_v1, CodecBehaviorV1, CodecClassificationV1, CodecClassificationsV1,
267    CodecDeclarationV1, CodecEnvironmentV1, CodecFailureBehaviorV1,
268    CodecProtocolDiagnosticReasonV1, CodecProtocolDiagnosticV1, CodecProtocolRecordV1,
269    CodecProtocolV1, CodecRepresentationV1, CODEC_PROTOCOL_SCHEMA_VERSION,
270};
271pub use compilation_unit::CompilationUnit;
272pub use compiler_pass::{
273    fold_component_graph, AnalysisPass, ConstantEvaluation, ConstantEvaluationPass,
274    ConstantFoldingPass, DependencyAnalysis, DependencyAnalysisPass, ImmutableAsmPass,
275};
276pub use component_composition::{
277    analyze_component_composition, ComponentCompositionAnalysis, ComponentCompositionCycle,
278};
279pub use component_diagnostics::{
280    collect_component_diagnostics, ComponentDiagnosticContract, COMPONENT_DIAGNOSTIC_CONTRACTS,
281};
282pub use component_graph::{
283    build_component_graph, build_component_graph_for_module, build_v2_component_graph_for_module,
284    is_valid_opaque_action_fact, ActionEndpoint, ArithmeticEvaluationError, ArithmeticExpression,
285    ArithmeticExpressionKind, ArithmeticOperator, AuthoredComponentHeritage,
286    AuthoredContextDeclarationCandidate, AuthoredDeclarationKind, AuthoredOpaqueActionFact,
287    AuthoredRouteLoaderDeclarationFact, AuthoredServerActionFact, AuthoredSlotDeclarationCandidate,
288    AuthoredStandardSchemaValidationFact, AuthoredSubmissionDeclarationFact,
289    AuthoredValidationRuleArgument, AuthoredValidationRuleArgumentKind,
290    AuthoredValidationRuleDeclarationFact, AuthoredValidationRuleExpression,
291    AuthoredValidationRuleExpressionKind, ComparisonOperator, ComponentAction, ComponentDiagnostic,
292    ComponentDiagnosticSeverity, ComponentEffectField, ComponentGraph, ComponentMethod,
293    ComponentNode, ComputedExpression, ComputedExpressionKind, ConstantEvaluationError,
294    ConstantExpression, ConstantExpressionKind, ConsumerDeclaration, ContextDeclaration,
295    ContextDeclarationCandidateKind, ContextDeclarationViolation, ContextDesignator,
296    DeclaredStateType, DeclaredStateTypeKind, DiagnosticSecondaryLabel, EffectBodySyntax,
297    EffectCleanupSyntax, EffectExpression, EffectExpressionKind, EffectStatementSyntax,
298    EffectStatementSyntaxKind, FormDeclarationCandidate, FormDeclarationStatus,
299    FormDeclarationViolation, FormDesignatorFact, FormFieldDeclarationCandidate,
300    FormFieldDeclarationViolation, LogicalOperator, MethodCall, MethodLocalVariable,
301    MethodParameter, RenderAttribute, RenderAttributeValue, RenderChild, RenderEventHandler,
302    RenderFragment, RenderList, RenderModel, SerializableValue, SlotDeclaration,
303    SlotDeclarationViolation, SlotKind, StateField, StateOperation, UnsupportedEffectStatementKind,
304    UnsupportedFormDesignatorFact,
305};
306pub use component_inheritance_lowering::{
307    component_inheritance_sites_v1, lower_component_inheritance_v1,
308    ComponentInheritanceLoweringErrorV1, ComponentInheritanceLoweringV1,
309    ComponentInheritanceSiteV1, ResolvedComponentInheritanceV1,
310};
311pub use component_initialization::{
312    plan_component_initialization, ComponentInitializationPlan, ComponentInstanceBatch,
313    SlotBindingBatch,
314};
315pub use component_instance::{
316    plan_component_instances, plan_component_instances_with_virtual_invocations,
317    structural_template_entity_for_region, BlockedComponentInstancePlan,
318    BlockedComponentInstanceReason, ComponentBuildRoot, ComponentBuildRootKind, ComponentInstance,
319    ComponentInstancePlan, ComponentInstanceStatus,
320};
321pub use component_instance_scope::{
322    build_component_instance_scope_graph, validate_component_instance_scope_graph,
323    ComponentInstanceScopeDiagnostic, ComponentInstanceScopeGraph, ComponentInstanceScopeNode,
324    ComponentInstanceScopeViolation,
325};
326pub use component_invocation::{
327    collect_component_invocations, ComponentInvocationEntity, ComponentInvocationResolutionStatus,
328};
329pub use component_ir::{
330    lower_component_ir, validate_component_ir, ComponentIrInstruction, ComponentIrOperation,
331    ComponentIrReport,
332};
333pub use component_ir_optimization::{
334    optimize_component_ir, validate_optimized_component_ir, OptimizedComponentIrReport,
335};
336pub use component_scope::{ComponentScopeDiagnostic, ComponentScopeGraph};
337pub use composition_typing::{
338    collect_composition_type_products, ComponentInvocationTypeRecord, CompositionCompatibility,
339    CompositionTypeProducts, InstanceContextBindingTypeRecord, SlotBindingTypeRecord,
340};
341pub use computed_getter_lowering::{
342    computed_getter_sites_v1, lower_computed_getters_v1, ComputedGetterLoweringErrorV1,
343    ComputedGetterLoweringV1, ComputedGetterSiteV1,
344};
345pub use computed_instance_slots::{
346    build_computed_instance_slot_registry, validate_computed_instance_slot_registry,
347    ComputedInstanceSlotRecord, ComputedInstanceSlotRegistry,
348    COMPUTED_INSTANCE_SLOT_REGISTRY_VERSION,
349};
350pub use computed_projection::{
351    build_computed_projection_v1, ComputedProjectionRecordV1, ComputedProjectionV1,
352    COMPUTED_PROJECTION_SCHEMA_VERSION,
353};
354pub use computed_value::{
355    collect_computed_values, ComputedCachePolicy, ComputedDiagnosticCode, ComputedPurity,
356    ComputedPurityViolation, ComputedPurityViolationKind, ComputedValue,
357};
358pub use consumer::{collect_consumer_entities, ConsumerEntity, ContextResolutionState};
359pub use context::{collect_context_entities, ContextEntity};
360pub use context_declaration_candidate::{
361    collect_context_declaration_candidates, ContextDeclarationCandidate,
362    ContextDeclarationCandidateRegistry, ContextDeclarationStatus, ContextSemanticEntityId,
363};
364pub use context_dependency::{
365    collect_context_dependency_graph, ContextDependencyCompatibility, ContextDependencyEdge,
366    ContextDependencyEdgeKind, ContextDependencyGraph, ContextDependencyNode,
367    ContextDependencyNodeId, ContextDependencyNodeKind,
368};
369pub use context_diagnostics::collect_context_diagnostics;
370pub use context_evaluation::{
371    collect_context_evaluation_plan, ContextConsumerAvailabilityEntry,
372    ContextConsumerAvailabilityStatus, ContextEvaluationBatch, ContextEvaluationBatchId,
373    ContextEvaluationPlan, ContextEvaluationPlanId, ContextSourceBlockReason,
374    ContextSourcePlanEntry, ContextSourcePlanStatus, ContextValueSourceId,
375};
376pub use context_inspection::{
377    build_context_inspection_registry, ContextInspection, ContextInspectionRegistry,
378};
379pub use context_lifetime::{
380    collect_context_lifetime_analysis, ContextBindingLifetimeRecord, ContextBindingLifetimeSource,
381    ContextBindingLifetimeStatus, ContextDefaultLifetimeRecord, ContextDependencyLifetimeRecord,
382    ContextEntityLifetimeRecord, ContextLifetimeAnalysis, ContextLifetimeEntityId,
383    ContextLifetimeId, LifetimeCompatibilityStatus, ProviderLifetimeRecord,
384};
385pub use context_ownership::{
386    collect_context_ownership_graph, ContextOwnedEntities, ContextOwnershipEdge,
387    ContextOwnershipEdgeKind, ContextOwnershipGraph, ContextOwnershipNode, ContextOwnershipNodeId,
388    ContextOwnershipNodeKind, ContextOwnershipOwnerId, ContextOwnershipTargetId,
389};
390pub use context_projection::{
391    build_context_projection_v1, ContextProjectionV1, ContextProviderRecordV1,
392    ContextTokenRecordV1, CONTEXT_PROJECTION_SCHEMA_VERSION,
393};
394pub use context_resolution::{
395    collect_context_resolutions, ContextResolution, ContextResolutionResult,
396};
397pub use context_resume::{
398    build_context_resume_plan, ContextResumePlan, ContextResumeRecord, ContextResumeSlotId,
399    ContextSlotResumeStatus,
400};
401pub use context_resume_projection::{
402    build_context_resume_projection_v1, ContextConsumerResolutionV1,
403    ContextResumeProjectionRecordV1, ContextResumeProjectionV1,
404    CONTEXT_RESUME_PROJECTION_SCHEMA_VERSION,
405};
406pub use context_typing::{
407    collect_context_type_products, CompatibilityStatus, ConsumerTypeRecord,
408    ContextBindingCompatibility, ContextBindingTypeRecord, ContextSerializationCompatibility,
409    ContextTypeProducts, ContextTypeRecord, ProviderTypeRecord,
410};
411pub use context_update::{build_context_update_plan, ContextActionUpdatePlan, ContextUpdatePlan};
412pub use control_flow::{
413    build_control_flow_graph_v1, ControlFlowAccessKindV1, ControlFlowAccessV1, ControlFlowBlockV1,
414    ControlFlowBranchArmV1, ControlFlowBranchEdgeV1, ControlFlowCoverageStatusV1,
415    ControlFlowCoverageV1, ControlFlowFunctionV1, ControlFlowGraphV1, ControlFlowLoopV1,
416    ControlFlowProvenanceV1, CONTROL_FLOW_SCHEMA_VERSION,
417};
418pub use document_template::{apply_document_template_v1, DocumentTemplateErrorV1};
419pub use effect::{
420    analyze_effect_reactivity, collect_effects, derive_effect_trigger_plan, lower_effect_bodies,
421    plan_effect_execution, validate_effects, ActionBatch, ActionBatchEffectTrigger,
422    ActionEffectExecutionPlan, Effect, EffectBody, EffectComputedPrerequisiteBatch,
423    EffectDeclaration, EffectExecutionBatch, EffectExecutionPlan, EffectExecutionPolicy,
424    EffectReactiveAnalysis, EffectRenderBoundary, EffectSemanticViolation,
425    EffectSemanticViolationKind, EffectStatement, EffectStatementKind, EffectTriggerPlan,
426    EffectValidation, InitialEffectExecutionPlan, UnplannedEffect, UnplannedEffectReason,
427};
428pub use effect_capability::{
429    ArgumentSerializationPolicy, BuiltinCapabilityProvenance, CapabilityDefinition, CapabilityId,
430    CapabilityOperation, CapabilityOperationId, CapabilityOperationKind, CapabilityParameters,
431    CapabilityResultPolicy, CapabilitySignature, CapabilityValueContract, EffectCapabilityRegistry,
432    RuntimeCapabilityLowering, StaticCapabilityPath, EFFECT_CAPABILITY_REGISTRY,
433    EFFECT_CAPABILITY_REGISTRY_VERSION,
434};
435pub use effect_diagnostics::{collect_effect_diagnostics, EffectDiagnosticCode};
436pub use effect_field_lowering::{
437    effect_field_sites_v1, lower_effect_fields_v1, EffectFieldLoweringErrorV1,
438    EffectFieldLoweringV1, EffectFieldSiteV1, ResolvedEffectFieldV1,
439};
440pub use effect_inspection::{
441    build_effect_inspection_registry, validate_effect_inspection_registry, EffectInspection,
442    EffectInspectionActionTrigger, EffectInspectionCapability, EffectInspectionDependencies,
443    EffectInspectionInitialTrigger, EffectInspectionIr, EffectInspectionPrerequisiteBatch,
444    EffectInspectionProvenance, EffectInspectionRegistry, EffectInspectionResumability,
445    EffectInspectionRuntime, EffectInspectionSchedule, EffectInspectionScheduledAction,
446    EffectInspectionUnplanned, EffectInspectionValidation, EffectInspectionValidationDiagnostic,
447    EffectInspectionViolation,
448};
449pub use effect_projection::{
450    build_effect_projection_v1, EffectProjectionRecordV1, EffectProjectionV1,
451    EFFECT_PROJECTION_SCHEMA_VERSION,
452};
453pub use effect_resume::{
454    build_effect_resume_plan, validate_effect_resume_plan, EffectActivationSlotId,
455    EffectActivationStatus, EffectInitialResumeMembership, EffectResumePlan, EffectResumeRecord,
456    EffectResumeValidationDiagnostic,
457};
458pub use environment_input::{
459    build_environment_input_manifest_v1, environment_input_manifest_from_json_v1,
460    environment_input_manifest_json_v1, validate_environment_input_manifest_v1,
461    EnvironmentInputErrorV1, EnvironmentInputManifestV1, ENVIRONMENT_INPUT_SCHEMA_VERSION,
462};
463pub use environment_ownership::{
464    build_environment_ownership_graph_v1, EnvironmentClassV1, EnvironmentOwnershipDiagnosticV1,
465    EnvironmentOwnershipEdgeKindV1, EnvironmentOwnershipEdgeV1, EnvironmentOwnershipErrorV1,
466    EnvironmentOwnershipFactsV1, EnvironmentOwnershipGraphV1, EnvironmentOwnershipNodeV1,
467    EnvironmentOwnershipViolationV1, LifetimeClassV1, ENVIRONMENT_OWNERSHIP_SCHEMA_VERSION,
468};
469pub use environment_publication::{
470    build_environment_publication_artifact_v1, environment_publication_artifact_json_v1,
471    validate_environment_publication_artifact_v1, EnvironmentPublicationArtifactV1,
472    EnvironmentPublicationErrorV1, ENVIRONMENT_PUBLICATION_SCHEMA_VERSION,
473};
474pub use environment_read_lowering::{
475    lower_environment_reads_v1, EnvironmentReadDiagnosticCodeV1, EnvironmentReadDiagnosticV1,
476    EnvironmentReadLoweringV1, EnvironmentReadRecordV1, ENVIRONMENT_READ_LOWERING_SCHEMA_VERSION,
477};
478pub use environment_read_ownership::{
479    build_environment_read_ownership_v1, EnvironmentReadOwnershipErrorV1,
480};
481pub use explain::{explain_json, explain_text};
482pub use expression_graph::{ExpressionGraph, ExpressionNode, ExpressionNodeKind};
483pub use file_route_publication::{
484    build_file_route_publication_v1, file_route_artifact_root_v1,
485    file_route_publication_manifest_json_v1, resolve_file_route_request_match_v1,
486    resolve_file_route_request_v1, FileRoutePublicationErrorV1, FileRoutePublicationManifestV1,
487    FileRoutePublicationProductV1, FileRoutePublicationRequestV1, FileRoutePublicationRouteV1,
488    FileRouteRequestMatchV1, FileRouteRequestTargetV1, FILE_ROUTE_ENVIRONMENT_ARTIFACT_PATH_V1,
489    FILE_ROUTE_PUBLICATION_COMPILER_CONTRACT_V1, FILE_ROUTE_PUBLICATION_MANIFEST_SCHEMA_VERSION,
490};
491pub use form::{collect_form_entities, FormEntity};
492pub use form_binding::{
493    collect_form_field_binding_products, FormControlChannel, FormControlCompatibility,
494    FormControlNormalization, FormFieldBinding, FormFieldBindingCandidate,
495    FormFieldBindingEvidence, FormFieldBindingEvidenceKind, FormFieldBindingExpressionFact,
496    FormFieldBindingProducts, FormFieldBindingViolation, FormInputKind,
497};
498pub use form_definition_lowering::{
499    form_definition_sites_v1, lower_form_definitions_v1, FormDefinitionLoweringErrorV1,
500    FormDefinitionLoweringV1, FormDefinitionSiteV1, ResolvedFormDefinitionV1,
501};
502pub use form_diagnostics::{
503    collect_form_diagnostics, FormDiagnosticReservation, FORM_DIAGNOSTIC_RESERVATIONS,
504};
505pub use form_field::{collect_form_field_products, FormFieldEntity, FormFieldProducts};
506pub use form_field_definition_lowering::{
507    form_field_definition_sites_v1, lower_form_field_definitions_v1,
508    FormFieldDefinitionLoweringErrorV1, FormFieldDefinitionLoweringV1, FormFieldDefinitionSiteV1,
509    ResolvedFormFieldDefinitionV1, ResolvedFormFieldValueClassificationV1,
510};
511pub use form_inspection::{build_form_inspection_registry, FormInspection, FormInspectionRegistry};
512pub use form_ir::{
513    lower_form_ir, FormInstanceIr, FormIrOperation, FormIrReport, FormRuntimeStorage,
514};
515pub use form_ir_optimization::{
516    optimize_form_ir, FormIrOptimizationMetrics, OptimizedFormIrReport,
517};
518pub use form_ownership::{
519    collect_form_ownership_graph, validate_form_ownership_graph, FormOwnershipEdge,
520    FormOwnershipEdgeKind, FormOwnershipGraph, FormOwnershipIntegrityDiagnostic,
521    FormOwnershipIntegrityKind, FormOwnershipNode, FormOwnershipNodeKey, FormOwnershipValidation,
522    FormReferenceEdge, FormReferenceKind,
523};
524pub use form_reset::{
525    collect_reset_products, FieldResetOperation, FieldResetStep, FormResetPlan, ResetProducts,
526};
527pub use form_serialization::{
528    collect_serialization_products, FormFieldSerializationConversion, FormSerializationFormat,
529    FormSerializationPlan, SerializationDeclarationFact, SerializationPlanStatus,
530    SerializationProducts, SerializedFieldPlan,
531};
532pub use form_submission::{
533    collect_submission_products, FormSubmissionCapability, FormSubmissionPlan,
534    SubmissionDeclarationCandidate, SubmissionDeclarationViolation, SubmissionProducts,
535    SubmitResetPolicy,
536};
537pub use form_submission_host::{
538    collect_submission_host_products, SubmissionHost, SubmissionHostCandidate,
539    SubmissionHostProducts, SubmissionHostViolation,
540};
541pub use form_tracking::{
542    collect_form_tracking_products, structurally_equal_serializable_values,
543    validate_dirty_tracking_graph, validate_touched_tracking_graph, DirtyTrackingGraph,
544    DirtyTrackingPlan, DirtyTransitionCause, DirtyTransitionPlan, FieldDirtyTracking,
545    FieldTouchedTracking, FormTrackingIntegrityDiagnostic, FormTrackingIntegrityKind,
546    FormTrackingProducts, FormTrackingValidation, TouchedTrackingGraph, TouchedTrackingPlan,
547    TouchedTransitionCause, TouchedTransitionPlan,
548};
549pub use form_validation::{
550    collect_validation_graph, collect_validation_products, validate_validation_graph,
551    ValidationCompatibility, ValidationDependencyCycle, ValidationDependencyDesignator,
552    ValidationGraph, ValidationGraphEdge, ValidationGraphEdgeKind,
553    ValidationGraphIntegrityDiagnostic, ValidationGraphIntegrityKind, ValidationGraphNode,
554    ValidationGraphNodeKey, ValidationGraphValidation, ValidationProducts, ValidationRule,
555    ValidationRuleArgument, ValidationRuleCandidate, ValidationRuleKind, ValidationRuleViolation,
556};
557pub use form_validation_definition_lowering::{
558    form_validation_definition_sites_v1, lower_form_validation_definitions_v1,
559    FormValidationDefinitionLoweringErrorV1, FormValidationDefinitionSiteV1,
560    ResolvedFormValidationDefinitionKindV1, ResolvedFormValidationDefinitionV1,
561};
562pub use form_validation_plan::{
563    collect_validation_dependency_plans, validate_validation_dependency_plans,
564    BlockedFieldValidationDependency, FieldChangeSet, FieldChangeValidationSchedule,
565    FieldDependencyBlockReason, FieldValidationChangePlan, FieldValidationDependency,
566    FieldValidationSourceEntry, FieldValidationTargetEntry, FormValidationDependencyPlan,
567    ValidationDependencyPlanIntegrityDiagnostic, ValidationDependencyPlanIntegrityKind,
568    ValidationDependencyPlanValidation, ValidationDependencyPlans, ValidationPlanningStatus,
569};
570pub use function_summary::{
571    build_function_summary_graph_v1, FunctionCallCoverageV1, FunctionCallFactV1,
572    FunctionCallFactsV1, FunctionSummaryErrorV1, FunctionSummaryGraphV1, FunctionSummaryV1,
573    FUNCTION_SUMMARY_SCHEMA_VERSION,
574};
575pub use html_codegen::generate_static_html;
576pub use instance_context::{
577    collect_instance_context_registry, ConsumerInstanceId, ConsumerInstanceRecord,
578    ContextDefaultSourceInstanceId, ContextSourceInstanceId, ContextSourceInstanceOwner,
579    InstanceContextRegistry, InstanceContextResolution, InstanceContextResolutionStatus,
580    InstanceContextValueSlotId, ProviderInstanceId, ProviderInstanceRecord,
581};
582pub use intermediate_representation::{
583    analyze_constant_propagation, analyze_dead_assignments, analyze_definition_uses,
584    analyze_liveness, analyze_reachability, analyze_reactive_cycles,
585    analyze_reactive_transitive_graph, analyze_use_definitions, build_reactive_graph,
586    compute_dominators, compute_post_dominators, computed_optimization_pipeline, inspect_dom_nodes,
587    lower_components_to_ir, optimize_computed_ir, optimize_context_ir, optimize_effect_ir,
588    plan_computed_evaluation, validate_context_ir, validate_effect_ir,
589    validate_intermediate_representation, validate_optimized_context_ir, ContextConsumerLoadId,
590    ContextIrReport, ContextSourceFunctionId, ContextValueSlotId, IntermediateRepresentation,
591    IrBinaryOperation, IrBlock, IrBlockId, IrBranchArm, IrBranchEdge, IrCfgCleanupPass,
592    IrCommonSubexpressionEliminationPass, IrComputedEvaluation, IrComputedEvaluationPlan,
593    IrConstant, IrConstantFoldingPass, IrConstantPropagationAnalysis, IrContextConsumerBinding,
594    IrContextLoad, IrContextSourceEvaluation, IrCopyPropagationPass, IrDeadAssignmentAnalysis,
595    IrDeadCodeEliminationPass, IrDefinitionUseAnalysis, IrDomAttribute, IrDomAttributeValue,
596    IrDomBinding, IrDomConditional, IrDomEvent, IrDomInspection, IrDomList, IrDomNode, IrDomNodeId,
597    IrDomNodeKind, IrDomText, IrDominatorTree, IrEffectCompletion, IrEffectExecution, IrFunction,
598    IrInstruction, IrInstructionId, IrInstructionKind, IrInstructionSimplificationPass,
599    IrLivenessAnalysis, IrLoop, IrLoopId, IrModule, IrOperand, IrOptimizationMetrics,
600    IrOptimizationPass, IrOptimizationPassReport, IrOptimizationPipeline, IrOptimizationReport,
601    IrPassManager, IrPostDominatorTree, IrReachabilityAnalysis, IrReactiveCycle,
602    IrReactiveCycleAnalysis, IrReactiveEdge, IrReactiveEdgeKind, IrReactiveGraph, IrReactiveNode,
603    IrReactiveNodeKind, IrReactiveTransitiveAnalysis, IrSchedulerInspection, IrStorage,
604    IrStorageId, IrTemplateEntrypoint, IrUnaryOperation, IrUpdateScheduler, IrUse, IrUseDefinition,
605    IrValidationDiagnostic, IrValue, IrValueDefinition, IrValueId, OptimizedContextIrReport,
606    OptimizedIrContextSourceEvaluation,
607};
608pub use layout_composition::{
609    build_layout_composition_plan_from_components_v1, build_layout_composition_plan_v1,
610    layout_composition_virtual_invocations_from_provenance_v1,
611    layout_composition_virtual_invocations_v1, LayoutCompositionEdgeV1, LayoutCompositionErrorV1,
612    LayoutCompositionPlanV1, LayoutCompositionRouteV1,
613};
614pub use legacy_decorator_lowering::{
615    legacy_decorator_sites_v1, lower_legacy_decorators_v1, LegacyDecoratorLoweringErrorV1,
616    LegacyDecoratorLoweringV1, LegacyDecoratorResolutionV1, LegacyDecoratorSiteV1,
617};
618pub use metaframework_handoff::{
619    build_deployable_release_manifest_v1, build_static_request_handoff_v1,
620    deployable_release_manifest_json_v1, static_request_handoff_json_v1, DeployableArtifactV1,
621    DeployableReleaseManifestV1, StaticRequestHandoffV1, StaticRequestRouteV1,
622};
623pub use model::{
624    ClassSummary, DecoratorSummary, Diagnostic, RenderMethodSummary, Severity, SourceSummary, Span,
625};
626pub use module_graph::{
627    build_module_graph, ModuleEdge, ModuleEdgeKind, ModuleGraph, ModuleNode, ModuleTarget,
628};
629pub use opaque::{OpaqueActionResolution, OpaqueActionResolutionOutcome, OpaqueTerminalBinding};
630pub use ordinary_html_codegen::{
631    generate_ordinary_instance_html, generate_ordinary_instance_html_for_component,
632    generate_structural_conditional_host_fragments, generate_structural_keyed_host_fragments,
633    generate_structural_template_instance_html,
634};
635pub use ordinary_template_instance::{
636    build_ordinary_template_instance_registry, validate_ordinary_template_instance_registry,
637    OrdinaryTemplateBindingKind, OrdinaryTemplateInstanceBindingRecord,
638    OrdinaryTemplateInstanceEventRecord, OrdinaryTemplateInstanceRegistry,
639    OrdinaryTemplateInstanceTargetRecord, OrdinaryTemplateTargetKind,
640    ORDINARY_TEMPLATE_INSTANCE_REGISTRY_VERSION,
641};
642pub use ordinary_template_integrity::{
643    ComputedInstanceSlotIntegrityCode, OrdinaryTemplateIntegrityCode,
644    StateInstanceStorageIntegrityCode,
645};
646pub use page_codegen::{
647    embed_opaque_runtime_artifact, generate_standalone_page,
648    generate_standalone_page_with_component_runtime,
649    generate_standalone_page_with_component_runtime_and_forms,
650    generate_standalone_page_with_computed_runtime, generate_standalone_page_with_context_runtime,
651    generate_standalone_page_with_effect_runtime, generate_standalone_page_with_resume_runtime,
652    generate_standalone_page_with_resume_runtime_and_resources,
653};
654pub use production_audit::{
655    build_production_audit_report_v1, production_audit_report_json_v1, ProductionAuditErrorV1,
656    ProductionAuditReportV1, ProductionRuntimeAuthority, PRODUCTION_AUDIT_REPORT_SCHEMA_VERSION,
657    PRODUCTION_RUNTIME_AUTHORITIES, PRODUCTION_RUNTIME_REFINEMENT_INVARIANTS,
658};
659pub use production_bootstrap::{
660    build_production_bootstrap_plan, ProductionBootstrapBlock, ProductionBootstrapPlan,
661    ProductionRuntimeIndexes,
662};
663pub use production_chunk_graph::{
664    extract_production_chunk_graph, validate_production_chunk_graph, ProductionActivationChunkPlan,
665    ProductionChunkDependency, ProductionChunkExtractionError, ProductionChunkExtractionReport,
666    ProductionChunkGraph, ProductionChunkGraphValidationError, ProductionChunkKind,
667    ProductionChunkRecord, ProductionRootChunkInput, ProductionSharedChunkFailurePolicy,
668};
669pub use production_cleanup::{
670    build_production_destroy_plan, validate_production_cleanup_closure,
671    ProductionCleanupClosureViolation, ProductionCleanupKind, ProductionDestroyPlan,
672    ProductionOwnedRuntimeRecord,
673};
674pub use production_constant_pool::{
675    pool_production_constants, ConstantPoolConsumer, ConstantPoolingDecision,
676    ConstantPoolingReport, ProductionConstantCandidate, ProductionConstantPool,
677    ProductionConstantPoolEntry,
678};
679pub use production_deduplication::{
680    deduplicate_generated_programs, DeduplicatedProgramRegistry, ExecutableProgramCandidate,
681    ExecutableProgramCanonicalStream, ExecutableProgramFingerprintRegistry, ProgramAliasRecord,
682    ProgramDeduplicationReport,
683};
684pub use production_diagnostics::{
685    project_production_diagnostics, ProductionDiagnosticContract, ProductionDiagnosticFact,
686    ProductionDiagnosticKind, ProductionDiagnosticProvenance, ProductionProjectedDiagnostic,
687    PRODUCTION_DIAGNOSTIC_CATALOG,
688};
689pub use production_elimination::{
690    eliminate_unreachable_production_records, DeadProductEliminationReason,
691    DeadProductEliminationReport, OptimizationDecision, ProductionArtifactProjection,
692    ProductionExecutionRecord,
693};
694pub use production_module_emitter::{
695    emit_production_modules, validate_production_module, ProductionModuleLayout,
696    ProductionModuleRecord, ProductionModuleValidationError,
697};
698pub use production_optimization::{
699    BenchmarkFixtureId, ConstantPoolEntryId, ExecutableProgramFingerprint, OptimizationDecisionId,
700    OptimizationPolicyId, OptimizationReportId, PerformanceBudgetId, ProductionArtifactId,
701    ProductionChunkId, ProductionOptimizationDiagnosticReservation,
702    ProductionOptimizationIdentityParseError, ProductionOptimizationPolicy,
703    ProductionOptimizationPolicyV1, RuntimeTableId, SharedChunkCandidateId,
704    PRODUCTION_OPTIMIZATION_DIAGNOSTIC_RESERVATIONS,
705    PRODUCTION_OPTIMIZATION_INTEGRITY_RESERVATION_END,
706    PRODUCTION_OPTIMIZATION_INTEGRITY_RESERVATION_START,
707};
708pub use production_reachability::{
709    build_production_reachability_graph, ProductionExecutableRoot, ProductionProgramEdge,
710    ProductionReachabilityBlock, ProductionReachabilityGraph, ProductionReachabilityReason,
711    ProductionUnreachableRecord,
712};
713pub use production_reports::{
714    build_production_reports, optimization_report_json, runtime_cost_report_json,
715    OptimizationInspectionQuery, OptimizationReportV1, ProductionReportInputs,
716    RuntimeCostInspectionQuery, RuntimeCostReportV1, OPTIMIZATION_REPORT_SCHEMA_VERSION,
717    RUNTIME_COST_REPORT_SCHEMA_VERSION,
718};
719pub use production_runtime_artifact::{
720    build_production_runtime_artifact, build_production_runtime_table,
721    parse_production_runtime_artifact_v1, production_runtime_artifact_json,
722    validate_production_runtime_artifact, ProductionArtifactActivationEntry,
723    ProductionArtifactChunkRecord, ProductionArtifactEntry, ProductionArtifactIntegrity,
724    ProductionArtifactIntegrityViolation, ProductionOrdinalMapping, ProductionOrdinalWidth,
725    ProductionRuntimeArtifactV1, ProductionRuntimeTable, ProductionRuntimeTableRegistry,
726    PRODUCTION_RUNTIME_ARTIFACT_SCHEMA_VERSION, PRODUCTION_RUNTIME_TABLE_SCHEMA_VERSION,
727};
728pub use production_scheduler::{
729    build_production_patch_schedule, coalesce_production_binding_writes,
730    BindingWriteCoalescingDecision, BindingWriteCoalescingReport, ProductionPatchBatchKind,
731    ProductionPatchOperation, ProductionPatchSchedule,
732};
733pub use production_validation::{
734    validate_production_runtime_pipeline, ProductionRuntimeFailure, ProductionValidationEvidence,
735    ProductionValidationPhase, ProductionValidationResult,
736};
737pub use project_discovery::{
738    discover_project_v1, discover_semantic_packages_v1, DiscoveredProjectSourceV1,
739    DiscoveredProjectV1, ProjectDiscoveryErrorV1,
740};
741pub use provider::{collect_provider_entities, DuplicateProviderDeclaration, ProviderEntity};
742pub use purity_effect::{
743    build_purity_effect_graph_v1, FunctionEffectFactV1, FunctionEffectKindV1,
744    FunctionPurityEffectV1, FunctionPurityV1, PurityEffectErrorV1, PurityEffectGraphV1,
745    PURITY_EFFECT_SCHEMA_VERSION,
746};
747pub use resource::{
748    ResourceActivation, ResourceDeclaration, ResourceDeclarationError, ResourceEndpointBinding,
749    ResourceEndpointResolution, ResourceEndpointResolutionOutcome, ResourceInvalidationPolicy,
750    ResourceLifecycleError, ResourceLifecycleEvent, ResourceLifecycleState, ResourceRetryPolicy,
751};
752pub use resume_activation::{
753    build_resume_activation_plan, validate_resume_activation_plan, ResumeActivationBlock,
754    ResumeActivationBlockReason, ResumeActivationIntegrityCode,
755    ResumeActivationIntegrityDiagnostic, ResumeActivationPlan, ResumeActivationPolicy,
756    ResumeActivationPolicyDecision, ResumeActivationPrerequisite, RESUME_ACTIVATION_PLAN_VERSION,
757};
758pub use resume_anchor::{
759    build_resume_anchor_plan, validate_resume_anchor_plan, validate_resume_marker_html,
760    ResumeAnchorIntegrityCode, ResumeAnchorIntegrityDiagnostic, ResumeAnchorKind,
761    ResumeAnchorPlacement, ResumeAnchorPlan, ResumeAnchorPlanRecord, ResumeEventMarkerRecord,
762    RESUME_ANCHOR_PLAN_VERSION,
763};
764pub use resume_boundary::{
765    build_resume_boundary_graph, validate_resume_boundary_graph, ResumeBoundary,
766    ResumeBoundaryActivationIdentity, ResumeBoundaryActivationProgram,
767    ResumeBoundaryActivationReference, ResumeBoundaryBlock, ResumeBoundaryBlockSource,
768    ResumeBoundaryGraph, ResumeBoundaryIntegrityCode, ResumeBoundaryIntegrityDiagnostic,
769    ResumeBoundaryOwner, ResumeBoundaryOwnershipEdge, RESUME_BOUNDARY_GRAPH_VERSION,
770};
771pub use resume_capture::{
772    build_resume_capture_plan, capture_resume_snapshot, encode_resume_value,
773    resume_snapshot_artifact_json, resume_snapshot_json, validate_resume_capture_plan,
774    ResumeCaptureBlock, ResumeCaptureBlockReason, ResumeCaptureError, ResumeCaptureErrorKind,
775    ResumeCaptureInstruction, ResumeCaptureIntegrityCode, ResumeCaptureIntegrityDiagnostic,
776    ResumeCapturePlan, ResumeCaptureProgram, ResumeEncodedValue, ResumeEnvelopeWriterPlan,
777    ResumeSnapshotBoundaryV1, ResumeSnapshotStructuralOccurrenceV2,
778    ResumeSnapshotStructuralStateV2, ResumeSnapshotV1, ResumeSnapshotV2,
779    ResumeSnapshotValueRecordV1, RuntimeQuiescenceState, RESUME_CAPTURE_MANIFEST_VERSION,
780    RESUME_CAPTURE_PLAN_VERSION, RESUME_SNAPSHOT_SCHEMA_VERSION,
781};
782pub use resume_chunk::{
783    build_resume_chunk_graph, validate_resume_chunk_graph, ResumeChunk, ResumeChunkBlock,
784    ResumeChunkBlockReason, ResumeChunkGraph, ResumeChunkIntegrityCode,
785    ResumeChunkIntegrityDiagnostic, ResumeChunkModulePlan, ResumeChunkProgram,
786    ResumeChunkProgramInclusion, ResumeChunkRootKind, RESUME_CHUNK_GRAPH_VERSION,
787};
788pub use resume_diagnostics::{
789    build_resume_diagnostic_products, project_resume_diagnostics,
790    project_resume_diagnostics_from_products, ResumeDiagnosticProducts,
791    ResumeDiagnosticReservation, ResumeProjectedDiagnostic, RESUME_DIAGNOSTIC_RESERVATIONS,
792    RESUME_INTEGRITY_RESERVATION_END, RESUME_INTEGRITY_RESERVATION_START,
793};
794pub use resume_identity::{
795    ComputedInstanceCacheSlotId, ComputedInstanceDirtySlotId, ResumeActivationId,
796    ResumeActivationRootKind, ResumeAnchorId, ResumeBoundaryId, ResumeBoundaryKind, ResumeBuildId,
797    ResumeCaptureProgramId, ResumeChunkGroupId, ResumeChunkId, ResumeEventId,
798    ResumeIdentityParseError, ResumeRestoreProgramId, ResumeSchemaId, ResumeSlotId,
799    ResumeSnapshotId, ResumeValueRecordId, StateInstanceSlotId, TemplateInstanceBindingId,
800    TemplateInstanceTargetId,
801};
802pub use resume_liveness::{
803    build_resume_liveness_plan, validate_resume_liveness_plan, ResumeExcludedSlot,
804    ResumeExistingSlot, ResumeLivenessBlock, ResumeLivenessBlockReason,
805    ResumeLivenessClassificationKind, ResumeLivenessClassificationRef, ResumeLivenessIntegrityCode,
806    ResumeLivenessIntegrityDiagnostic, ResumeLivenessOwner, ResumeLivenessPlan, ResumeLivenessSlot,
807    ResumeRecomputableSlot, ResumeRecomputationProof, ResumeRetainedSlot, ResumeRetentionReason,
808    RESUME_LIVENESS_PLAN_VERSION,
809};
810pub use resume_manifest::{
811    build_resume_manifest, compute_resume_build_id, parse_resume_manifest_v6,
812    parse_resume_manifest_v7, resume_manifest_json, validate_resume_manifest, ResumeManifest,
813    ResumeManifestActivationPolicy, ResumeManifestActivationRecord, ResumeManifestAnchorRecord,
814    ResumeManifestBoundaryKind, ResumeManifestBoundaryRecord, ResumeManifestCaptureInstruction,
815    ResumeManifestCaptureProgram, ResumeManifestChunkRecord, ResumeManifestContextSlotRecord,
816    ResumeManifestEffectRecord, ResumeManifestEventRecord,
817    ResumeManifestPhaseIComponentResumeRecord, ResumeManifestRestoreInstruction,
818    ResumeManifestRestoreInstructionRecord, ResumeManifestRestoreProgram,
819    ResumeManifestSlotSchemaRecord, ResumeManifestValidationDiagnostic,
820    RESUME_MANIFEST_SCHEMA_VERSION, RESUME_RUNTIME_PROTOCOL_VERSION,
821};
822pub use resume_plan::{
823    build_resume_plan, ComponentInstanceResumePlan, FormFieldResumePlan, FormInstanceResumePlan,
824    ResumeComponentPlan, ResumeComputedPlan, ResumePlan, SlotBindingResumePlan,
825    StructuralRegionResumePlan,
826};
827pub use resume_restore::{
828    build_resume_restore_plan, validate_resume_restore_plan, ResumeRestoreApplicationSchedule,
829    ResumeRestoreBlock, ResumeRestoreBlockReason, ResumeRestoreInstruction,
830    ResumeRestoreInstructionRecord, ResumeRestoreIntegrityCode, ResumeRestoreIntegrityDiagnostic,
831    ResumeRestorePhase, ResumeRestorePlan, ResumeRestoreProgram, ResumeRestoreSchedulePhase,
832    ResumeRestoreSlotAssignment, RESUME_RESTORE_PLAN_VERSION,
833};
834pub use resume_schema::{
835    build_resume_schema_registry, resume_value_codec, validate_resume_schema_registry,
836    ResumeBoundarySchema, ResumeObjectPropertyCodec, ResumeSchemaBlock, ResumeSchemaBlockReason,
837    ResumeSchemaIntegrityCode, ResumeSchemaIntegrityDiagnostic, ResumeSchemaRegistry,
838    ResumeSlotSchema, ResumeValueCodec, RESUME_SCHEMA_REGISTRY_VERSION,
839};
840pub use route_graph::{
841    build_file_route_graph_from_components_v1, build_file_route_graph_v1, build_route_graph,
842    build_static_route_publication_v1, build_validated_file_route_graph_from_components_v1,
843    build_validated_file_route_graph_v1, build_validated_route_graph_v1, route_manifest_json_v1,
844    route_manifest_v1, FileRouteGraphV1, FileRouteNodeV1, RouteGraph, RouteGraphError,
845    RouteManifestEntryV1, RouteManifestV1, RouteNode,
846};
847pub use route_loader::{
848    build_route_loader_plan_v1, route_loader_plan_json_v1, RouteLoaderBindingV1,
849    RouteLoaderPlanErrorV1, RouteLoaderPlanV1, RouteLoaderRouteV1,
850    ROUTE_LOADER_PLAN_SCHEMA_VERSION,
851};
852pub use route_metadata::{
853    build_route_metadata_manifest_v1, route_metadata_manifest_json_v1, RouteMetadataErrorV1,
854    RouteMetadataInputV1, RouteMetadataManifestV1, RouteMetadataRecordV1,
855    ROUTE_METADATA_SCHEMA_VERSION,
856};
857pub use route_server_action::{
858    build_route_server_action_plan_v1, route_server_action_plan_json_v1,
859    RouteServerActionBindingV1, RouteServerActionPlanErrorV1, RouteServerActionPlanV1,
860    RouteServerActionRouteV1, ROUTE_SERVER_ACTION_PLAN_SCHEMA_VERSION,
861};
862pub use runtime_codegen::generate_runtime_stub;
863pub use runtime_component::{
864    build_runtime_component_registry, RuntimeComponentContextBindingRecord,
865    RuntimeComponentDefinitionRecord, RuntimeComponentInitializationBatch,
866    RuntimeComponentInstanceRecord, RuntimeComponentRegistry, RuntimeComponentSlotBindingRecord,
867    RUNTIME_COMPONENT_REGISTRY_SCHEMA_CONTRACT_VERSION,
868};
869pub use runtime_component_artifact::{
870    build_runtime_component_artifact, runtime_component_artifact_json,
871    validate_runtime_component_artifact, RuntimeComponentArtifact,
872    RUNTIME_COMPONENT_ARTIFACT_SCHEMA_VERSION,
873};
874pub use runtime_computed::{
875    build_runtime_computed_registry, ComputedCacheSlotId, ComputedDirtyFlagId,
876    RuntimeComputedCacheSlot, RuntimeComputedDirtyFlag, RuntimeComputedRecord,
877    RuntimeComputedRegistry,
878};
879pub use runtime_computed_artifact::{
880    build_runtime_computed_artifact, runtime_computed_artifact_json, RuntimeComputedArtifact,
881    RuntimeComputedArtifactDirtyFlag, RuntimeComputedArtifactEvaluation,
882    RuntimeComputedArtifactInstruction, RuntimeComputedArtifactInvalidation,
883    RuntimeComputedArtifactResourceInvalidation, RuntimeComputedArtifactSerialization,
884    RUNTIME_COMPUTED_ARTIFACT_SCHEMA_VERSION,
885};
886pub use runtime_context::{
887    build_runtime_context_registry, validate_runtime_context_registry,
888    RuntimeContextConsumerRecord, RuntimeContextEvaluationBatch, RuntimeContextRegistry,
889    RuntimeContextRegistryValidationDiagnostic, RuntimeContextSourceKind,
890    RuntimeContextSourceRecord, RUNTIME_CONTEXT_REGISTRY_SCHEMA_CONTRACT_VERSION,
891};
892pub use runtime_context_artifact::{
893    build_runtime_context_artifact, runtime_context_artifact_json, RuntimeContextArtifact,
894    SerializedContextActionUpdatePlan, SerializedContextBatchId, SerializedContextConsumerBinding,
895    SerializedContextEvaluationBatch, SerializedContextExecutionBoundary,
896    SerializedContextInstruction, SerializedContextInstructionKind, SerializedContextProgram,
897    SerializedContextSource, SerializedContextSourceKind, RUNTIME_CONTEXT_ARTIFACT_SCHEMA_VERSION,
898};
899pub use runtime_effect::{
900    build_runtime_effect_registry, RuntimeActionBatchEffectTrigger, RuntimeEffectRecord,
901    RuntimeEffectRegistry, RuntimeInitialEffectTrigger,
902};
903pub use runtime_effect_artifact::{
904    build_runtime_effect_artifact, runtime_effect_artifact_json, RuntimeEffectArtifact,
905    RuntimeEffectArtifactActionTrigger, RuntimeEffectArtifactCapabilityInstructionKind,
906    RuntimeEffectArtifactCapabilityOperation, RuntimeEffectArtifactEffect,
907    RuntimeEffectArtifactExecutionBoundary, RuntimeEffectArtifactExecutionPolicy,
908    RuntimeEffectArtifactInitialTrigger, RuntimeEffectArtifactInstance,
909    RuntimeEffectArtifactInstruction, RuntimeEffectArtifactPrerequisiteBatch,
910    RuntimeEffectArtifactProgram, RuntimeEffectArtifactRenderBoundary,
911    RuntimeEffectArtifactStructuralTemplate, RUNTIME_EFFECT_ARTIFACT_SCHEMA_VERSION,
912};
913pub use runtime_effect_instance::{
914    build_runtime_effect_instance_registry, build_runtime_effect_structural_template_registry,
915    RuntimeEffectInstanceRecord, RuntimeEffectInstanceRegistry,
916    RuntimeEffectStructuralTemplateRecord, RUNTIME_EFFECT_INSTANCE_REGISTRY_VERSION,
917};
918pub use runtime_form_artifact::{
919    build_runtime_forms_artifact, runtime_forms_artifact_json, validate_runtime_forms_artifact,
920    RuntimeFormsArtifact, RuntimeFormsArtifactBinding, RuntimeFormsArtifactDependency,
921    RuntimeFormsArtifactField, RuntimeFormsArtifactFieldProgram, RuntimeFormsArtifactFieldSlots,
922    RuntimeFormsArtifactForm, RuntimeFormsArtifactInstance, RuntimeFormsArtifactPrograms,
923    RuntimeFormsArtifactReset, RuntimeFormsArtifactRule, RuntimeFormsArtifactRuleArgument,
924    RuntimeFormsArtifactSerialization, RuntimeFormsArtifactStandardSchemaModule,
925    RuntimeFormsArtifactSubmission, RuntimeFormsArtifactValidation,
926    RUNTIME_FORM_ARTIFACT_SCHEMA_VERSION,
927};
928pub use runtime_form_registry::{
929    build_runtime_form_registry, RuntimeFormInstanceRecord, RuntimeFormRecord, RuntimeFormRegistry,
930    RUNTIME_FORM_REGISTRY_VERSION,
931};
932pub use runtime_opaque_artifact::{
933    build_runtime_opaque_artifact, build_runtime_opaque_artifact_with_modules,
934    runtime_opaque_artifact_json, validate_runtime_opaque_artifact, RuntimeOpaqueArtifact,
935    RuntimeOpaqueArtifactActivation, RuntimeOpaqueArtifactBuildError,
936    RuntimeOpaqueArtifactValidationError, RUNTIME_OPAQUE_ARTIFACT_SCHEMA_VERSION,
937};
938pub use runtime_resource_artifact::{
939    build_runtime_resource_artifact, build_runtime_resource_artifact_with_modules,
940    runtime_resource_artifact_json, validate_runtime_resource_artifact, RuntimeResourceArtifact,
941    RuntimeResourceArtifactActivation, RuntimeResourceArtifactBuildError,
942    RuntimeResourceArtifactDeclaration, RuntimeResourceArtifactEndpoint,
943    RuntimeResourceArtifactValidationError, RUNTIME_RESOURCE_ARTIFACT_SCHEMA_VERSION,
944};
945pub use semantic_capability::{
946    build_semantic_capability_registry, semantic_capability_matrix_text,
947    semantic_capability_migration_text, semantic_capability_registry_json, SemanticCapability,
948    SemanticCapabilityClass, SemanticCapabilityRegistry, SemanticCapabilityStatus,
949    SEMANTIC_CAPABILITY_REGISTRY_SCHEMA_VERSION,
950};
951pub use semantic_graph::{
952    build_semantic_graph, semantic_graph_json, SemanticGraph, SemanticGraphConsumer,
953    SemanticGraphContext, SemanticGraphEdge, SemanticGraphEdgeKind, SemanticGraphNode,
954    SemanticGraphNodeKind, SemanticGraphProvenance, SemanticGraphProvider,
955    SEMANTIC_GRAPH_SCHEMA_VERSION,
956};
957pub use semantic_id::{
958    ComponentInstanceId, ComponentInvocationId, ComponentRootId, ComponentStructuralRegionId,
959    ConsumerId, ContextDeclarationCandidateId, ContextId, DirtyTrackingPlanId, EffectId,
960    EffectInstanceId, EffectStatementId, FieldBindingId, FieldDependencyId, FieldId,
961    FieldResetOperationId, FieldTrackingId, FormDeclarationCandidateId,
962    FormFieldBindingCandidateId, FormFieldDeclarationCandidateId, FormFieldDirtySlotId,
963    FormFieldTouchedSlotId, FormFieldValidationSlotId, FormFieldValueSlotId, FormId,
964    FormInstanceId, FormOwnershipGraphId, FormSubmissionStateSlotId, FormValidationAggregateSlotId,
965    ProviderId, ResetPlanId, ResourceActivationId, ResourceId, SemanticId, SemanticOwner,
966    SerializationPlanId, SlotBindingId, SlotContentFragmentId, SlotDeclarationCandidateId, SlotId,
967    SlotOutletId, SubmissionDeclarationCandidateId, SubmissionHostCandidateId, SubmissionHostId,
968    SubmissionPlanId, TemplatePositionId, TouchedTrackingPlanId, ValidationDependencyCycleId,
969    ValidationGraphId, ValidationPlanId, ValidationRuleCandidateId, ValidationRuleId,
970};
971pub use semantic_package::{
972    parse_semantic_package_contract, SemanticPackageContract, SemanticPackageContractError,
973    SemanticPackageExport, SemanticPackageFormSubmission,
974    SemanticPackageFormSubmissionCancellation, SemanticPackageFormSubmissionExecutionBoundary,
975    SemanticPackageFormSubmissionInput, SemanticPackageFormSubmissionResult, SemanticPackageKind,
976    SemanticPackageOpaqueExecutionBoundary, SemanticPackageOpaqueResumePolicy,
977    SemanticPackageOpaqueTerminal, SemanticPackagePureOperation, SemanticPackageResolutionTable,
978    SemanticPackageResourceCancellation, SemanticPackageResourceEndpoint,
979    SemanticPackageResourceExecutionBoundary, SemanticPackageResourceResumePolicy,
980    SemanticPackageRouteLoader, SemanticPackageRouteLoaderFailure, SemanticPackageRouteLoaderInput,
981    SemanticPackageServerAction, SemanticPackageServerActionInput,
982    SemanticPackageServerActionResponse, SemanticPackageServerCachePolicy,
983    SemanticPackageServerCacheScope, SEMANTIC_PACKAGE_CONTRACT_SCHEMA_VERSION,
984};
985pub use semantic_package_runtime::{
986    SemanticPackageRuntimeModuleError, SemanticPackageRuntimeModuleKey,
987    SemanticPackageRuntimeModuleTable,
988};
989pub use semantic_provenance::SourceProvenance;
990pub use semantic_reference::{SemanticReference, SemanticReferenceKind};
991pub use semantic_type::{
992    boundary_compatibility, dom_binding_contract, infer_serializable_value_type, is_assignable,
993    is_state_initializer_assignable, operator_result_type, semantic_type_text,
994    serialization_compatibility, state_initializer_value_type, BoundaryCompatibility,
995    BuiltinTypeAuthority, ComputedValueType, DomBindingContract, DomBindingKind,
996    EffectCompatibility, EffectOperationClassification, EffectStatementTypeRecord,
997    ExecutionBoundary, ObjectType, ResolvedDeclaredSemanticType, ResourceExecutionBoundary,
998    ResourceType, SemanticOperator, SemanticType, SemanticTypeAlias, SemanticTypeAssignment,
999    SemanticTypeId, SemanticTypeModel, SemanticTypeStatus, SerializationCompatibility,
1000    TypeDiagnosticCode, TypeDiagnosticFamily,
1001};
1002pub use shared_chunk_candidate::{
1003    plan_shared_lazy_chunk_candidates, SharedChunkCandidate, SharedChunkCandidatePlan,
1004    SharedChunkConsumerRoot, SharedChunkProgramOccurrence, SharedChunkRejectionReason,
1005    SharedChunkSavingsCalculation,
1006};
1007pub use slot::{collect_slot_entities, SlotEntity};
1008pub use slot_binding::{
1009    collect_slot_bindings, collect_slot_bindings_with_virtual_invocations, SlotBinding,
1010    SlotBindingRegistry, SlotBindingStatus,
1011};
1012pub use slot_content::{
1013    collect_slot_composition, SlotCompositionRegistry, SlotContentFragment,
1014    SlotContentFragmentStatus, SlotContentFragmentViolation, SlotOutlet, SlotOutletStatus,
1015    SlotOutletViolation,
1016};
1017pub use slot_field_lowering::{
1018    lower_slot_fields_v1, slot_field_sites_v1, ResolvedSlotFieldV1, SlotFieldLoweringErrorV1,
1019    SlotFieldLoweringV1, SlotFieldSiteV1,
1020};
1021pub use slot_projection::{
1022    build_slot_projection_graph_v1, SlotProjectionGraphV1, SlotProjectionRecordV1,
1023    SlotProjectionStatusV1, SlotResumabilityCoverageV1, SLOT_PROJECTION_SCHEMA_VERSION,
1024};
1025pub use state_initializer_lowering::{
1026    lower_state_initializers_v1, state_initializer_sites_v1, ResolvedStateInitializerV1,
1027    StateInitializerLoweringErrorV1, StateInitializerLoweringV1, StateInitializerSiteV1,
1028};
1029pub use state_instance_storage::{
1030    build_state_instance_storage_registry, validate_state_instance_storage_registry,
1031    StateInstanceStorageRecord, StateInstanceStorageRegistry,
1032    STATE_INSTANCE_STORAGE_REGISTRY_VERSION,
1033};
1034pub use state_projection::{
1035    build_state_projection_v1, StateProjectionRecordV1, StateProjectionV1, StateResumeAdmissionV1,
1036    StateUpdateCoverageV1, STATE_PROJECTION_SCHEMA_VERSION,
1037};
1038pub use structural_component::{
1039    build_structural_component_graph_v1, ComponentInheritanceStatusV1, ComponentPropsResolutionV1,
1040    StructuralComponentDiagnosticReasonV1, StructuralComponentDiagnosticV1,
1041    StructuralComponentFactV1, StructuralComponentGraphV1, StructuralComponentPropV1,
1042    StructuralComponentRecordV1, STRUCTURAL_COMPONENT_SCHEMA_VERSION,
1043};
1044pub use structural_occurrence_identity::{
1045    decode_structural_occurrence_identity, encode_structural_occurrence_identity,
1046    StructuralOccurrenceIdentity,
1047};
1048pub use summarize::summarize_source;
1049pub use symbol_table::{
1050    build_symbol_table, ModuleSymbol, ModuleSymbolTable, SymbolDiagnostic, SymbolKind, SymbolTable,
1051};
1052pub use template_graph::{
1053    build_template_graph, AttributeValue, ConditionalNode, ElementNode, FragmentNode, ListNode,
1054    TemplateAttribute, TemplateChild, TemplateGraph, TemplateNode, TemplateNodeId,
1055};
1056pub use template_manifest::{
1057    build_template_manifest, build_template_manifest_from_asm, template_manifest_json,
1058    validate_template_manifest, ManifestAction, ManifestBindingTarget, ManifestComponent,
1059    ManifestEvent, ManifestEventKind, ManifestFormBinding, ManifestFormHost, ManifestNode,
1060    ManifestOperation, ManifestOrdinaryBinding, ManifestOrdinaryEvent, ManifestOrdinaryTarget,
1061    ManifestTemplate, TemplateManifest, TEMPLATE_MANIFEST_SCHEMA_VERSION,
1062};
1063pub use template_semantics::{
1064    build_template_semantic_entities, TemplateSemanticEntity, TemplateSemanticKind,
1065    TemplateSemanticScope,
1066};
1067pub use v2_authoring_lowering::{
1068    lower_v2_authoring_v1, V2AuthoringLoweringErrorV1, V2AuthoringLoweringV1,
1069    V2AuthoringResolutionsV1,
1070};
1071pub use v2_authority_request::{
1072    build_v2_authority_component_request_v1, build_v2_authority_request_v1,
1073    build_v2_environment_authority_request_v1, V2AuthorityCanonicalV1, V2AuthorityFormFieldSiteV1,
1074    V2AuthorityMemberSiteV1, V2AuthorityPositionV1, V2AuthorityRequestErrorV1,
1075    V2AuthorityRequestV1, V2AuthoritySiteV1, V2_AUTHORITY_REQUEST_SCHEMA_VERSION,
1076};
1077pub use v2_authority_response::{
1078    v2_authoring_resolutions_from_response_v1, v2_environment_public_resolutions_from_response_v1,
1079    validate_v2_authority_response_v1, ResolvedEnvironmentPublicReadV1,
1080    V2AuthorityFormFieldResolutionV1, V2AuthorityFormFieldValueClassificationV1,
1081    V2AuthorityIdentityV1, V2AuthorityResolutionV1, V2AuthorityResponseErrorV1,
1082    V2AuthorityResponseV1, V2_AUTHORITY_RESPONSE_SCHEMA_VERSION,
1083};
1084
1085#[cfg(test)]
1086mod tests {
1087    use super::*;
1088    use std::{collections::BTreeMap, path::Path};
1089
1090    #[test]
1091    fn summarizes_component_decorator_class_and_render_method() {
1092        let source = r#"
1093@component("x-counter")
1094class Counter extends Component {
1095  render() {
1096    return <button>Count</button>;
1097  }
1098}
1099"#;
1100
1101        let summary = summarize_source("Counter.tsx", source);
1102
1103        assert_eq!(summary.component_decorators.len(), 1);
1104        assert_eq!(summary.component_classes.len(), 1);
1105        assert_eq!(
1106            summary.component_decorators[0].argument.as_deref(),
1107            Some("x-counter")
1108        );
1109        assert_eq!(summary.class_declarations.len(), 1);
1110        assert_eq!(summary.class_declarations[0].name, "Counter");
1111        assert_eq!(summary.render_methods.len(), 1);
1112        assert!(summary.has_tsx_like_syntax);
1113    }
1114
1115    #[test]
1116    fn recognizes_decorator_free_component_classes_without_ps0100() {
1117        let source = r#"
1118import { Component } from "presolve";
1119
1120export default class Home extends Component {
1121  render() {
1122    return <main>Home</main>;
1123  }
1124}
1125"#;
1126
1127        let summary = summarize_source("app/routes/index.tsx", source);
1128
1129        assert_eq!(summary.component_classes.len(), 1);
1130        assert_eq!(summary.component_classes[0].name, "Home");
1131        assert!(summary.component_decorators.is_empty());
1132        assert!(!summary
1133            .diagnostics
1134            .iter()
1135            .any(|diagnostic| diagnostic.code == "PS0100"));
1136    }
1137
1138    #[test]
1139    fn emits_diagnostics_for_empty_source() {
1140        let summary = summarize_source("Empty.tsx", "");
1141        assert!(summary
1142            .diagnostics
1143            .iter()
1144            .any(|diagnostic| diagnostic.code == "PS0001"));
1145    }
1146
1147    #[test]
1148    fn fixture_0001_source_summary_explain_text_matches_expected() {
1149        let fixture_root = Path::new(env!("CARGO_MANIFEST_DIR"))
1150            .join("../..")
1151            .join("fixtures/0001-source-summary");
1152
1153        let input_path = fixture_root.join("input/Counter.tsx");
1154        let expected_path = fixture_root.join("expected/explain.txt");
1155
1156        let source = std::fs::read_to_string(&input_path).expect("failed to read fixture input");
1157        let expected = std::fs::read_to_string(&expected_path)
1158            .expect("failed to read expected explain output");
1159
1160        let summary = summarize_source("fixtures/0001-source-summary/input/Counter.tsx", &source);
1161
1162        let actual = explain_text(&summary);
1163
1164        assert_eq!(actual, expected);
1165    }
1166
1167    #[test]
1168    fn fixture_0001_source_summary_explain_json_matches_expected() {
1169        let fixture_root = Path::new(env!("CARGO_MANIFEST_DIR"))
1170            .join("../..")
1171            .join("fixtures/0001-source-summary");
1172
1173        let input_path = fixture_root.join("input/Counter.tsx");
1174        let expected_path = fixture_root.join("expected/explain.json");
1175
1176        let source = std::fs::read_to_string(&input_path).expect("failed to read fixture input");
1177        let expected = std::fs::read_to_string(&expected_path)
1178            .expect("failed to read expected JSON explain output");
1179
1180        let summary = summarize_source("fixtures/0001-source-summary/input/Counter.tsx", &source);
1181
1182        let actual = explain_json(&summary);
1183
1184        let actual_json: serde_json::Value =
1185            serde_json::from_str(&actual).expect("actual explain JSON is invalid");
1186        let expected_json: serde_json::Value =
1187            serde_json::from_str(&expected).expect("expected explain JSON fixture is invalid");
1188
1189        assert_eq!(actual_json, expected_json);
1190    }
1191
1192    #[test]
1193    #[allow(clippy::too_many_lines)]
1194    fn builds_component_graph_from_parsed_counter() {
1195        let source = include_str!("../../../fixtures/0001-source-summary/input/Counter.tsx");
1196
1197        let parsed =
1198            presolve_parser::parse_file("fixtures/0001-source-summary/input/Counter.tsx", source);
1199
1200        let graph = build_component_graph(&parsed);
1201
1202        assert!(graph.diagnostics.is_empty());
1203
1204        let component = graph.components.first().expect("expected component");
1205
1206        assert_eq!(component.class_name, "Counter");
1207        assert_eq!(component.id.as_str(), "component:x-counter");
1208        assert_eq!(component.owner, SemanticOwner::Application);
1209        assert_eq!(component.element_name.as_deref(), Some("x-counter"));
1210        assert_eq!(component.route_path.as_deref(), Some("/counter"));
1211
1212        assert_eq!(component.state_fields.len(), 1);
1213        assert_eq!(component.state_fields[0].name, "count");
1214        assert_eq!(
1215            component.state_fields[0].id.as_str(),
1216            "component:x-counter/state:count"
1217        );
1218        assert_eq!(
1219            component.state_fields[0].owner,
1220            SemanticOwner::entity(component.id.clone())
1221        );
1222        assert_eq!(
1223            component.state_fields[0].initial_value,
1224            Some(SerializableValue::Number("0".to_string()))
1225        );
1226
1227        let method_names = component
1228            .methods
1229            .iter()
1230            .map(|method| method.name.as_str())
1231            .collect::<Vec<_>>();
1232
1233        assert_eq!(method_names, vec!["increment", "render"]);
1234        assert_eq!(
1235            component.methods[0].id.as_str(),
1236            "component:x-counter/method:increment"
1237        );
1238        assert_eq!(
1239            component.methods[0].owner,
1240            SemanticOwner::entity(component.id.clone())
1241        );
1242        assert_eq!(
1243            component.actions,
1244            vec![ComponentAction {
1245                id: SemanticId::component(Some("x-counter"), "Counter").action("increment", 0),
1246                owner: SemanticOwner::entity(
1247                    SemanticId::component(Some("x-counter"), "Counter").method("increment"),
1248                ),
1249                method: "increment".to_string(),
1250                operation: StateOperation::AddAssign(SerializableValue::Number("1".to_string())),
1251                field: "count".to_string(),
1252            }]
1253        );
1254
1255        let render = component.render.as_ref().expect("expected render model");
1256
1257        assert_eq!(render.root_element.as_deref(), Some("button"));
1258        assert_eq!(render.attributes.len(), 1);
1259        assert_eq!(render.attributes[0].name, "onClick");
1260        assert!(matches!(
1261            render.attributes[0].value,
1262            RenderAttributeValue::Expression(_)
1263        ));
1264        assert_eq!(render.bindings, vec!["this.count"]);
1265        assert_eq!(render.root_span.expect("expected root span").line, 12);
1266        assert_eq!(render.root_span.expect("expected root span").column, 7);
1267        assert_eq!(render.event_handlers.len(), 1);
1268        assert_eq!(
1269            render.event_handlers[0].id.as_str(),
1270            "component:x-counter/event:click:0"
1271        );
1272        assert_eq!(
1273            render.event_handlers[0].owner,
1274            SemanticOwner::entity(component.id.template())
1275        );
1276        assert_eq!(render.event_handlers[0].event, "click");
1277        assert_eq!(render.event_handlers[0].handler, "this.increment");
1278        assert_eq!(render.event_handlers[0].span.line, 12);
1279        assert_eq!(render.event_handlers[0].span.column, 15);
1280        assert_eq!(render.children.len(), 2);
1281
1282        let RenderChild::Text { value, span } = &render.children[0] else {
1283            panic!("expected text child");
1284        };
1285        assert_eq!(value, "Count:");
1286        assert_eq!(span.line, 13);
1287        assert_eq!(span.column, 9);
1288
1289        let RenderChild::Binding { expression, span } = &render.children[1] else {
1290            panic!("expected binding child");
1291        };
1292        assert_eq!(expression, "this.count");
1293        assert_eq!(span.line, 13);
1294        assert_eq!(span.column, 16);
1295
1296        assert_eq!(graph.references.len(), 2);
1297        assert_eq!(graph.references[0].kind, SemanticReferenceKind::ActionState);
1298        assert_eq!(
1299            graph.references[0].source,
1300            SemanticId::component(Some("x-counter"), "Counter").action("increment", 0)
1301        );
1302        assert_eq!(
1303            graph.references[0].target,
1304            SemanticId::component(Some("x-counter"), "Counter").state_field("count")
1305        );
1306        assert_eq!(
1307            graph.references[0].provenance.path,
1308            Path::new("fixtures/0001-source-summary/input/Counter.tsx")
1309        );
1310        assert_eq!(graph.references[0].provenance.span.line, 7);
1311
1312        assert_eq!(graph.references[1].kind, SemanticReferenceKind::EventMethod);
1313        assert_eq!(
1314            graph.references[1].source,
1315            SemanticId::component(Some("x-counter"), "Counter").event_handler("click", 0)
1316        );
1317        assert_eq!(
1318            graph.references[1].target,
1319            SemanticId::component(Some("x-counter"), "Counter").method("increment")
1320        );
1321        assert_eq!(
1322            graph.references[1].provenance.path,
1323            Path::new("fixtures/0001-source-summary/input/Counter.tsx")
1324        );
1325        assert_eq!(graph.references[1].provenance.span.line, 12);
1326
1327        assert_eq!(graph.provenance[&component.id].span.line, 1);
1328        assert_eq!(graph.provenance[&component.state_fields[0].id].span.line, 4);
1329        assert_eq!(graph.provenance[&component.methods[0].id].span.line, 6);
1330        assert_eq!(graph.provenance[&component.actions[0].id].span.line, 7);
1331        assert_eq!(graph.provenance[&component.id.template()].span.line, 10);
1332        assert_eq!(graph.provenance[&render.event_handlers[0].id].span.line, 12);
1333    }
1334
1335    #[test]
1336    fn lowers_method_parameters_into_canonical_method_metadata() {
1337        let parsed = presolve_parser::parse_file(
1338            "src/Parameters.tsx",
1339            r#"
1340@component("x-parameters")
1341class Parameters extends Component {
1342  save(title: string, retries?: number) {}
1343}
1344"#,
1345        );
1346
1347        let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1348        let method = &graph.components[0].methods[0];
1349
1350        assert_eq!(method.name, "save");
1351        assert_eq!(
1352            method
1353                .parameters
1354                .iter()
1355                .map(|parameter| parameter.name.as_str())
1356                .collect::<Vec<_>>(),
1357            vec!["title", "retries"]
1358        );
1359        assert_eq!(method.parameters[0].span.line, 4);
1360        assert_eq!(method.parameters[1].span.line, 4);
1361    }
1362
1363    #[test]
1364    fn assembles_application_semantic_model_from_existing_graphs() {
1365        let source = include_str!("../../../fixtures/0001-source-summary/input/Counter.tsx");
1366        let parsed =
1367            presolve_parser::parse_file("fixtures/0001-source-summary/input/Counter.tsx", source);
1368
1369        let asm = build_application_semantic_model(&parsed);
1370        let component = &asm.components[0];
1371
1372        assert!(asm.diagnostics.is_empty());
1373        assert_eq!(asm.templates.len(), 1);
1374        assert!(asm.template_entities.len() >= 3);
1375        assert_eq!(asm.references.len(), 4);
1376        assert_eq!(asm.ownership.len(), asm.provenance.len());
1377        assert_eq!(asm.ownership[&component.id], SemanticOwner::Application);
1378        assert_eq!(
1379            asm.ownership[&component.state_fields[0].id],
1380            SemanticOwner::entity(component.id.clone())
1381        );
1382        assert_eq!(
1383            asm.ownership[&component.actions[0].id],
1384            SemanticOwner::entity(component.methods[0].id.clone())
1385        );
1386        assert_eq!(
1387            asm.ownership[&asm.templates[0].id],
1388            SemanticOwner::entity(component.id.clone())
1389        );
1390        assert_eq!(asm.provenance[&asm.templates[0].id].span.line, 10);
1391
1392        assert!(matches!(
1393            asm.entity(&component.id),
1394            Some(SemanticEntity::Component(_))
1395        ));
1396        assert!(matches!(
1397            asm.entity(&component.state_fields[0].id),
1398            Some(SemanticEntity::StateField(_))
1399        ));
1400        assert_eq!(asm.component(&component.id), Some(component));
1401        assert_eq!(asm.template(&asm.templates[0].id), Some(&asm.templates[0]));
1402        assert_eq!(
1403            asm.owner(&component.actions[0].id),
1404            Some(&component.actions[0].owner)
1405        );
1406        assert_eq!(asm.provenance(&asm.templates[0].id).unwrap().span.line, 10);
1407        let template_binding = asm
1408            .template_entities
1409            .iter()
1410            .find(|entity| entity.kind == TemplateSemanticKind::Binding)
1411            .expect("template binding entity");
1412        assert!(matches!(
1413            asm.entity(&template_binding.id),
1414            Some(SemanticEntity::TemplateEntity(_))
1415        ));
1416        assert_eq!(
1417            asm.template_entities_for(&asm.templates[0].id).len(),
1418            asm.template_entities.len()
1419        );
1420        assert_eq!(asm.references_from(&component.actions[0].id).len(), 1);
1421        assert_eq!(asm.references_to(&component.state_fields[0].id).len(), 2);
1422        assert_eq!(asm.references_to(&component.methods[0].id).len(), 2);
1423        assert!(validate_application_semantic_model(&asm).is_empty());
1424
1425        let mut invalid = asm.clone();
1426        invalid.provenance.remove(&component.actions[0].id);
1427        let diagnostics = validate_application_semantic_model(&invalid);
1428        let codes = diagnostics
1429            .iter()
1430            .map(|diagnostic| diagnostic.code.as_str())
1431            .collect::<Vec<_>>();
1432        assert!(codes.contains(&"PSASM1002"));
1433        assert!(codes.contains(&"PSASM1006"));
1434
1435        let mut invalid_type = asm.clone();
1436        let state_id = component.state_fields[0].id.clone();
1437        invalid_type
1438            .semantic_types
1439            .assignments
1440            .get_mut(&state_id)
1441            .expect("state type")
1442            .id = SemanticTypeId::for_subject(&component.id);
1443        let diagnostics = validate_application_semantic_model(&invalid_type);
1444        assert!(diagnostics
1445            .iter()
1446            .any(|diagnostic| diagnostic.code == "PSASM1102"));
1447
1448        let dependencies = DependencyAnalysisPass.analyze(&asm);
1449        assert_eq!(dependencies.dependencies[&component.actions[0].id].len(), 1);
1450        assert_eq!(
1451            dependencies.dependents[&component.state_fields[0].id].len(),
1452            2
1453        );
1454    }
1455
1456    #[test]
1457    fn carries_declared_state_types_into_component_and_asm_data() {
1458        let parsed = presolve_parser::parse_file(
1459            "src/Panel.tsx",
1460            r#"
1461@component("x-panel")
1462class Panel extends Component {
1463  count: number = state(0);
1464}
1465"#,
1466        );
1467
1468        let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1469        let graph_type = graph.components[0].state_fields[0]
1470            .declared_type
1471            .as_ref()
1472            .expect("declared state type");
1473
1474        assert_eq!(graph_type.text, "number");
1475        assert_eq!(graph_type.provenance.path, Path::new("src/Panel.tsx"));
1476        assert_eq!(graph_type.provenance.span.line, 4);
1477        assert_eq!(graph_type.provenance.span.column, 8);
1478        assert_eq!(graph_type.kind, Some(DeclaredStateTypeKind::Number));
1479
1480        let asm = build_application_semantic_model(&parsed);
1481        assert_eq!(
1482            asm.components[0].state_fields[0].declared_type,
1483            Some(graph_type.clone())
1484        );
1485    }
1486
1487    #[test]
1488    fn lowers_and_evaluates_constant_arithmetic_state_initializers() {
1489        let parsed = presolve_parser::parse_file(
1490            "src/ArithmeticState.tsx",
1491            r#"
1492@component("x-arithmetic-state")
1493class ArithmeticState extends Component {
1494  total: number = state((1 + 2) * 3);
1495  difference: number = state(10 - 3);
1496  quotient: number = state(10 / 2);
1497  remainder: number = state(10 % 3);
1498
1499  render() {
1500    return <output>{this.total}</output>;
1501  }
1502}
1503"#,
1504        );
1505        let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1506        let field = &graph.components[0].state_fields[0];
1507
1508        assert_eq!(
1509            field
1510                .initial_expression
1511                .as_ref()
1512                .map(ToString::to_string)
1513                .as_deref(),
1514            Some("((1 + 2) * 3)")
1515        );
1516        assert_eq!(
1517            field.initial_value,
1518            Some(SerializableValue::Number("9".to_string()))
1519        );
1520        assert_eq!(
1521            graph.components[0].state_fields[1].initial_value,
1522            Some(SerializableValue::Number("7".to_string()))
1523        );
1524        assert_eq!(
1525            graph.components[0].state_fields[2].initial_value,
1526            Some(SerializableValue::Number("5".to_string()))
1527        );
1528        assert_eq!(
1529            graph.components[0].state_fields[3].initial_value,
1530            Some(SerializableValue::Number("1".to_string()))
1531        );
1532        assert!(graph.diagnostics.is_empty());
1533    }
1534
1535    #[test]
1536    fn reports_invalid_constant_arithmetic_state_initializers() {
1537        let parsed = presolve_parser::parse_file(
1538            "src/ArithmeticState.tsx",
1539            r#"
1540@component("x-arithmetic-state")
1541class ArithmeticState extends Component {
1542  total: number = state(10 / 0);
1543}
1544"#,
1545        );
1546        let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1547        let diagnostic = graph
1548            .diagnostics
1549            .iter()
1550            .find(|diagnostic| diagnostic.code == "PSC1022")
1551            .expect("arithmetic diagnostic");
1552
1553        assert!(diagnostic.message.contains("division or remainder by zero"));
1554        assert_eq!(
1555            diagnostic
1556                .provenance
1557                .as_ref()
1558                .map(|provenance| provenance.span.line),
1559            Some(4)
1560        );
1561    }
1562
1563    #[test]
1564    fn lowers_and_evaluates_constant_comparison_state_initializers() {
1565        let parsed = presolve_parser::parse_file(
1566            "src/ComparisonState.tsx",
1567            r#"
1568@component("x-comparison-state")
1569class ComparisonState extends Component {
1570  equal: boolean = state(3 === 3);
1571  notEqual: boolean = state(3 !== 4);
1572  lessThan: boolean = state(2 < 3);
1573  lessThanOrEqual: boolean = state(3 <= 3);
1574  greaterThan: boolean = state(4 > 3);
1575  ready: boolean = state(((1 + 2) * 3) >= 9);
1576
1577  render() {
1578    return <output>{this.ready}</output>;
1579  }
1580}
1581"#,
1582        );
1583        let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1584        let fields = &graph.components[0].state_fields;
1585
1586        assert_eq!(
1587            fields[0]
1588                .initial_expression
1589                .as_ref()
1590                .map(ToString::to_string)
1591                .as_deref(),
1592            Some("(3 === 3)")
1593        );
1594        for field in fields {
1595            assert_eq!(field.initial_value, Some(SerializableValue::Boolean(true)));
1596        }
1597        assert_eq!(
1598            fields[5]
1599                .initial_expression
1600                .as_ref()
1601                .map(ToString::to_string)
1602                .as_deref(),
1603            Some("(((1 + 2) * 3) >= 9)")
1604        );
1605        assert!(graph.diagnostics.is_empty());
1606    }
1607
1608    #[test]
1609    fn reports_invalid_constant_comparison_state_initializers() {
1610        let parsed = presolve_parser::parse_file(
1611            "src/ComparisonState.tsx",
1612            r#"
1613@component("x-comparison-state")
1614class ComparisonState extends Component {
1615  ready: boolean = state((10 / 0) >= 1);
1616}
1617"#,
1618        );
1619        let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1620        let diagnostic = graph
1621            .diagnostics
1622            .iter()
1623            .find(|diagnostic| diagnostic.code == "PSC1023")
1624            .expect("comparison diagnostic");
1625
1626        assert!(diagnostic.message.contains("division or remainder by zero"));
1627        assert_eq!(
1628            diagnostic
1629                .provenance
1630                .as_ref()
1631                .map(|provenance| provenance.span.line),
1632            Some(4)
1633        );
1634    }
1635
1636    #[test]
1637    fn lowers_and_evaluates_constant_logical_state_initializers() {
1638        let parsed = presolve_parser::parse_file(
1639            "src/LogicalState.tsx",
1640            r#"
1641@component("x-logical-state")
1642class LogicalState extends Component {
1643  both: boolean = state((1 < 2) && (3 >= 3));
1644  either: boolean = state(false || (10 !== 4));
1645  shortAnd: boolean = state(false && ((10 / 0) > 1));
1646  shortOr: boolean = state(true || ((10 / 0) > 1));
1647
1648  render() {
1649    return <output>{this.both}</output>;
1650  }
1651}
1652"#,
1653        );
1654        let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1655        let fields = &graph.components[0].state_fields;
1656
1657        assert_eq!(
1658            fields[0]
1659                .initial_expression
1660                .as_ref()
1661                .map(ToString::to_string)
1662                .as_deref(),
1663            Some("((1 < 2) && (3 >= 3))")
1664        );
1665        assert_eq!(
1666            fields
1667                .iter()
1668                .map(|field| field.initial_value.clone())
1669                .collect::<Vec<_>>(),
1670            vec![
1671                Some(SerializableValue::Boolean(true)),
1672                Some(SerializableValue::Boolean(true)),
1673                Some(SerializableValue::Boolean(false)),
1674                Some(SerializableValue::Boolean(true)),
1675            ]
1676        );
1677        assert!(graph.diagnostics.is_empty());
1678    }
1679
1680    #[test]
1681    fn reports_evaluated_invalid_constant_logical_state_initializers() {
1682        let parsed = presolve_parser::parse_file(
1683            "src/LogicalState.tsx",
1684            r#"
1685@component("x-logical-state")
1686class LogicalState extends Component {
1687  ready: boolean = state(true && ((10 / 0) > 1));
1688}
1689"#,
1690        );
1691        let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1692        let diagnostic = graph
1693            .diagnostics
1694            .iter()
1695            .find(|diagnostic| diagnostic.code == "PSC1024")
1696            .expect("logical diagnostic");
1697
1698        assert!(diagnostic.message.contains("division or remainder by zero"));
1699        assert_eq!(
1700            diagnostic
1701                .provenance
1702                .as_ref()
1703                .map(|provenance| provenance.span.line),
1704            Some(4)
1705        );
1706    }
1707
1708    #[test]
1709    fn lowers_and_evaluates_constant_nullish_state_initializers() {
1710        let parsed = presolve_parser::parse_file(
1711            "src/NullishState.tsx",
1712            r#"
1713@component("x-nullish-state")
1714class NullishState extends Component {
1715  label: string = state(null ?? "fallback");
1716  total: number = state(5 ?? (10 / 0));
1717
1718  render() {
1719    return <output>{this.label}</output>;
1720  }
1721}
1722"#,
1723        );
1724        let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1725        let fields = &graph.components[0].state_fields;
1726
1727        assert_eq!(
1728            fields[0]
1729                .initial_expression
1730                .as_ref()
1731                .map(ToString::to_string)
1732                .as_deref(),
1733            Some("(null ?? \"fallback\")")
1734        );
1735        assert_eq!(
1736            fields[0].initial_value,
1737            Some(SerializableValue::String("fallback".to_string()))
1738        );
1739        assert_eq!(
1740            fields[1].initial_value,
1741            Some(SerializableValue::Number("5".to_string()))
1742        );
1743        assert!(graph.diagnostics.is_empty());
1744    }
1745
1746    #[test]
1747    fn lowers_and_evaluates_constant_unary_state_initializers() {
1748        let parsed = presolve_parser::parse_file(
1749            "src/UnaryState.tsx",
1750            r#"
1751@component("x-unary-state")
1752class UnaryState extends Component {
1753  negated: boolean = state(!(1 < 2));
1754  signed: number = state(-(1 + 2));
1755  render() { return <output>{this.signed}</output>; }
1756}
1757"#,
1758        );
1759        let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1760        assert_eq!(
1761            graph.components[0].state_fields[0].initial_value,
1762            Some(SerializableValue::Boolean(false))
1763        );
1764        assert_eq!(
1765            graph.components[0].state_fields[1].initial_value,
1766            Some(SerializableValue::Number("-3".to_string()))
1767        );
1768    }
1769
1770    #[test]
1771    fn reports_reached_invalid_constant_nullish_state_initializers() {
1772        let parsed = presolve_parser::parse_file(
1773            "src/NullishState.tsx",
1774            r#"
1775@component("x-nullish-state")
1776class NullishState extends Component {
1777  total: number = state(null ?? (10 / 0));
1778}
1779"#,
1780        );
1781        let graph = fold_component_graph(&build_component_graph_for_module(&parsed));
1782        let diagnostic = graph
1783            .diagnostics
1784            .iter()
1785            .find(|diagnostic| diagnostic.code == "PSC1025")
1786            .expect("nullish diagnostic");
1787        assert!(diagnostic.message.contains("division or remainder by zero"));
1788    }
1789
1790    #[test]
1791    fn classifies_exact_primitive_declared_state_types() {
1792        let source =
1793            include_str!("../../../fixtures/0025-typed-state-annotations/input/TypedState.tsx");
1794        let parsed = presolve_parser::parse_file(
1795            "fixtures/0025-typed-state-annotations/input/TypedState.tsx",
1796            source,
1797        );
1798        let graph = build_component_graph_for_module(&parsed);
1799        let kinds = graph.components[0]
1800            .state_fields
1801            .iter()
1802            .map(|field| {
1803                (
1804                    field.name.as_str(),
1805                    field
1806                        .declared_type
1807                        .as_ref()
1808                        .and_then(|declared_type| declared_type.kind),
1809                )
1810            })
1811            .collect::<Vec<_>>();
1812
1813        assert_eq!(
1814            kinds,
1815            vec![
1816                ("count", Some(DeclaredStateTypeKind::Number)),
1817                ("status", None),
1818                ("title", Some(DeclaredStateTypeKind::String)),
1819                ("enabled", Some(DeclaredStateTypeKind::Boolean)),
1820                ("empty", Some(DeclaredStateTypeKind::Null)),
1821            ]
1822        );
1823    }
1824
1825    #[test]
1826    fn reports_primitive_declared_state_initializer_mismatches() {
1827        let source = include_str!(
1828            "../../../fixtures/0027-declared-state-type-diagnostics/input/InvalidTypedState.tsx"
1829        );
1830        let parsed = presolve_parser::parse_file(
1831            "fixtures/0027-declared-state-type-diagnostics/input/InvalidTypedState.tsx",
1832            source,
1833        );
1834        let graph = build_component_graph_for_module(&parsed);
1835        let folded = ConstantFoldingPass.transform(
1836            &build_application_semantic_model_from_component_graph(&graph),
1837        );
1838        let diagnostics = folded
1839            .diagnostics
1840            .iter()
1841            .map(|diagnostic| (diagnostic.code.as_str(), diagnostic.message.as_str()))
1842            .collect::<Vec<_>>();
1843
1844        assert_eq!(diagnostics.len(), 6);
1845        assert!(diagnostics.iter().all(|(code, _)| *code == "PSC1016"));
1846
1847        let provenance = folded.diagnostics[0]
1848            .provenance
1849            .as_ref()
1850            .expect("mismatch diagnostic provenance");
1851        assert_eq!(
1852            provenance.path,
1853            Path::new("fixtures/0027-declared-state-type-diagnostics/input/InvalidTypedState.tsx")
1854        );
1855        assert_eq!(provenance.span.line, 3);
1856        assert_eq!(provenance.span.column, 8);
1857    }
1858
1859    #[test]
1860    fn reports_primitive_declared_state_action_assignment_mismatches() {
1861        let source = include_str!(
1862            "../../../fixtures/0028-primitive-action-type-diagnostics/input/InvalidTypedActions.tsx"
1863        );
1864        let parsed = presolve_parser::parse_file(
1865            "fixtures/0028-primitive-action-type-diagnostics/input/InvalidTypedActions.tsx",
1866            source,
1867        );
1868        let graph = build_component_graph_for_module(&parsed);
1869        let folded = ConstantFoldingPass.transform(
1870            &build_application_semantic_model_from_component_graph(&graph),
1871        );
1872        let diagnostics = folded
1873            .diagnostics
1874            .iter()
1875            .filter(|diagnostic| diagnostic.code == "PSC1017")
1876            .map(|diagnostic| (diagnostic.code.as_str(), diagnostic.message.as_str()))
1877            .collect::<Vec<_>>();
1878
1879        assert_eq!(diagnostics.len(), 6);
1880        assert!(diagnostics.iter().all(|(code, _)| *code == "PSC1017"));
1881        assert!(diagnostics.iter().any(|(_, message)| {
1882            message.contains("state field `status`") && message.contains("assigns `number`")
1883        }));
1884        assert!(diagnostics.iter().any(|(_, message)| {
1885            message.contains("state field `collection`") && message.contains("assigns `tuple`")
1886        }));
1887
1888        let provenance = folded
1889            .diagnostics
1890            .iter()
1891            .find(|diagnostic| diagnostic.code == "PSC1017")
1892            .expect("action mismatch diagnostic")
1893            .provenance
1894            .as_ref()
1895            .expect("action mismatch diagnostic provenance");
1896        assert_eq!(
1897            provenance.path,
1898            Path::new(
1899                "fixtures/0028-primitive-action-type-diagnostics/input/InvalidTypedActions.tsx"
1900            )
1901        );
1902        assert_eq!(provenance.span.line, 11);
1903        assert_eq!(provenance.span.column, 5);
1904    }
1905
1906    #[test]
1907    fn reports_non_boolean_primitive_toggle_actions() {
1908        let source = include_str!(
1909            "../../../fixtures/0029-primitive-toggle-type-diagnostics/input/InvalidTypedToggles.tsx"
1910        );
1911        let parsed = presolve_parser::parse_file(
1912            "fixtures/0029-primitive-toggle-type-diagnostics/input/InvalidTypedToggles.tsx",
1913            source,
1914        );
1915        let graph = build_component_graph_for_module(&parsed);
1916        let folded = ConstantFoldingPass.transform(
1917            &build_application_semantic_model_from_component_graph(&graph),
1918        );
1919        let diagnostics = folded
1920            .diagnostics
1921            .iter()
1922            .map(|diagnostic| (diagnostic.code.as_str(), diagnostic.message.as_str()))
1923            .collect::<Vec<_>>();
1924
1925        assert_eq!(diagnostics.len(), 4);
1926        assert!(diagnostics.iter().all(|(code, _)| *code == "PSC1018"));
1927
1928        let provenance = folded.diagnostics[0]
1929            .provenance
1930            .as_ref()
1931            .expect("toggle diagnostic provenance");
1932        assert_eq!(
1933            provenance.path,
1934            Path::new(
1935                "fixtures/0029-primitive-toggle-type-diagnostics/input/InvalidTypedToggles.tsx"
1936            )
1937        );
1938        assert_eq!(provenance.span.line, 10);
1939        assert_eq!(provenance.span.column, 5);
1940    }
1941
1942    #[test]
1943    fn reports_non_numeric_primitive_increment_and_decrement_actions() {
1944        let source = include_str!(
1945            "../../../fixtures/0030-primitive-numeric-action-type-diagnostics/input/InvalidTypedNumericActions.tsx"
1946        );
1947        let parsed = presolve_parser::parse_file(
1948            "fixtures/0030-primitive-numeric-action-type-diagnostics/input/InvalidTypedNumericActions.tsx",
1949            source,
1950        );
1951        let graph = build_component_graph_for_module(&parsed);
1952        let folded = ConstantFoldingPass.transform(
1953            &build_application_semantic_model_from_component_graph(&graph),
1954        );
1955        let diagnostics = folded
1956            .diagnostics
1957            .iter()
1958            .map(|diagnostic| (diagnostic.code.as_str(), diagnostic.message.as_str()))
1959            .collect::<Vec<_>>();
1960
1961        assert_eq!(diagnostics.len(), 4);
1962        assert!(diagnostics.iter().all(|(code, _)| *code == "PSC1019"));
1963
1964        let provenance = folded.diagnostics[0]
1965            .provenance
1966            .as_ref()
1967            .expect("numeric action diagnostic provenance");
1968        assert_eq!(
1969            provenance.path,
1970            Path::new(
1971                "fixtures/0030-primitive-numeric-action-type-diagnostics/input/InvalidTypedNumericActions.tsx"
1972            )
1973        );
1974        assert_eq!(provenance.span.line, 10);
1975        assert_eq!(provenance.span.column, 5);
1976    }
1977
1978    #[test]
1979    fn reports_compound_numeric_action_target_and_operand_mismatches() {
1980        let source = include_str!(
1981            "../../../fixtures/0031-primitive-compound-action-type-diagnostics/input/InvalidTypedCompoundActions.tsx"
1982        );
1983        let parsed = presolve_parser::parse_file(
1984            "fixtures/0031-primitive-compound-action-type-diagnostics/input/InvalidTypedCompoundActions.tsx",
1985            source,
1986        );
1987        let graph = build_component_graph_for_module(&parsed);
1988        let folded = ConstantFoldingPass.transform(
1989            &build_application_semantic_model_from_component_graph(&graph),
1990        );
1991        let diagnostics = folded
1992            .diagnostics
1993            .iter()
1994            .map(|diagnostic| (diagnostic.code.as_str(), diagnostic.message.as_str()))
1995            .collect::<Vec<_>>();
1996
1997        assert_eq!(diagnostics.len(), 7);
1998        assert_eq!(
1999            diagnostics
2000                .iter()
2001                .filter(|(code, _)| *code == "PSC1020")
2002                .count(),
2003            3
2004        );
2005        assert_eq!(
2006            diagnostics
2007                .iter()
2008                .filter(|(code, _)| *code == "PSC1021")
2009                .count(),
2010            4
2011        );
2012
2013        let provenance = folded.diagnostics[0]
2014            .provenance
2015            .as_ref()
2016            .expect("compound action diagnostic provenance");
2017        assert_eq!(
2018            provenance.path,
2019            Path::new(
2020                "fixtures/0031-primitive-compound-action-type-diagnostics/input/InvalidTypedCompoundActions.tsx"
2021            )
2022        );
2023        assert_eq!(provenance.span.line, 9);
2024        assert_eq!(provenance.span.column, 5);
2025    }
2026
2027    #[test]
2028    fn assembles_application_semantic_model_from_multiple_files() {
2029        let unit = CompilationUnit::parse_sources([
2030            (
2031                "src/Zeta.tsx",
2032                r#"
2033@component("x-zeta")
2034class Zeta extends Component {
2035  render() {
2036    return <div>Zeta</div>;
2037  }
2038}
2039"#,
2040            ),
2041            (
2042                "src/Alpha.tsx",
2043                r#"
2044@component("x-alpha")
2045class Alpha extends Component {
2046  render() {
2047    return <div>Alpha</div>;
2048  }
2049}
2050"#,
2051            ),
2052        ]);
2053
2054        let asm = build_application_semantic_model_for_unit(&unit);
2055
2056        assert_eq!(
2057            unit.files()
2058                .iter()
2059                .map(|file| file.path.to_string_lossy().into_owned())
2060                .collect::<Vec<_>>(),
2061            vec!["src/Alpha.tsx", "src/Zeta.tsx"]
2062        );
2063        assert_eq!(
2064            asm.components
2065                .iter()
2066                .map(|component| component.id.as_str())
2067                .collect::<Vec<_>>(),
2068            vec![
2069                "module:src/Alpha.tsx/component:x-alpha",
2070                "module:src/Zeta.tsx/component:x-zeta"
2071            ]
2072        );
2073        assert!(asm.diagnostics.is_empty());
2074        assert!(validate_application_semantic_model(&asm).is_empty());
2075    }
2076
2077    #[test]
2078    fn component_graph_reports_semantic_errors() {
2079        let source =
2080            include_str!("../../../fixtures/0003-semantic-errors/input/BrokenSemantics.tsx");
2081
2082        let parsed = presolve_parser::parse_file(
2083            "fixtures/0003-semantic-errors/input/BrokenSemantics.tsx",
2084            source,
2085        );
2086
2087        let graph = build_component_graph(&parsed);
2088
2089        let codes = graph
2090            .diagnostics
2091            .iter()
2092            .map(|diagnostic| diagnostic.code.as_str())
2093            .collect::<Vec<_>>();
2094
2095        assert!(codes.contains(&"PSC1001"));
2096        assert!(codes.contains(&"PSC1003"));
2097        assert!(codes.contains(&"PSC1004"));
2098        assert!(graph.references.is_empty());
2099    }
2100
2101    #[test]
2102    fn component_graph_reports_unsupported_event_errors() {
2103        let source = r#"
2104@component("x-counter")
2105class Counter extends Component {
2106  count = state(0);
2107
2108  increment() {
2109    this.count++;
2110  }
2111
2112  render() {
2113    return <button onMouseover={() => this.increment()}>Count: {this.count}</button>;
2114  }
2115}
2116"#;
2117
2118        let parsed = presolve_parser::parse_file("UnsupportedEvent.tsx", source);
2119
2120        let graph = build_component_graph(&parsed);
2121
2122        assert!(graph
2123            .diagnostics
2124            .iter()
2125            .any(|diagnostic| diagnostic.code == "PSC1005"));
2126    }
2127
2128    #[test]
2129    fn component_graph_validates_static_action_parameter_bindings() {
2130        let source = r#"
2131@component("x-parameterized")
2132class Parameterized extends Component {
2133  label = state("Ready");
2134
2135  @action() setLabel(value: string) {
2136    this.label = value;
2137  }
2138
2139  render() {
2140    return <button onClick={() => this.setLabel(1)}>{this.label}</button>;
2141  }
2142}
2143"#;
2144
2145        let parsed = presolve_parser::parse_file("Parameterized.tsx", source);
2146        let graph = build_component_graph(&parsed);
2147        let codes = graph
2148            .diagnostics
2149            .iter()
2150            .map(|diagnostic| diagnostic.code.as_str())
2151            .collect::<Vec<_>>();
2152
2153        assert!(codes.contains(&"PSC1043"));
2154        assert!(!codes.contains(&"PSC1041"));
2155        assert!(!codes.contains(&"PSC1042"));
2156    }
2157
2158    #[test]
2159    fn component_graph_rejects_unbound_action_parameters() {
2160        let source = r#"
2161@component("x-parameterized")
2162class Parameterized extends Component {
2163  label = state("Ready");
2164
2165  @action() setLabel(value: string) {
2166    this.label = value;
2167  }
2168
2169  render() {
2170    return <button onClick={this.setLabel}>{this.label}</button>;
2171  }
2172}
2173"#;
2174
2175        let parsed = presolve_parser::parse_file("UnboundParameterized.tsx", source);
2176        let graph = build_component_graph(&parsed);
2177
2178        assert!(graph
2179            .diagnostics
2180            .iter()
2181            .any(|diagnostic| diagnostic.code == "PSC1042"));
2182    }
2183
2184    #[test]
2185    fn component_graph_requires_action_decorator_for_parameter_state_assignment() {
2186        let source = r#"
2187@component("x-parameterized")
2188class Parameterized extends Component {
2189  label = state("Ready");
2190
2191  setLabel(value: string) {
2192    this.label = value;
2193  }
2194
2195  render() {
2196    return <button onClick={() => this.setLabel("Locked")}>{this.label}</button>;
2197  }
2198}
2199"#;
2200
2201        let parsed = presolve_parser::parse_file("UndecoratedParameterized.tsx", source);
2202        let graph = build_component_graph(&parsed);
2203
2204        assert!(graph.diagnostics.iter().any(|diagnostic| {
2205            diagnostic.code == "PSC1041" && diagnostic.message.contains("requires @action()")
2206        }));
2207    }
2208
2209    #[test]
2210    fn component_graph_rejects_action_parameter_state_type_mismatch() {
2211        let source = r#"
2212@component("x-parameterized")
2213class Parameterized extends Component {
2214  count = state(0);
2215
2216  @action() setCount(value: string) {
2217    this.count = value;
2218  }
2219
2220  render() {
2221    return <button onClick={() => this.setCount("Locked")}>{this.count}</button>;
2222  }
2223}
2224"#;
2225
2226        let parsed = presolve_parser::parse_file("MismatchedParameterized.tsx", source);
2227        let graph = build_component_graph(&parsed);
2228
2229        assert!(graph
2230            .diagnostics
2231            .iter()
2232            .any(|diagnostic| diagnostic.code == "PSC1044"));
2233    }
2234
2235    #[test]
2236    fn component_graph_lowers_serializable_action_local_to_state_assignment() {
2237        let source = r#"
2238@component("x-action-local")
2239class ActionLocal extends Component {
2240  label = state("Ready");
2241
2242  @action() lock() {
2243    const next = "Locked";
2244    this.label = next;
2245  }
2246
2247  render() {
2248    return <button onClick={this.lock}>{this.label}</button>;
2249  }
2250}
2251"#;
2252
2253        let parsed = presolve_parser::parse_file("ActionLocal.tsx", source);
2254        let graph = build_component_graph(&parsed);
2255
2256        assert!(graph.diagnostics.is_empty(), "{:?}", graph.diagnostics);
2257        assert!(matches!(
2258            graph.components[0].actions[0].operation,
2259            StateOperation::Assign(SerializableValue::String(ref value)) if value == "Locked"
2260        ));
2261    }
2262
2263    #[test]
2264    fn component_graph_lowers_structured_serializable_action_local_to_state_assignment() {
2265        let source = r#"
2266@component("x-action-local-record")
2267class ActionLocalRecord extends Component {
2268  profile = state({ name: "Ready", roles: ["reader"] });
2269
2270  @action() promote() {
2271    const next = { name: "Locked", roles: ["writer", "admin"] };
2272    this.profile = next;
2273  }
2274
2275  render() {
2276    return <button onClick={this.promote}>Promote</button>;
2277  }
2278}
2279"#;
2280
2281        let parsed = presolve_parser::parse_file("ActionLocalRecord.tsx", source);
2282        let graph = build_component_graph(&parsed);
2283
2284        assert!(graph.diagnostics.is_empty(), "{:?}", graph.diagnostics);
2285        assert!(matches!(
2286            graph.components[0].actions[0].operation,
2287            StateOperation::Assign(SerializableValue::Object(_))
2288        ));
2289    }
2290
2291    #[test]
2292    fn component_graph_retains_resource_declaration_facts_before_package_resolution() {
2293        let parsed = presolve_parser::parse_file(
2294            "ResourceFact.tsx",
2295            r#"
2296@component("x-resource-fact")
2297class ResourceFact extends Component {
2298  @resource("profile") profile!: string;
2299  render() { return <div>Profile</div>; }
2300}
2301"#,
2302        );
2303        let graph = build_component_graph(&parsed);
2304        let facts = &graph.components[0].resource_declaration_candidates;
2305        assert_eq!(facts.len(), 1);
2306        assert_eq!(facts[0].field, "profile");
2307        assert!(facts[0].decorator_invoked);
2308        assert_eq!(facts[0].decorator_argument_count, 1);
2309        assert_eq!(facts[0].endpoint_designator.as_deref(), Some("profile"));
2310        assert!(graph.diagnostics.is_empty(), "{:?}", graph.diagnostics);
2311    }
2312
2313    #[test]
2314    fn component_graph_retains_and_validates_opaque_action_declarations() {
2315        let parsed = presolve_parser::parse_file(
2316            "OpaqueActions.tsx",
2317            r#"
2318@component("x-opaque-actions")
2319class OpaqueActions extends Component {
2320  @action() @opaque("@acme/analytics", "trackPurchase")
2321  track(): void {}
2322
2323  @action() @opaque("@acme/analytics")
2324  missingExport(): void {}
2325
2326  @opaque("@acme/analytics", "trackPurchase")
2327  missingAction(): void {}
2328
2329  @action() @opaque("@acme/analytics", "trackPurchase")
2330  writesState(): void { this.count++; }
2331
2332  count = state(0);
2333
2334  render() { return <button onClick={this.track}>Buy</button>; }
2335}
2336"#,
2337        );
2338
2339        let graph = build_component_graph(&parsed);
2340        let component = &graph.components[0];
2341        assert_eq!(component.opaque_action_facts.len(), 4);
2342
2343        let track = component
2344            .opaque_action_facts
2345            .iter()
2346            .find(|fact| fact.method_name == "track")
2347            .expect("retained track declaration");
2348        assert_eq!(
2349            track.id.as_str(),
2350            "component:x-opaque-actions/opaque-activation:track"
2351        );
2352        assert_eq!(track.package.as_deref(), Some("@acme/analytics"));
2353        assert_eq!(track.export.as_deref(), Some("trackPurchase"));
2354        assert!(track.is_action);
2355        assert!(track.action_invoked);
2356        assert!(!track.has_body_effects);
2357
2358        assert_eq!(
2359            graph
2360                .diagnostics
2361                .iter()
2362                .filter(|diagnostic| diagnostic.code == "PSC1130")
2363                .count(),
2364            3,
2365            "{:?}",
2366            graph.diagnostics
2367        );
2368    }
2369
2370    #[test]
2371    fn resolves_opaque_action_through_an_integrity_checked_terminal_package_contract() {
2372        let unit = CompilationUnit::parse_sources([(
2373            "src/Checkout.tsx",
2374            r#"
2375import { trackPurchase } from "@acme/analytics";
2376
2377@component("x-checkout")
2378class Checkout extends Component {
2379  @action() @opaque("@acme/analytics", "trackPurchase")
2380  track(): void {}
2381
2382  render() { return <button onClick={this.track}>Buy</button>; }
2383}
2384"#,
2385        )]);
2386        let contract = parse_semantic_package_contract(
2387            r#"{"schema_version":1,"package":"@acme/analytics","version":"1.2.3","integrity":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","exports":{"trackPurchase":{"kind":"opaque","type_signature":"() -> void","runtime_module":"dist/track.js","resume_policy":"cold_fallback","opaque_terminal":{"execution_boundary":"client","resume":"cold_fallback"}}}}"#,
2388        )
2389        .expect("opaque terminal contract");
2390        let mut packages = SemanticPackageResolutionTable::default();
2391        packages
2392            .insert("@acme/analytics".into(), contract)
2393            .expect("unique contract");
2394
2395        let model = build_application_semantic_model_for_unit_with_packages(&unit, &packages);
2396        assert_eq!(model.opaque_action_resolutions.len(), 1);
2397        let resolution = &model.opaque_action_resolutions[0];
2398        assert_eq!(
2399            resolution.activation.as_str(),
2400            "module:src/Checkout.tsx/component:x-checkout/opaque-activation:track"
2401        );
2402        let OpaqueActionResolutionOutcome::Resolved(binding) = &resolution.outcome else {
2403            panic!(
2404                "expected resolved opaque terminal: {:?}",
2405                resolution.outcome
2406            );
2407        };
2408        assert_eq!(binding.package, "@acme/analytics");
2409        assert_eq!(binding.version, "1.2.3");
2410        assert_eq!(binding.export, "trackPurchase");
2411        assert_eq!(binding.runtime_module, "dist/track.js");
2412        assert!(model
2413            .diagnostics
2414            .iter()
2415            .all(|diagnostic| diagnostic.code != "PSC1130" && diagnostic.code != "PSC1131"));
2416    }
2417
2418    #[test]
2419    fn opaque_action_rejects_a_nonopaque_package_export() {
2420        let unit = CompilationUnit::parse_sources([(
2421            "src/Checkout.tsx",
2422            r#"
2423import { format } from "date-kit";
2424
2425@component("x-checkout")
2426class Checkout extends Component {
2427  @action() @opaque("date-kit", "format")
2428  track(): void {}
2429
2430  render() { return <button onClick={this.track}>Buy</button>; }
2431}
2432"#,
2433        )]);
2434        let contract = parse_semantic_package_contract(
2435            r#"{"schema_version":1,"package":"date-kit","version":"1.2.3","integrity":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","exports":{"format":{"kind":"pure","type_signature":"(Date) -> string","runtime_module":"dist/format.js","resume_policy":"input_only"}}}"#,
2436        )
2437        .expect("pure contract");
2438        let mut packages = SemanticPackageResolutionTable::default();
2439        packages.insert("date-kit".into(), contract).unwrap();
2440
2441        let model = build_application_semantic_model_for_unit_with_packages(&unit, &packages);
2442        assert!(matches!(
2443            model.opaque_action_resolutions[0].outcome,
2444            OpaqueActionResolutionOutcome::NonOpaqueBinding {
2445                kind: SemanticPackageKind::Pure,
2446                ..
2447            }
2448        ));
2449        assert!(model
2450            .diagnostics
2451            .iter()
2452            .any(|diagnostic| diagnostic.code == "PSC1131"));
2453    }
2454
2455    #[test]
2456    fn resolves_resource_source_designator_through_integrity_checked_package_contract() {
2457        let unit = CompilationUnit::parse_sources([(
2458            "src/Profile.tsx",
2459            r#"
2460import { loadProfile } from "profile-service";
2461
2462@component("x-profile")
2463class Profile extends Component {
2464  @resource("loadProfile") profile!: Resource<string, string>;
2465  render() { return <div>Profile</div>; }
2466}
2467"#,
2468        )]);
2469        let contract = parse_semantic_package_contract(
2470            r#"{"schema_version":1,"package":"profile-service","version":"1.2.3","integrity":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","exports":{"loadProfile":{"kind":"resource","type_signature":"(ProfileKey) -> Resource<Profile, ProfileError>","runtime_module":"dist/load-profile.js","resume_policy":"snapshot","resource_endpoint":{"execution_boundary":"shared","cancellation":"abort","resume":"snapshot"}}}}"#,
2471        )
2472        .expect("resource contract");
2473        let mut packages = SemanticPackageResolutionTable::default();
2474        packages
2475            .insert("profile-service".into(), contract)
2476            .expect("unique package contract");
2477
2478        let model = build_application_semantic_model_for_unit_with_packages(&unit, &packages);
2479        assert_eq!(model.resource_endpoint_resolutions.len(), 1);
2480        let ResourceEndpointResolutionOutcome::Resolved(endpoint) =
2481            &model.resource_endpoint_resolutions[0].outcome
2482        else {
2483            panic!("expected resolved resource endpoint");
2484        };
2485        assert_eq!(endpoint.local_name, "loadProfile");
2486        assert_eq!(endpoint.package, "profile-service");
2487        assert_eq!(endpoint.version, "1.2.3");
2488        assert_eq!(endpoint.export, "loadProfile");
2489        assert_eq!(
2490            endpoint.endpoint.execution_boundary,
2491            SemanticPackageResourceExecutionBoundary::Shared
2492        );
2493        assert_eq!(model.resource_declarations.len(), 1);
2494        let declaration = model
2495            .resource_declarations
2496            .values()
2497            .next()
2498            .expect("resource declaration");
2499        assert_eq!(declaration.name, "profile");
2500        assert_eq!(declaration.data_type, SemanticType::String);
2501        assert_eq!(declaration.error_type, SemanticType::String);
2502        assert_eq!(
2503            declaration.execution_boundary,
2504            ResourceExecutionBoundary::Shared
2505        );
2506        assert_eq!(model.resource_activations.len(), 1);
2507        assert!(model.resource_activations.values().all(|activation| {
2508            activation.declaration == declaration.id
2509                && activation.state == ResourceLifecycleState::Idle
2510        }));
2511        assert!(
2512            model
2513                .diagnostics
2514                .iter()
2515                .all(|diagnostic| { diagnostic.code != "PSC1046" && diagnostic.code != "PSC1128" }),
2516            "{:?}",
2517            model.diagnostics
2518        );
2519        assert!(matches!(
2520            model
2521                .semantic_types
2522                .assignments
2523                .get(declaration.id.as_semantic_id())
2524                .map(|assignment| &assignment.semantic_type),
2525            Some(SemanticType::Resource(resource))
2526                if resource.data.as_ref() == &SemanticType::String
2527                    && resource.error.as_ref() == &SemanticType::String
2528        ));
2529    }
2530
2531    #[test]
2532    fn lowers_direct_computed_resource_data_projection_with_exact_declaration_identity() {
2533        let unit = CompilationUnit::parse_sources([(
2534            "src/Profile.tsx",
2535            r#"
2536import { loadProfile } from "profile-service";
2537
2538@component("x-profile")
2539class Profile extends Component {
2540  @resource("loadProfile") profile!: Resource<string, string>;
2541  @computed() get profileName(): string | null { return this.profile.data; }
2542  render() { return <div>{this.profileName}</div>; }
2543}
2544"#,
2545        )]);
2546        let contract = parse_semantic_package_contract(
2547            r#"{"schema_version":1,"package":"profile-service","version":"1.2.3","integrity":"sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","exports":{"loadProfile":{"kind":"resource","type_signature":"(ProfileKey) -> Resource<Profile, ProfileError>","runtime_module":"dist/load-profile.js","resume_policy":"snapshot","resource_endpoint":{"execution_boundary":"shared","cancellation":"abort","resume":"snapshot"}}}}"#,
2548        )
2549        .expect("resource contract");
2550        let mut packages = SemanticPackageResolutionTable::default();
2551        packages
2552            .insert("profile-service".into(), contract)
2553            .expect("unique package contract");
2554
2555        let model = build_application_semantic_model_for_unit_with_packages(&unit, &packages);
2556        assert!(model.diagnostics.is_empty(), "{:?}", model.diagnostics);
2557        let declaration = model
2558            .resource_declarations
2559            .values()
2560            .next()
2561            .expect("resource declaration");
2562        let computed = model.components[0].id.computed("profileName");
2563        assert!(model.references.iter().any(|reference| {
2564            reference.kind == SemanticReferenceKind::ComputedResource
2565                && reference.source == computed
2566                && reference.target == *declaration.id.as_semantic_id()
2567        }));
2568        assert_eq!(
2569            model.semantic_types.computed_values[&computed].semantic_type,
2570            SemanticType::Union(vec![SemanticType::Null, SemanticType::String])
2571        );
2572
2573        let ir = lower_components_to_ir(&model);
2574        assert!(ir
2575            .modules
2576            .iter()
2577            .flat_map(|module| &module.functions)
2578            .any(|function| {
2579                function
2580                    .blocks
2581                    .iter()
2582                    .flat_map(|block| &block.instructions)
2583                    .any(|instruction| {
2584                        matches!(
2585                            &instruction.kind,
2586                            IrInstructionKind::LoadResource { declaration: loaded }
2587                                if loaded == declaration.id.as_semantic_id()
2588                        )
2589                    })
2590            }));
2591        let artifact = build_runtime_computed_artifact(&model, &ir);
2592        assert!(artifact.resource_invalidations.iter().any(|invalidation| {
2593            invalidation.declaration == declaration.id.as_str()
2594                && invalidation.dependents == vec![computed.to_string()]
2595        }));
2596        assert!(artifact.evaluations.iter().any(|evaluation| {
2597            evaluation.computed == computed.as_str()
2598                && evaluation.program.instructions.iter().any(|instruction| {
2599                    matches!(
2600                        instruction,
2601                        RuntimeComputedArtifactInstruction::LoadResource { declaration: loaded, .. }
2602                            if loaded == declaration.id.as_str()
2603                    )
2604                })
2605        }));
2606
2607        let chained_unit = CompilationUnit::parse_sources([(
2608            "src/ChainedProfile.tsx",
2609            r#"
2610import { loadProfile } from "profile-service";
2611
2612@component("x-chained-profile")
2613class ChainedProfile extends Component {
2614  @resource("loadProfile") profile!: Resource<string, string>;
2615  @computed() get invalid() { return this.profile.data.length; }
2616  render() { return <div>Profile</div>; }
2617}
2618"#,
2619        )]);
2620        let chained =
2621            build_application_semantic_model_for_unit_with_packages(&chained_unit, &packages);
2622        assert!(chained.diagnostics.iter().any(|diagnostic| {
2623            diagnostic.code == ComputedDiagnosticCode::UnsupportedBody.as_str()
2624                && diagnostic.message.contains("may only directly project")
2625        }));
2626    }
2627
2628    #[test]
2629    fn component_graph_reports_duplicate_event_errors() {
2630        let parsed = presolve_parser::ParsedFile {
2631            path: "DuplicateEvent.tsx".into(),
2632            syntax: presolve_parser::ParsedSourceAst {
2633                source: String::new(),
2634                estree_json: "{}".to_owned(),
2635                span: test_span(),
2636            },
2637            diagnostics: Vec::new(),
2638            imports: Vec::new(),
2639            exports: Vec::new(),
2640            call_expressions: Vec::new(),
2641            type_aliases: Vec::new(),
2642            local_type_bindings: Vec::new(),
2643            local_value_bindings: Vec::new(),
2644            classes: vec![presolve_parser::ParsedClass {
2645                name: "DuplicateEvent".to_string(),
2646                span: test_span(),
2647                heritage: None,
2648                decorators: vec![presolve_parser::ParsedDecorator {
2649                    name: "component".to_string(),
2650                    is_invoked: true,
2651                    arguments: vec![Some("x-duplicate-event".to_string())],
2652                    argument: Some("x-duplicate-event".to_string()),
2653                    argument_count: 1,
2654                    argument_spans: vec![test_span()],
2655                    static_member_argument: None,
2656                    this_member_argument: None,
2657                    validation_rule_expression: None,
2658                    span: test_span(),
2659                }],
2660                properties: Vec::new(),
2661                methods: vec![presolve_parser::ParsedMethod {
2662                    name: "render".to_string(),
2663                    span: test_span(),
2664                    decorators: Vec::new(),
2665                    is_getter: false,
2666                    is_setter: false,
2667                    is_async: false,
2668                    is_static: false,
2669                    jsx_roots: vec![presolve_parser::ParsedJsxNode::Element(
2670                        presolve_parser::ParsedJsxElement {
2671                            name: "button".to_string(),
2672                            name_span: test_span(),
2673                            span: test_span(),
2674                            attributes: Vec::new(),
2675                            event_handlers: vec![
2676                                presolve_parser::ParsedEventHandler {
2677                                    event: "click".to_string(),
2678                                    handler: "this.render".to_string(),
2679                                    arguments: Vec::new(),
2680                                    span: test_span(),
2681                                },
2682                                presolve_parser::ParsedEventHandler {
2683                                    event: "click".to_string(),
2684                                    handler: "this.render".to_string(),
2685                                    arguments: Vec::new(),
2686                                    span: test_span(),
2687                                },
2688                            ],
2689                            children: Vec::new(),
2690                        },
2691                    )],
2692                    bindings: Vec::new(),
2693                    state_updates: Vec::new(),
2694                    local_variables: Vec::new(),
2695                    parameters: Vec::new(),
2696                    return_type_annotation: None,
2697                    return_values: Vec::new(),
2698                    computed_expression: None,
2699                    effect_body: None,
2700                    calls: Vec::new(),
2701                }],
2702            }],
2703        };
2704
2705        let graph = build_component_graph(&parsed);
2706
2707        assert!(graph
2708            .diagnostics
2709            .iter()
2710            .any(|diagnostic| diagnostic.code == "PSC1006"));
2711    }
2712
2713    fn test_span() -> presolve_parser::SourceSpan {
2714        presolve_parser::SourceSpan {
2715            start: 0,
2716            end: 0,
2717            line: 1,
2718            column: 1,
2719        }
2720    }
2721
2722    #[test]
2723    fn builds_increment_action_from_parsed_method_update() {
2724        let source = include_str!("../../../fixtures/0004-nested-jsx/input/NestedCounter.tsx");
2725
2726        let parsed =
2727            presolve_parser::parse_file("fixtures/0004-nested-jsx/input/NestedCounter.tsx", source);
2728
2729        let graph = build_component_graph(&parsed);
2730        let component = graph.components.first().expect("expected component");
2731
2732        assert_eq!(
2733            component.actions,
2734            vec![ComponentAction {
2735                id: SemanticId::component(Some("x-nested-counter"), "NestedCounter")
2736                    .action("increment", 0),
2737                owner: SemanticOwner::entity(
2738                    SemanticId::component(Some("x-nested-counter"), "NestedCounter")
2739                        .method("increment"),
2740                ),
2741                method: "increment".to_string(),
2742                operation: StateOperation::Increment,
2743                field: "count".to_string(),
2744            }]
2745        );
2746    }
2747
2748    #[test]
2749    fn builds_decrement_action_from_parsed_method_update() {
2750        let source =
2751            include_str!("../../../fixtures/0009-decrement-counter/input/DecrementCounter.tsx");
2752
2753        let parsed = presolve_parser::parse_file(
2754            "fixtures/0009-decrement-counter/input/DecrementCounter.tsx",
2755            source,
2756        );
2757
2758        let graph = build_component_graph(&parsed);
2759        let component = graph.components.first().expect("expected component");
2760
2761        assert_eq!(
2762            component.actions,
2763            vec![ComponentAction {
2764                id: SemanticId::component(Some("x-decrement-counter"), "DecrementCounter")
2765                    .action("decrement", 0),
2766                owner: SemanticOwner::entity(
2767                    SemanticId::component(Some("x-decrement-counter"), "DecrementCounter")
2768                        .method("decrement"),
2769                ),
2770                method: "decrement".to_string(),
2771                operation: StateOperation::Decrement,
2772                field: "count".to_string(),
2773            }]
2774        );
2775    }
2776
2777    #[test]
2778    fn builds_add_and_subtract_assign_actions_from_parsed_method_updates() {
2779        let source =
2780            include_str!("../../../fixtures/0010-add-subtract-assign/input/StepCounter.tsx");
2781
2782        let parsed = presolve_parser::parse_file(
2783            "fixtures/0010-add-subtract-assign/input/StepCounter.tsx",
2784            source,
2785        );
2786
2787        let graph = build_component_graph(&parsed);
2788        let component = graph.components.first().expect("expected component");
2789
2790        assert_eq!(
2791            component.actions,
2792            vec![
2793                ComponentAction {
2794                    id: SemanticId::component(Some("x-step-counter"), "StepCounter")
2795                        .action("addTwo", 0),
2796                    owner: SemanticOwner::entity(
2797                        SemanticId::component(Some("x-step-counter"), "StepCounter")
2798                            .method("addTwo"),
2799                    ),
2800                    method: "addTwo".to_string(),
2801                    operation: StateOperation::AddAssign(SerializableValue::Number(
2802                        "2".to_string()
2803                    )),
2804                    field: "count".to_string(),
2805                },
2806                ComponentAction {
2807                    id: SemanticId::component(Some("x-step-counter"), "StepCounter")
2808                        .action("subtractThree", 0),
2809                    owner: SemanticOwner::entity(
2810                        SemanticId::component(Some("x-step-counter"), "StepCounter")
2811                            .method("subtractThree"),
2812                    ),
2813                    method: "subtractThree".to_string(),
2814                    operation: StateOperation::SubtractAssign(SerializableValue::Number(
2815                        "3".to_string()
2816                    )),
2817                    field: "count".to_string(),
2818                }
2819            ]
2820        );
2821    }
2822
2823    #[test]
2824    fn builds_direct_assignment_action_from_parsed_method_update() {
2825        let source =
2826            include_str!("../../../fixtures/0011-direct-assignment/input/ResetCounter.tsx");
2827
2828        let parsed = presolve_parser::parse_file(
2829            "fixtures/0011-direct-assignment/input/ResetCounter.tsx",
2830            source,
2831        );
2832
2833        let graph = build_component_graph(&parsed);
2834        let component = graph.components.first().expect("expected component");
2835
2836        assert_eq!(
2837            component.actions,
2838            vec![ComponentAction {
2839                id: SemanticId::component(Some("x-reset-counter"), "ResetCounter")
2840                    .action("reset", 0),
2841                owner: SemanticOwner::entity(
2842                    SemanticId::component(Some("x-reset-counter"), "ResetCounter").method("reset"),
2843                ),
2844                method: "reset".to_string(),
2845                operation: StateOperation::Assign(SerializableValue::Number("0".to_string())),
2846                field: "count".to_string(),
2847            }]
2848        );
2849    }
2850
2851    #[test]
2852    fn builds_boolean_toggle_action_from_parsed_method_update() {
2853        let source = include_str!("../../../fixtures/0012-boolean-toggle/input/ToggleFlag.tsx");
2854
2855        let parsed = presolve_parser::parse_file(
2856            "fixtures/0012-boolean-toggle/input/ToggleFlag.tsx",
2857            source,
2858        );
2859
2860        let graph = build_component_graph(&parsed);
2861        let component = graph.components.first().expect("expected component");
2862
2863        assert_eq!(
2864            component.actions,
2865            vec![ComponentAction {
2866                id: SemanticId::component(Some("x-toggle-flag"), "ToggleFlag").action("toggle", 0),
2867                owner: SemanticOwner::entity(
2868                    SemanticId::component(Some("x-toggle-flag"), "ToggleFlag").method("toggle"),
2869                ),
2870                method: "toggle".to_string(),
2871                operation: StateOperation::Toggle,
2872                field: "enabled".to_string(),
2873            }]
2874        );
2875    }
2876
2877    #[test]
2878    fn builds_multi_step_actions_from_parsed_method_updates_in_source_order() {
2879        let source =
2880            include_str!("../../../fixtures/0013-multi-step-action/input/BatchActionCounter.tsx");
2881
2882        let parsed = presolve_parser::parse_file(
2883            "fixtures/0013-multi-step-action/input/BatchActionCounter.tsx",
2884            source,
2885        );
2886
2887        let graph = build_component_graph(&parsed);
2888        let component = graph.components.first().expect("expected component");
2889
2890        assert_eq!(
2891            component.actions,
2892            vec![
2893                ComponentAction {
2894                    id:
2895                        SemanticId::component(Some("x-batch-action-counter"), "BatchActionCounter",)
2896                            .action("apply", 0),
2897                    owner:
2898                        SemanticOwner::entity(
2899                            SemanticId::component(
2900                                Some("x-batch-action-counter"),
2901                                "BatchActionCounter",
2902                            )
2903                            .method("apply"),
2904                        ),
2905                    method: "apply".to_string(),
2906                    operation: StateOperation::AddAssign(SerializableValue::Number(
2907                        "2".to_string()
2908                    )),
2909                    field: "count".to_string(),
2910                },
2911                ComponentAction {
2912                    id:
2913                        SemanticId::component(Some("x-batch-action-counter"), "BatchActionCounter",)
2914                            .action("apply", 1),
2915                    owner:
2916                        SemanticOwner::entity(
2917                            SemanticId::component(
2918                                Some("x-batch-action-counter"),
2919                                "BatchActionCounter",
2920                            )
2921                            .method("apply"),
2922                        ),
2923                    method: "apply".to_string(),
2924                    operation: StateOperation::Decrement,
2925                    field: "count".to_string(),
2926                },
2927                ComponentAction {
2928                    id:
2929                        SemanticId::component(Some("x-batch-action-counter"), "BatchActionCounter",)
2930                            .action("apply", 2),
2931                    owner:
2932                        SemanticOwner::entity(
2933                            SemanticId::component(
2934                                Some("x-batch-action-counter"),
2935                                "BatchActionCounter",
2936                            )
2937                            .method("apply"),
2938                        ),
2939                    method: "apply".to_string(),
2940                    operation: StateOperation::Assign(SerializableValue::Number("8".to_string())),
2941                    field: "count".to_string(),
2942                },
2943                ComponentAction {
2944                    id:
2945                        SemanticId::component(Some("x-batch-action-counter"), "BatchActionCounter",)
2946                            .action("apply", 3),
2947                    owner:
2948                        SemanticOwner::entity(
2949                            SemanticId::component(
2950                                Some("x-batch-action-counter"),
2951                                "BatchActionCounter",
2952                            )
2953                            .method("apply"),
2954                        ),
2955                    method: "apply".to_string(),
2956                    operation: StateOperation::Increment,
2957                    field: "count".to_string(),
2958                },
2959                ComponentAction {
2960                    id:
2961                        SemanticId::component(Some("x-batch-action-counter"), "BatchActionCounter",)
2962                            .action("apply", 4),
2963                    owner:
2964                        SemanticOwner::entity(
2965                            SemanticId::component(
2966                                Some("x-batch-action-counter"),
2967                                "BatchActionCounter",
2968                            )
2969                            .method("apply"),
2970                        ),
2971                    method: "apply".to_string(),
2972                    operation: StateOperation::Toggle,
2973                    field: "enabled".to_string(),
2974                }
2975            ]
2976        );
2977    }
2978
2979    #[test]
2980    fn generates_static_html_from_template_graph() {
2981        let source = include_str!("../../../fixtures/0001-source-summary/input/Counter.tsx");
2982
2983        let parsed =
2984            presolve_parser::parse_file("fixtures/0001-source-summary/input/Counter.tsx", source);
2985
2986        let component_graph = build_component_graph(&parsed);
2987        let template_graph = build_template_graph(&component_graph);
2988        let html = generate_static_html(&template_graph);
2989
2990        assert_eq!(
2991            html,
2992            "<button data-presolve-node=\"n0\" data-presolve-on-click=\"this.increment\" data-presolve-bindings=\"this.count\">Count:<!-- presolve-binding:n1:this.count -->0</button>\n"
2993        );
2994    }
2995
2996    #[test]
2997    fn lowers_jsx_html_attribute_aliases_before_html_and_manifest_generation() {
2998        let source = r#"
2999@component("x-attribute-alias")
3000class AttributeAlias extends Component {
3001  target = state("profile-name");
3002  render() {
3003    return <label className={this.target} htmlFor={this.target}>Profile</label>;
3004  }
3005}
3006"#;
3007        let parsed = presolve_parser::parse_file("AttributeAlias.tsx", source);
3008        let component_graph = build_component_graph(&parsed);
3009        let template_graph = build_template_graph(&component_graph);
3010        let html = generate_static_html(&template_graph);
3011        let manifest = build_template_manifest(&component_graph, &template_graph);
3012        let model = build_application_semantic_model_from_component_graph(&component_graph);
3013        let runtime = build_runtime_component_artifact(&model, &model.component_ir_optimization);
3014
3015        assert!(html.contains("class=\"profile-name\""));
3016        assert!(html.contains("for=\"profile-name\""));
3017        assert!(!html.contains("className="));
3018        assert!(!html.contains("htmlFor="));
3019        assert_eq!(
3020            manifest.components[0]
3021                .template
3022                .nodes
3023                .iter()
3024                .filter_map(|node| match node {
3025                    ManifestNode::Binding { attribute, .. } => attribute.as_deref(),
3026                    _ => None,
3027                })
3028                .collect::<Vec<_>>(),
3029            vec!["class", "for"]
3030        );
3031        assert_eq!(
3032            runtime
3033                .ordinary_template_bindings
3034                .iter()
3035                .filter_map(|binding| binding.attribute_name.as_deref())
3036                .collect::<Vec<_>>(),
3037            vec!["class", "for"]
3038        );
3039    }
3040
3041    #[test]
3042    fn preserves_string_state_literals_in_template_outputs() {
3043        let source = include_str!("../../../fixtures/0006-string-state/input/StringGreeting.tsx");
3044
3045        let parsed = presolve_parser::parse_file(
3046            "fixtures/0006-string-state/input/StringGreeting.tsx",
3047            source,
3048        );
3049
3050        let component_graph = build_component_graph(&parsed);
3051        let component = component_graph
3052            .components
3053            .first()
3054            .expect("expected component");
3055
3056        assert_eq!(
3057            component.state_fields[0].initial_value,
3058            Some(SerializableValue::String("Austin & <Zero>".to_string()))
3059        );
3060
3061        let template_graph = build_template_graph(&component_graph);
3062        let html = generate_static_html(&template_graph);
3063
3064        assert_eq!(
3065            html,
3066            "<p data-presolve-node=\"n0\" data-presolve-bindings=\"this.name\">Name:<!-- presolve-binding:n1:this.name -->Austin &amp; &lt;Zero&gt;</p>\n"
3067        );
3068
3069        let manifest = build_template_manifest(&component_graph, &template_graph);
3070
3071        assert_eq!(
3072            manifest.components[0].template.nodes,
3073            vec![
3074                ManifestNode::Element {
3075                    id: "n0".to_string(),
3076                    tag: "p".to_string(),
3077                },
3078                ManifestNode::Binding {
3079                    id: "n1".to_string(),
3080                    expression: "this.name".to_string(),
3081                    initial_value: Some(SerializableValue::String("Austin & <Zero>".to_string())),
3082                    target: None,
3083                    element: None,
3084                    attribute: None,
3085                }
3086            ]
3087        );
3088
3089        let manifest_json = template_manifest_json(&manifest);
3090        let manifest_value: serde_json::Value =
3091            serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
3092
3093        assert_eq!(
3094            manifest_value["components"][0]["template"]["nodes"][1]["initial_value"],
3095            serde_json::json!("Austin & <Zero>")
3096        );
3097    }
3098
3099    #[test]
3100    fn decodes_jsx_text_character_references_before_static_html_escaping() {
3101        let parsed = presolve_parser::parse_file(
3102            "CodeExample.tsx",
3103            r#"
3104@component("x-code-example")
3105class CodeExample extends Component {
3106  render() {
3107    return <pre><code>&lt;button&gt;A &amp; B&#123;ok&#125;&lt;/button&gt;</code></pre>;
3108  }
3109}
3110"#,
3111        );
3112
3113        let component_graph = build_component_graph(&parsed);
3114        assert!(component_graph.diagnostics.is_empty());
3115
3116        let html = generate_static_html(&build_template_graph(&component_graph));
3117
3118        assert!(html.contains("&lt;button&gt;A &amp; B{ok}&lt;/button&gt;"));
3119        assert!(!html.contains("&amp;lt;button&amp;gt;"));
3120        assert!(!html.contains("&amp;#123;"));
3121    }
3122
3123    #[test]
3124    fn preserves_inline_jsx_text_boundaries_in_static_html() {
3125        let parsed = presolve_parser::parse_file(
3126            "CodeExample.tsx",
3127            r#"
3128@component("x-code-example")
3129class CodeExample extends Component {
3130  render() {
3131    return <pre><code><span>import &#123; </span><span className="syntax-type">Component</span><span> &#125; from </span><span>&quot;presolve&quot;</span>;</code></pre>;
3132  }
3133}
3134"#,
3135        );
3136
3137        let component_graph = build_component_graph(&parsed);
3138        assert!(component_graph.diagnostics.is_empty());
3139
3140        let html = generate_static_html(&build_template_graph(&component_graph));
3141
3142        assert!(html.contains("import { </span><span data-presolve-node"));
3143        assert!(html.contains("class=\"syntax-type\">Component</span><span"));
3144        assert!(html.contains("> } from </span><span"));
3145        assert!(html.contains(">\"presolve\"</span>;"));
3146        assert!(!html.contains("data-presolve-bindings"));
3147    }
3148
3149    #[test]
3150    fn preserves_static_jsx_attributes_in_template_outputs() {
3151        let source =
3152            include_str!("../../../fixtures/0014-static-attributes/input/StaticAttributePanel.tsx");
3153
3154        let parsed = presolve_parser::parse_file(
3155            "fixtures/0014-static-attributes/input/StaticAttributePanel.tsx",
3156            source,
3157        );
3158
3159        let component_graph = build_component_graph(&parsed);
3160        assert!(component_graph.diagnostics.is_empty());
3161
3162        let template_graph = build_template_graph(&component_graph);
3163        let root = template_graph.templates[0]
3164            .root
3165            .as_ref()
3166            .expect("expected root");
3167
3168        assert_eq!(root.attributes.len(), 3);
3169        assert_eq!(root.attributes[0].name, "id");
3170        assert_eq!(
3171            root.attributes[0].value,
3172            AttributeValue::Static("panel-root".to_string())
3173        );
3174        assert_eq!(root.attributes[1].name, "aria-label");
3175        assert_eq!(
3176            root.attributes[1].value,
3177            AttributeValue::Static("Status \"Panel\"".to_string())
3178        );
3179        assert_eq!(root.attributes[2].name, "hidden");
3180        assert_eq!(root.attributes[2].value, AttributeValue::Boolean);
3181
3182        let TemplateChild::Element(button) = &root.children[0] else {
3183            panic!("expected button child");
3184        };
3185
3186        assert_eq!(button.attributes.len(), 4);
3187        assert_eq!(button.attributes[0].name, "type");
3188        assert_eq!(
3189            button.attributes[0].value,
3190            AttributeValue::Static("button".to_string())
3191        );
3192        assert_eq!(button.attributes[1].name, "data-mode");
3193        assert_eq!(
3194            button.attributes[1].value,
3195            AttributeValue::Static("safe & sound".to_string())
3196        );
3197        assert_eq!(button.attributes[2].name, "title");
3198        assert_eq!(
3199            button.attributes[2].value,
3200            AttributeValue::Static("Use <carefully>".to_string())
3201        );
3202        assert_eq!(button.attributes[3].name, "data-presolve-bindings");
3203
3204        let html = generate_static_html(&template_graph);
3205
3206        assert_eq!(
3207            html,
3208            "<section data-presolve-node=\"n0\" id=\"panel-root\" aria-label=\"Status &quot;Panel&quot;\" hidden><button data-presolve-node=\"n1\" type=\"button\" data-mode=\"safe &amp; sound\" title=\"Use &lt;carefully&gt;\" data-presolve-bindings=\"this.label\">Label:<!-- presolve-binding:n2:this.label -->Ready</button></section>\n"
3209        );
3210    }
3211
3212    #[test]
3213    fn reports_static_attribute_semantic_errors() {
3214        let source = r#"
3215@component("x-bad-attrs")
3216class BadAttrs extends Component {
3217  disabled = state(false);
3218
3219  render() {
3220    return <button type="button" type="submit" title={label} {...props}>Go</button>;
3221  }
3222}
3223"#;
3224
3225        let parsed = presolve_parser::parse_file("BadAttrs.tsx", source);
3226        let graph = build_component_graph(&parsed);
3227        let codes = graph
3228            .diagnostics
3229            .iter()
3230            .map(|diagnostic| diagnostic.code.as_str())
3231            .collect::<Vec<_>>();
3232
3233        assert!(codes.contains(&"PSC1007"));
3234        assert!(codes.contains(&"PSC1008"));
3235        assert!(codes.contains(&"PSC1009"));
3236    }
3237
3238    #[test]
3239    fn builds_dynamic_attribute_bindings_in_template_outputs() {
3240        let source = include_str!(
3241            "../../../fixtures/0015-dynamic-attributes/input/DynamicAttributeButton.tsx"
3242        );
3243
3244        let parsed = presolve_parser::parse_file(
3245            "fixtures/0015-dynamic-attributes/input/DynamicAttributeButton.tsx",
3246            source,
3247        );
3248
3249        let component_graph = build_component_graph(&parsed);
3250        assert!(component_graph.diagnostics.is_empty());
3251
3252        let template_graph = build_template_graph(&component_graph);
3253        let root = template_graph.templates[0]
3254            .root
3255            .as_ref()
3256            .expect("expected root");
3257
3258        assert_eq!(root.attributes[0].name, "disabled");
3259        assert_eq!(
3260            root.attributes[0].value,
3261            AttributeValue::Binding {
3262                id: TemplateNodeId("n1".to_string()),
3263                expression: "this.disabled".to_string(),
3264                initial_value: Some(SerializableValue::Boolean(false)),
3265            }
3266        );
3267        assert_eq!(root.attributes[1].name, "title");
3268        assert_eq!(
3269            root.attributes[1].value,
3270            AttributeValue::Binding {
3271                id: TemplateNodeId("n2".to_string()),
3272                expression: "this.label".to_string(),
3273                initial_value: Some(SerializableValue::String("Ready".to_string())),
3274            }
3275        );
3276
3277        let html = generate_static_html(&template_graph);
3278
3279        assert_eq!(
3280            html,
3281            "<button data-presolve-node=\"n0\" title=\"Ready\" data-presolve-on-click=\"this.lock\" data-presolve-bindings=\"this.label\">Status:<!-- presolve-binding:n3:this.label -->Ready</button>\n"
3282        );
3283    }
3284
3285    #[test]
3286    fn preserves_boolean_state_literals_in_template_outputs() {
3287        let source = include_str!("../../../fixtures/0007-boolean-state/input/BooleanFlags.tsx");
3288
3289        let parsed = presolve_parser::parse_file(
3290            "fixtures/0007-boolean-state/input/BooleanFlags.tsx",
3291            source,
3292        );
3293
3294        let component_graph = build_component_graph(&parsed);
3295        let component = component_graph
3296            .components
3297            .first()
3298            .expect("expected component");
3299
3300        assert_eq!(
3301            component.state_fields[0].initial_value,
3302            Some(SerializableValue::Boolean(true))
3303        );
3304        assert_eq!(
3305            component.state_fields[1].initial_value,
3306            Some(SerializableValue::Boolean(false))
3307        );
3308
3309        let template_graph = build_template_graph(&component_graph);
3310        let html = generate_static_html(&template_graph);
3311
3312        assert_eq!(
3313            html,
3314            "<section data-presolve-node=\"n0\"><p data-presolve-node=\"n1\" data-presolve-bindings=\"this.enabled\">Enabled:<!-- presolve-binding:n2:this.enabled -->true</p><p data-presolve-node=\"n3\" data-presolve-bindings=\"this.disabled\">Disabled:<!-- presolve-binding:n4:this.disabled -->false</p></section>\n"
3315        );
3316
3317        let manifest = build_template_manifest(&component_graph, &template_graph);
3318
3319        assert_eq!(
3320            manifest.components[0].template.nodes,
3321            vec![
3322                ManifestNode::Element {
3323                    id: "n0".to_string(),
3324                    tag: "section".to_string(),
3325                },
3326                ManifestNode::Element {
3327                    id: "n1".to_string(),
3328                    tag: "p".to_string(),
3329                },
3330                ManifestNode::Binding {
3331                    id: "n2".to_string(),
3332                    expression: "this.enabled".to_string(),
3333                    initial_value: Some(SerializableValue::Boolean(true)),
3334                    target: None,
3335                    element: None,
3336                    attribute: None,
3337                },
3338                ManifestNode::Element {
3339                    id: "n3".to_string(),
3340                    tag: "p".to_string(),
3341                },
3342                ManifestNode::Binding {
3343                    id: "n4".to_string(),
3344                    expression: "this.disabled".to_string(),
3345                    initial_value: Some(SerializableValue::Boolean(false)),
3346                    target: None,
3347                    element: None,
3348                    attribute: None,
3349                }
3350            ]
3351        );
3352
3353        let manifest_json = template_manifest_json(&manifest);
3354        let manifest_value: serde_json::Value =
3355            serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
3356
3357        assert_eq!(
3358            manifest_value["components"][0]["template"]["nodes"][2]["initial_value"],
3359            serde_json::json!(true)
3360        );
3361        assert_eq!(
3362            manifest_value["components"][0]["template"]["nodes"][4]["initial_value"],
3363            serde_json::json!(false)
3364        );
3365    }
3366
3367    #[test]
3368    fn preserves_null_state_literals_in_template_outputs() {
3369        let source = include_str!("../../../fixtures/0008-null-state/input/NullSelection.tsx");
3370
3371        let parsed =
3372            presolve_parser::parse_file("fixtures/0008-null-state/input/NullSelection.tsx", source);
3373
3374        let component_graph = build_component_graph(&parsed);
3375        let component = component_graph
3376            .components
3377            .first()
3378            .expect("expected component");
3379
3380        assert_eq!(
3381            component.state_fields[0].initial_value,
3382            Some(SerializableValue::Null)
3383        );
3384
3385        let template_graph = build_template_graph(&component_graph);
3386        let html = generate_static_html(&template_graph);
3387
3388        assert_eq!(
3389            html,
3390            "<p data-presolve-node=\"n0\" data-presolve-bindings=\"this.selection\">Selection:<!-- presolve-binding:n1:this.selection --></p>\n"
3391        );
3392
3393        let manifest = build_template_manifest(&component_graph, &template_graph);
3394
3395        assert_eq!(
3396            manifest.components[0].template.nodes,
3397            vec![
3398                ManifestNode::Element {
3399                    id: "n0".to_string(),
3400                    tag: "p".to_string(),
3401                },
3402                ManifestNode::Binding {
3403                    id: "n1".to_string(),
3404                    expression: "this.selection".to_string(),
3405                    initial_value: Some(SerializableValue::Null),
3406                    target: None,
3407                    element: None,
3408                    attribute: None,
3409                }
3410            ]
3411        );
3412
3413        let manifest_json = template_manifest_json(&manifest);
3414        let manifest_value: serde_json::Value =
3415            serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
3416
3417        assert_eq!(
3418            manifest_value["components"][0]["template"]["nodes"][1]["initial_value"],
3419            serde_json::Value::Null
3420        );
3421    }
3422
3423    #[test]
3424    fn builds_template_graph_from_component_graph() {
3425        let source = include_str!("../../../fixtures/0001-source-summary/input/Counter.tsx");
3426
3427        let parsed =
3428            presolve_parser::parse_file("fixtures/0001-source-summary/input/Counter.tsx", source);
3429
3430        let component_graph = build_component_graph(&parsed);
3431        let template_graph = build_template_graph(&component_graph);
3432
3433        assert_eq!(template_graph.templates.len(), 1);
3434
3435        let template = &template_graph.templates[0];
3436        assert_eq!(template.component_name, "Counter");
3437        assert_eq!(template.id.as_str(), "component:x-counter/template:render");
3438        assert_eq!(
3439            template.provenance.path,
3440            Path::new("fixtures/0001-source-summary/input/Counter.tsx")
3441        );
3442        assert_eq!(template.provenance.span.line, 10);
3443        assert_eq!(
3444            template.owner,
3445            SemanticOwner::entity(SemanticId::component(Some("x-counter"), "Counter"))
3446        );
3447
3448        let root = template.root.as_ref().expect("expected template root");
3449
3450        assert_eq!(root.id.0, "n0");
3451        assert_eq!(root.tag_name, "button");
3452        assert_eq!(root.span.line, 12);
3453        assert_eq!(root.span.column, 7);
3454
3455        assert_eq!(root.attributes.len(), 2);
3456        assert_eq!(root.attributes[0].name, "data-presolve-on-click");
3457        assert_eq!(
3458            root.attributes[0].span.expect("expected event span").line,
3459            12
3460        );
3461        assert_eq!(
3462            root.attributes[0].span.expect("expected event span").column,
3463            15
3464        );
3465        assert_eq!(
3466            root.attributes[0].value,
3467            AttributeValue::EventHandler {
3468                event: "click".to_string(),
3469                handler: "this.increment".to_string(),
3470                arguments: Vec::new(),
3471            }
3472        );
3473
3474        assert_eq!(root.attributes[1].name, "data-presolve-bindings");
3475        assert_eq!(root.attributes[1].span, None);
3476        assert_eq!(
3477            root.attributes[1].value,
3478            AttributeValue::BindingList(vec!["this.count".to_string()])
3479        );
3480
3481        assert_eq!(root.children.len(), 2);
3482        let TemplateChild::Text { value, span } = &root.children[0] else {
3483            panic!("expected text child");
3484        };
3485        assert_eq!(value, "Count:");
3486        assert_eq!(span.line, 13);
3487        assert_eq!(span.column, 9);
3488
3489        let TemplateChild::Binding {
3490            id,
3491            expression,
3492            initial_value,
3493            span,
3494        } = &root.children[1]
3495        else {
3496            panic!("expected binding child");
3497        };
3498        assert_eq!(id.0, "n1");
3499        assert_eq!(expression, "this.count");
3500        assert_eq!(
3501            initial_value,
3502            &Some(SerializableValue::Number("0".to_string()))
3503        );
3504        assert_eq!(span.line, 13);
3505        assert_eq!(span.column, 16);
3506    }
3507
3508    #[test]
3509    fn semantic_ids_are_stable_when_component_declaration_order_changes() {
3510        let alpha = r#"
3511@component("x-alpha")
3512class Alpha extends Component {
3513  count = state(0);
3514
3515  increment() {
3516    this.count++;
3517  }
3518
3519  render() {
3520    return <p>{this.count}</p>;
3521  }
3522}
3523"#;
3524        let beta = r#"
3525@component("x-beta")
3526class Beta extends Component {
3527  enabled = state(false);
3528
3529  toggle() {
3530    this.enabled = !this.enabled;
3531  }
3532
3533  render() {
3534    return <p>{this.enabled}</p>;
3535  }
3536}
3537"#;
3538
3539        let ids_for = |source: &str| {
3540            let parsed = presolve_parser::parse_file("App.tsx", source);
3541            let component_graph = build_component_graph(&parsed);
3542            let template_graph = build_template_graph(&component_graph);
3543            let template_ids = template_graph
3544                .templates
3545                .iter()
3546                .map(|template| (template.component_name.as_str(), template.id.to_string()))
3547                .collect::<BTreeMap<_, _>>();
3548
3549            component_graph
3550                .components
3551                .iter()
3552                .map(|component| {
3553                    let ids = component
3554                        .state_fields
3555                        .iter()
3556                        .map(|field| field.id.to_string())
3557                        .chain(component.methods.iter().map(|method| method.id.to_string()))
3558                        .chain(component.actions.iter().map(|action| action.id.to_string()))
3559                        .chain(std::iter::once(
3560                            template_ids
3561                                .get(component.class_name.as_str())
3562                                .expect("expected component template")
3563                                .clone(),
3564                        ))
3565                        .collect::<Vec<_>>();
3566
3567                    (
3568                        component.class_name.clone(),
3569                        (component.id.to_string(), ids),
3570                    )
3571                })
3572                .collect::<BTreeMap<_, _>>()
3573        };
3574
3575        assert_eq!(
3576            ids_for(&format!("{alpha}\n{beta}")),
3577            ids_for(&format!("{beta}\n{alpha}"))
3578        );
3579    }
3580
3581    #[test]
3582    fn carries_source_spans_into_nested_template_nodes() {
3583        let source = include_str!("../../../fixtures/0004-nested-jsx/input/NestedCounter.tsx");
3584
3585        let parsed =
3586            presolve_parser::parse_file("fixtures/0004-nested-jsx/input/NestedCounter.tsx", source);
3587
3588        let component_graph = build_component_graph(&parsed);
3589        let template_graph = build_template_graph(&component_graph);
3590        let root = template_graph.templates[0]
3591            .root
3592            .as_ref()
3593            .expect("expected root");
3594
3595        assert_eq!(root.tag_name, "section");
3596        assert_eq!(root.span.line, 12);
3597        assert_eq!(root.span.column, 7);
3598
3599        let TemplateChild::Element(button) = &root.children[0] else {
3600            panic!("expected nested button");
3601        };
3602
3603        assert_eq!(button.tag_name, "button");
3604        assert_eq!(button.span.line, 13);
3605        assert_eq!(button.span.column, 9);
3606        assert_eq!(button.attributes[0].name, "data-presolve-on-click");
3607        assert_eq!(
3608            button.attributes[0].span.expect("expected event span").line,
3609            13
3610        );
3611        assert_eq!(
3612            button.attributes[0]
3613                .span
3614                .expect("expected event span")
3615                .column,
3616            17
3617        );
3618        assert_eq!(button.attributes[1].name, "data-presolve-bindings");
3619        assert_eq!(button.attributes[1].span, None);
3620
3621        let TemplateChild::Text { value, span } = &button.children[0] else {
3622            panic!("expected nested text");
3623        };
3624        assert_eq!(value, "Count: ");
3625        assert_eq!(span.line, 13);
3626        assert_eq!(span.column, 50);
3627
3628        let TemplateChild::Binding {
3629            expression, span, ..
3630        } = &button.children[1]
3631        else {
3632            panic!("expected nested binding");
3633        };
3634        assert_eq!(expression, "this.count");
3635        assert_eq!(span.line, 13);
3636        assert_eq!(span.column, 57);
3637    }
3638
3639    #[test]
3640    fn preserves_fragment_siblings_without_wrapper_elements() {
3641        let source = include_str!("../../../fixtures/0016-fragments/input/FragmentPanel.tsx");
3642
3643        let parsed =
3644            presolve_parser::parse_file("fixtures/0016-fragments/input/FragmentPanel.tsx", source);
3645
3646        let component_graph = build_component_graph(&parsed);
3647        assert!(component_graph.diagnostics.is_empty());
3648
3649        let template_graph = build_template_graph(&component_graph);
3650        let template = &template_graph.templates[0];
3651        let fragment = template
3652            .root_fragment
3653            .as_ref()
3654            .expect("expected fragment root");
3655
3656        assert!(template.root.is_none());
3657        assert_eq!(fragment.id.0, "n0");
3658        assert_eq!(fragment.children.len(), 2);
3659
3660        let TemplateChild::Element(heading) = &fragment.children[0] else {
3661            panic!("expected heading child");
3662        };
3663        assert_eq!(heading.id.0, "n1");
3664        assert_eq!(heading.tag_name, "h1");
3665
3666        let TemplateChild::Fragment(nested) = &fragment.children[1] else {
3667            panic!("expected nested fragment child");
3668        };
3669        assert_eq!(nested.id.0, "n2");
3670
3671        let TemplateChild::Element(paragraph) = &nested.children[0] else {
3672            panic!("expected paragraph child");
3673        };
3674        assert_eq!(paragraph.id.0, "n3");
3675        assert_eq!(paragraph.tag_name, "p");
3676
3677        let html = generate_static_html(&template_graph);
3678        assert_eq!(
3679            html,
3680            "<h1 data-presolve-node=\"n1\">Title</h1><p data-presolve-node=\"n3\" data-presolve-bindings=\"this.label\">Status: <!-- presolve-binding:n4:this.label -->Ready</p><span data-presolve-node=\"n5\">Done</span>\n"
3681        );
3682
3683        let manifest = build_template_manifest(&component_graph, &template_graph);
3684        assert_eq!(
3685            manifest.components[0].template.nodes,
3686            vec![
3687                ManifestNode::Element {
3688                    id: "n1".to_string(),
3689                    tag: "h1".to_string(),
3690                },
3691                ManifestNode::Element {
3692                    id: "n3".to_string(),
3693                    tag: "p".to_string(),
3694                },
3695                ManifestNode::Binding {
3696                    id: "n4".to_string(),
3697                    expression: "this.label".to_string(),
3698                    initial_value: Some(SerializableValue::String("Ready".to_string())),
3699                    target: None,
3700                    element: None,
3701                    attribute: None,
3702                },
3703                ManifestNode::Element {
3704                    id: "n5".to_string(),
3705                    tag: "span".to_string(),
3706                },
3707            ]
3708        );
3709    }
3710
3711    #[test]
3712    fn builds_conditional_template_boundaries_and_manifest_branch_html() {
3713        let source = include_str!(
3714            "../../../fixtures/0017-conditional-rendering/input/ConditionalStatus.tsx"
3715        );
3716
3717        let parsed = presolve_parser::parse_file(
3718            "fixtures/0017-conditional-rendering/input/ConditionalStatus.tsx",
3719            source,
3720        );
3721
3722        let component_graph = build_component_graph(&parsed);
3723        assert!(component_graph.diagnostics.is_empty());
3724
3725        let template_graph = build_template_graph(&component_graph);
3726        let root = template_graph.templates[0]
3727            .root
3728            .as_ref()
3729            .expect("expected root element");
3730
3731        assert_eq!(root.id.0, "n0");
3732        assert_eq!(root.attributes[1].name, "data-presolve-bindings");
3733
3734        let TemplateChild::Conditional(conditional) = &root.children[0] else {
3735            panic!("expected conditional child");
3736        };
3737
3738        assert_eq!(conditional.id.0, "n1");
3739        assert_eq!(conditional.start_id.0, "n2");
3740        assert_eq!(conditional.end_id.0, "n3");
3741        assert_eq!(conditional.condition, "this.enabled");
3742        assert_eq!(
3743            conditional.initial_value,
3744            Some(SerializableValue::Boolean(true))
3745        );
3746
3747        let TemplateChild::Element(when_true) = &conditional.when_true[0] else {
3748            panic!("expected true branch element");
3749        };
3750        assert_eq!(when_true.id.0, "n4");
3751        assert_eq!(when_true.tag_name, "span");
3752
3753        let TemplateChild::Element(when_false) = &conditional.when_false[0] else {
3754            panic!("expected false branch element");
3755        };
3756        assert_eq!(when_false.id.0, "n5");
3757        assert_eq!(when_false.tag_name, "span");
3758
3759        let html = generate_static_html(&template_graph);
3760        assert_eq!(
3761            html,
3762            "<button data-presolve-node=\"n0\" data-presolve-on-click=\"this.toggle\" data-presolve-bindings=\"this.enabled\"><!-- presolve-conditional-start:n2:this.enabled --><span data-presolve-node=\"n4\">On</span><!-- presolve-conditional-end:n3 --></button>\n"
3763        );
3764
3765        let manifest = build_template_manifest(&component_graph, &template_graph);
3766        assert_eq!(
3767            manifest.components[0].template.nodes,
3768            vec![
3769                ManifestNode::Element {
3770                    id: "n0".to_string(),
3771                    tag: "button".to_string(),
3772                },
3773                ManifestNode::Conditional {
3774                    id: "n1".to_string(),
3775                    start: "n2".to_string(),
3776                    end: "n3".to_string(),
3777                    condition: "this.enabled".to_string(),
3778                    initial_value: Some(SerializableValue::Boolean(true)),
3779                    when_true_html: "<span data-presolve-node=\"n4\">On</span>".to_string(),
3780                    when_false_html: "<span data-presolve-node=\"n5\">Off</span>".to_string(),
3781                },
3782            ]
3783        );
3784    }
3785
3786    #[test]
3787    fn builds_logical_and_conditional_with_empty_false_branch() {
3788        let source = include_str!(
3789            "../../../fixtures/0018-logical-and-conditional/input/LogicalAndStatus.tsx"
3790        );
3791
3792        let parsed = presolve_parser::parse_file(
3793            "fixtures/0018-logical-and-conditional/input/LogicalAndStatus.tsx",
3794            source,
3795        );
3796
3797        let component_graph = build_component_graph(&parsed);
3798        assert!(component_graph.diagnostics.is_empty());
3799
3800        let template_graph = build_template_graph(&component_graph);
3801        let root = template_graph.templates[0]
3802            .root
3803            .as_ref()
3804            .expect("expected root element");
3805
3806        let TemplateChild::Conditional(conditional) = &root.children[0] else {
3807            panic!("expected conditional child");
3808        };
3809
3810        assert_eq!(conditional.when_true.len(), 1);
3811        assert!(conditional.when_false.is_empty());
3812
3813        let manifest = build_template_manifest(&component_graph, &template_graph);
3814        assert_eq!(
3815            manifest.components[0].template.nodes[1],
3816            ManifestNode::Conditional {
3817                id: "n1".to_string(),
3818                start: "n2".to_string(),
3819                end: "n3".to_string(),
3820                condition: "this.enabled".to_string(),
3821                initial_value: Some(SerializableValue::Boolean(true)),
3822                when_true_html: "<span data-presolve-node=\"n4\">On</span>".to_string(),
3823                when_false_html: String::new(),
3824            }
3825        );
3826    }
3827
3828    #[test]
3829    fn builds_empty_keyed_list_from_a_serializable_array() {
3830        let source =
3831            include_str!("../../../fixtures/0019-keyed-list-semantics/input/KeyedList.tsx");
3832
3833        let parsed = presolve_parser::parse_file(
3834            "fixtures/0019-keyed-list-semantics/input/KeyedList.tsx",
3835            source,
3836        );
3837
3838        let component_graph = build_component_graph(&parsed);
3839        assert!(component_graph.diagnostics.is_empty());
3840
3841        let template_graph = build_template_graph(&component_graph);
3842        let root = template_graph.templates[0]
3843            .root
3844            .as_ref()
3845            .expect("expected root element");
3846
3847        assert_eq!(root.id.0, "n0");
3848        assert_eq!(root.attributes[0].name, "data-presolve-bindings");
3849
3850        let TemplateChild::List(list) = &root.children[0] else {
3851            panic!("expected keyed list child");
3852        };
3853
3854        assert_eq!(list.id.0, "n1");
3855        assert_eq!(list.start_id.0, "n2");
3856        assert_eq!(list.end_id.0, "n3");
3857        assert_eq!(list.iterable, "this.items");
3858        assert_eq!(
3859            list.initial_value,
3860            Some(SerializableValue::Array(Vec::new()))
3861        );
3862        assert_eq!(list.item_variable, "item");
3863        assert_eq!(list.index_variable.as_deref(), Some("index"));
3864        assert_eq!(list.key_expression, "item.id");
3865
3866        let TemplateChild::Element(item_template) = &list.item_template[0] else {
3867            panic!("expected list item element");
3868        };
3869        assert_eq!(item_template.id.0, "n4");
3870        assert_eq!(item_template.tag_name, "li");
3871
3872        assert_eq!(
3873            generate_static_html(&template_graph),
3874            "<ul data-presolve-node=\"n0\" data-presolve-bindings=\"this.items\"><!-- presolve-list-start:n2:this.items --><!-- presolve-list-end:n3 --></ul>\n"
3875        );
3876
3877        let manifest = build_template_manifest(&component_graph, &template_graph);
3878        assert_eq!(
3879            manifest.components[0].template.nodes,
3880            vec![
3881                ManifestNode::Element {
3882                    id: "n0".to_string(),
3883                    tag: "ul".to_string(),
3884                },
3885                ManifestNode::List {
3886                    id: "n1".to_string(),
3887                    start: "n2".to_string(),
3888                    end: "n3".to_string(),
3889                    iterable: "this.items".to_string(),
3890                    initial_value: Some(SerializableValue::Array(Vec::new())),
3891                    item_variable: "item".to_string(),
3892                    index_variable: Some("index".to_string()),
3893                    key_expression: "item.id".to_string(),
3894                    item_root: "n4".to_string(),
3895                    item_template_html: "<li data-presolve-node=\"n4:__ez_list_key__\" data-presolve-bindings=\"index,item.label\"><!-- presolve-binding:n5:__ez_list_key__:index -->__ez_list_index__<!-- presolve-list-binding-end:n5:__ez_list_key__ -->: <!-- presolve-binding:n6:__ez_list_key__:item.label --><!-- presolve-list-binding-end:n6:__ez_list_key__ --></li>".to_string(),
3896                },
3897            ]
3898        );
3899    }
3900
3901    #[test]
3902    fn preserves_recursive_object_values_in_component_and_manifest_models() {
3903        let source = include_str!(
3904            "../../../fixtures/0023-recursive-object-values/input/RecursiveObjectValues.tsx"
3905        );
3906        let parsed = presolve_parser::parse_file(
3907            "fixtures/0023-recursive-object-values/input/RecursiveObjectValues.tsx",
3908            source,
3909        );
3910
3911        let component_graph = build_component_graph(&parsed);
3912        assert!(component_graph.diagnostics.is_empty());
3913
3914        let profile = SerializableValue::Object(BTreeMap::from([
3915            (
3916                "name".to_string(),
3917                SerializableValue::String("North".to_string()),
3918            ),
3919            (
3920                "settings".to_string(),
3921                SerializableValue::Object(BTreeMap::from([
3922                    ("enabled".to_string(), SerializableValue::Boolean(true)),
3923                    (
3924                        "tags".to_string(),
3925                        SerializableValue::Array(vec![
3926                            SerializableValue::String("compiler".to_string()),
3927                            SerializableValue::Object(BTreeMap::from([
3928                                (
3929                                    "name".to_string(),
3930                                    SerializableValue::String("runtime".to_string()),
3931                                ),
3932                                (
3933                                    "rank".to_string(),
3934                                    SerializableValue::Number("2".to_string()),
3935                                ),
3936                            ])),
3937                        ]),
3938                    ),
3939                ])),
3940            ),
3941        ]));
3942        assert_eq!(
3943            component_graph.components[0].state_fields[0].initial_value,
3944            Some(profile.clone())
3945        );
3946
3947        let template_graph = build_template_graph(&component_graph);
3948        let manifest = build_template_manifest(&component_graph, &template_graph);
3949        assert_eq!(
3950            manifest.components[0].template.nodes[3],
3951            ManifestNode::Binding {
3952                id: "n3".to_string(),
3953                expression: "this.profile".to_string(),
3954                initial_value: Some(profile),
3955                target: None,
3956                element: None,
3957                attribute: None,
3958            }
3959        );
3960        assert!(matches!(
3961            manifest.components[0].actions[0].operand,
3962            Some(SerializableValue::Object(_))
3963        ));
3964    }
3965
3966    #[test]
3967    fn renders_static_object_list_members_and_keys() {
3968        let source = include_str!(
3969            "../../../fixtures/0024-static-object-keyed-list/input/StaticObjectKeyedList.tsx"
3970        );
3971        let parsed = presolve_parser::parse_file(
3972            "fixtures/0024-static-object-keyed-list/input/StaticObjectKeyedList.tsx",
3973            source,
3974        );
3975        let component_graph = build_component_graph(&parsed);
3976        assert!(component_graph.diagnostics.is_empty());
3977
3978        let template_graph = build_template_graph(&component_graph);
3979        assert_eq!(
3980            generate_static_html(&template_graph),
3981            "<ol data-presolve-node=\"n0\" data-presolve-bindings=\"this.items\"><!-- presolve-list-start:n2:this.items --><li data-presolve-node=\"n4:north\" data-presolve-bindings=\"index,item.label,item.details.region\"><!-- presolve-binding:n5:north:index -->0<!-- presolve-list-binding-end:n5:north -->: <!-- presolve-binding:n6:north:item.label -->North<!-- presolve-list-binding-end:n6:north --> (<!-- presolve-binding:n7:north:item.details.region -->west<!-- presolve-list-binding-end:n7:north -->)</li><li data-presolve-node=\"n4:south\" data-presolve-bindings=\"index,item.label,item.details.region\"><!-- presolve-binding:n5:south:index -->1<!-- presolve-list-binding-end:n5:south -->: <!-- presolve-binding:n6:south:item.label -->South<!-- presolve-list-binding-end:n6:south --> (<!-- presolve-binding:n7:south:item.details.region -->east<!-- presolve-list-binding-end:n7:south -->)</li><!-- presolve-list-end:n3 --></ol>\n"
3982        );
3983    }
3984
3985    #[test]
3986    fn renders_initial_keyed_list_items_from_a_serializable_array() {
3987        let source =
3988            include_str!("../../../fixtures/0020-static-keyed-list/input/StaticKeyedList.tsx");
3989
3990        let parsed = presolve_parser::parse_file(
3991            "fixtures/0020-static-keyed-list/input/StaticKeyedList.tsx",
3992            source,
3993        );
3994        let component_graph = build_component_graph(&parsed);
3995        assert!(component_graph.diagnostics.is_empty());
3996
3997        let template_graph = build_template_graph(&component_graph);
3998        let root = template_graph.templates[0]
3999            .root
4000            .as_ref()
4001            .expect("expected root element");
4002        let TemplateChild::List(list) = &root.children[0] else {
4003            panic!("expected keyed list child");
4004        };
4005
4006        assert_eq!(
4007            list.initial_value,
4008            Some(SerializableValue::Array(vec![
4009                SerializableValue::String("North".to_string()),
4010                SerializableValue::String("South".to_string()),
4011            ]))
4012        );
4013
4014        assert_eq!(
4015            generate_static_html(&template_graph),
4016            "<ol data-presolve-node=\"n0\" data-presolve-bindings=\"this.labels\"><!-- presolve-list-start:n2:this.labels --><li data-presolve-node=\"n4:North\" data-presolve-bindings=\"index,label\"><!-- presolve-binding:n5:North:index -->0<!-- presolve-list-binding-end:n5:North -->: <!-- presolve-binding:n6:North:label -->North<!-- presolve-list-binding-end:n6:North --></li><li data-presolve-node=\"n4:South\" data-presolve-bindings=\"index,label\"><!-- presolve-binding:n5:South:index -->1<!-- presolve-list-binding-end:n5:South -->: <!-- presolve-binding:n6:South:label -->South<!-- presolve-list-binding-end:n6:South --></li><!-- presolve-list-end:n3 --></ol>\n"
4017        );
4018
4019        let manifest = build_template_manifest(&component_graph, &template_graph);
4020        assert_eq!(
4021            manifest.components[0].template.nodes,
4022            vec![
4023                ManifestNode::Element {
4024                    id: "n0".to_string(),
4025                    tag: "ol".to_string(),
4026                },
4027                ManifestNode::List {
4028                    id: "n1".to_string(),
4029                    start: "n2".to_string(),
4030                    end: "n3".to_string(),
4031                    iterable: "this.labels".to_string(),
4032                    initial_value: Some(SerializableValue::Array(vec![
4033                        SerializableValue::String("North".to_string()),
4034                        SerializableValue::String("South".to_string()),
4035                    ])),
4036                    item_variable: "label".to_string(),
4037                    index_variable: Some("index".to_string()),
4038                    key_expression: "label".to_string(),
4039                    item_root: "n4".to_string(),
4040                    item_template_html: "<li data-presolve-node=\"n4:__ez_list_key__\" data-presolve-bindings=\"index,label\"><!-- presolve-binding:n5:__ez_list_key__:index -->__ez_list_index__<!-- presolve-list-binding-end:n5:__ez_list_key__ -->: <!-- presolve-binding:n6:__ez_list_key__:label -->__ez_list_item__<!-- presolve-list-binding-end:n6:__ez_list_key__ --></li>".to_string(),
4041                },
4042            ]
4043        );
4044    }
4045
4046    #[test]
4047    fn builds_template_manifest_for_nested_jsx() {
4048        let source = include_str!("../../../fixtures/0004-nested-jsx/input/NestedCounter.tsx");
4049
4050        let parsed =
4051            presolve_parser::parse_file("fixtures/0004-nested-jsx/input/NestedCounter.tsx", source);
4052
4053        let component_graph = build_component_graph(&parsed);
4054        let template_graph = build_template_graph(&component_graph);
4055        let manifest = build_template_manifest(&component_graph, &template_graph);
4056
4057        assert_eq!(manifest.schema_version, 1);
4058        assert_eq!(manifest.components.len(), 1);
4059
4060        let component = &manifest.components[0];
4061        assert_eq!(component.name, "NestedCounter");
4062
4063        assert_eq!(
4064            component.template.nodes,
4065            vec![
4066                ManifestNode::Element {
4067                    id: "n0".to_string(),
4068                    tag: "section".to_string(),
4069                },
4070                ManifestNode::Element {
4071                    id: "n1".to_string(),
4072                    tag: "button".to_string(),
4073                },
4074                ManifestNode::Binding {
4075                    id: "n2".to_string(),
4076                    expression: "this.count".to_string(),
4077                    initial_value: Some(SerializableValue::Number("0".to_string())),
4078                    target: None,
4079                    element: None,
4080                    attribute: None,
4081                }
4082            ]
4083        );
4084
4085        assert_eq!(
4086            component.template.events,
4087            vec![ManifestEvent {
4088                node: "n1".to_string(),
4089                kind: None,
4090                event: "click".to_string(),
4091                handler: "this.increment".to_string(),
4092                arguments: Vec::new(),
4093                method_id: None,
4094                action_batch_id: None,
4095            }]
4096        );
4097
4098        assert_eq!(
4099            component.actions,
4100            vec![ManifestAction {
4101                method: "increment".to_string(),
4102                method_id: None,
4103                action_batch_id: None,
4104                operation: ManifestOperation::Increment,
4105                field: "count".to_string(),
4106                storage_id: None,
4107                operand: None,
4108            }]
4109        );
4110
4111        let manifest_json = template_manifest_json(&manifest);
4112        let manifest_value: serde_json::Value =
4113            serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4114
4115        assert_eq!(manifest_value["schema_version"], serde_json::json!(1));
4116    }
4117
4118    #[test]
4119    fn builds_template_manifest_for_decrement_action() {
4120        let source =
4121            include_str!("../../../fixtures/0009-decrement-counter/input/DecrementCounter.tsx");
4122
4123        let parsed = presolve_parser::parse_file(
4124            "fixtures/0009-decrement-counter/input/DecrementCounter.tsx",
4125            source,
4126        );
4127
4128        let component_graph = build_component_graph(&parsed);
4129        let template_graph = build_template_graph(&component_graph);
4130        let manifest = build_template_manifest(&component_graph, &template_graph);
4131
4132        assert_eq!(
4133            manifest.components[0].actions,
4134            vec![ManifestAction {
4135                method: "decrement".to_string(),
4136                method_id: None,
4137                action_batch_id: None,
4138                operation: ManifestOperation::Decrement,
4139                field: "count".to_string(),
4140                storage_id: None,
4141                operand: None,
4142            }]
4143        );
4144
4145        let manifest_json = template_manifest_json(&manifest);
4146        let manifest_value: serde_json::Value =
4147            serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4148
4149        assert_eq!(
4150            manifest_value["components"][0]["actions"][0]["operation"],
4151            serde_json::json!("decrement")
4152        );
4153    }
4154
4155    #[test]
4156    fn builds_template_manifest_for_add_and_subtract_assign_actions() {
4157        let source =
4158            include_str!("../../../fixtures/0010-add-subtract-assign/input/StepCounter.tsx");
4159
4160        let parsed = presolve_parser::parse_file(
4161            "fixtures/0010-add-subtract-assign/input/StepCounter.tsx",
4162            source,
4163        );
4164
4165        let component_graph = build_component_graph(&parsed);
4166        let template_graph = build_template_graph(&component_graph);
4167        let manifest = build_template_manifest(&component_graph, &template_graph);
4168
4169        assert_eq!(
4170            manifest.components[0].actions,
4171            vec![
4172                ManifestAction {
4173                    method: "addTwo".to_string(),
4174                    method_id: None,
4175                    action_batch_id: None,
4176                    operation: ManifestOperation::AddAssign,
4177                    field: "count".to_string(),
4178                    storage_id: None,
4179                    operand: Some(SerializableValue::Number("2".to_string())),
4180                },
4181                ManifestAction {
4182                    method: "subtractThree".to_string(),
4183                    method_id: None,
4184                    action_batch_id: None,
4185                    operation: ManifestOperation::SubtractAssign,
4186                    field: "count".to_string(),
4187                    storage_id: None,
4188                    operand: Some(SerializableValue::Number("3".to_string())),
4189                }
4190            ]
4191        );
4192
4193        let manifest_json = template_manifest_json(&manifest);
4194        let manifest_value: serde_json::Value =
4195            serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4196
4197        assert_eq!(
4198            manifest_value["components"][0]["actions"][0]["operation"],
4199            serde_json::json!("add_assign")
4200        );
4201        assert_eq!(
4202            manifest_value["components"][0]["actions"][0]["operand"],
4203            serde_json::json!("2")
4204        );
4205        assert_eq!(
4206            manifest_value["components"][0]["actions"][1]["operation"],
4207            serde_json::json!("subtract_assign")
4208        );
4209        assert_eq!(
4210            manifest_value["components"][0]["actions"][1]["operand"],
4211            serde_json::json!("3")
4212        );
4213    }
4214
4215    #[test]
4216    fn builds_template_manifest_for_direct_assignment_action() {
4217        let source =
4218            include_str!("../../../fixtures/0011-direct-assignment/input/ResetCounter.tsx");
4219
4220        let parsed = presolve_parser::parse_file(
4221            "fixtures/0011-direct-assignment/input/ResetCounter.tsx",
4222            source,
4223        );
4224
4225        let component_graph = build_component_graph(&parsed);
4226        let template_graph = build_template_graph(&component_graph);
4227        let manifest = build_template_manifest(&component_graph, &template_graph);
4228
4229        assert_eq!(
4230            manifest.components[0].actions,
4231            vec![ManifestAction {
4232                method: "reset".to_string(),
4233                method_id: None,
4234                action_batch_id: None,
4235                operation: ManifestOperation::Assign,
4236                field: "count".to_string(),
4237                storage_id: None,
4238                operand: Some(SerializableValue::Number("0".to_string())),
4239            }]
4240        );
4241
4242        let manifest_json = template_manifest_json(&manifest);
4243        let manifest_value: serde_json::Value =
4244            serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4245
4246        assert_eq!(
4247            manifest_value["components"][0]["actions"][0]["operation"],
4248            serde_json::json!("assign")
4249        );
4250        assert_eq!(
4251            manifest_value["components"][0]["actions"][0]["operand"],
4252            serde_json::json!("0")
4253        );
4254    }
4255
4256    #[test]
4257    fn builds_template_manifest_for_boolean_toggle_action() {
4258        let source = include_str!("../../../fixtures/0012-boolean-toggle/input/ToggleFlag.tsx");
4259
4260        let parsed = presolve_parser::parse_file(
4261            "fixtures/0012-boolean-toggle/input/ToggleFlag.tsx",
4262            source,
4263        );
4264
4265        let component_graph = build_component_graph(&parsed);
4266        let template_graph = build_template_graph(&component_graph);
4267        let manifest = build_template_manifest(&component_graph, &template_graph);
4268
4269        assert_eq!(
4270            manifest.components[0].actions,
4271            vec![ManifestAction {
4272                method: "toggle".to_string(),
4273                method_id: None,
4274                action_batch_id: None,
4275                operation: ManifestOperation::Toggle,
4276                field: "enabled".to_string(),
4277                storage_id: None,
4278                operand: None,
4279            }]
4280        );
4281
4282        let manifest_json = template_manifest_json(&manifest);
4283        let manifest_value: serde_json::Value =
4284            serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4285
4286        assert_eq!(
4287            manifest_value["components"][0]["actions"][0]["operation"],
4288            serde_json::json!("toggle")
4289        );
4290        assert!(manifest_value["components"][0]["actions"][0]
4291            .get("operand")
4292            .is_none());
4293    }
4294
4295    #[test]
4296    fn builds_template_manifest_for_multi_step_action_in_source_order() {
4297        let source =
4298            include_str!("../../../fixtures/0013-multi-step-action/input/BatchActionCounter.tsx");
4299
4300        let parsed = presolve_parser::parse_file(
4301            "fixtures/0013-multi-step-action/input/BatchActionCounter.tsx",
4302            source,
4303        );
4304
4305        let component_graph = build_component_graph(&parsed);
4306        let template_graph = build_template_graph(&component_graph);
4307        let manifest = build_template_manifest(&component_graph, &template_graph);
4308
4309        assert_eq!(
4310            manifest.components[0].actions,
4311            vec![
4312                ManifestAction {
4313                    method: "apply".to_string(),
4314                    method_id: None,
4315                    action_batch_id: None,
4316                    operation: ManifestOperation::AddAssign,
4317                    field: "count".to_string(),
4318                    storage_id: None,
4319                    operand: Some(SerializableValue::Number("2".to_string())),
4320                },
4321                ManifestAction {
4322                    method: "apply".to_string(),
4323                    method_id: None,
4324                    action_batch_id: None,
4325                    operation: ManifestOperation::Decrement,
4326                    field: "count".to_string(),
4327                    storage_id: None,
4328                    operand: None,
4329                },
4330                ManifestAction {
4331                    method: "apply".to_string(),
4332                    method_id: None,
4333                    action_batch_id: None,
4334                    operation: ManifestOperation::Assign,
4335                    field: "count".to_string(),
4336                    storage_id: None,
4337                    operand: Some(SerializableValue::Number("8".to_string())),
4338                },
4339                ManifestAction {
4340                    method: "apply".to_string(),
4341                    method_id: None,
4342                    action_batch_id: None,
4343                    operation: ManifestOperation::Increment,
4344                    field: "count".to_string(),
4345                    storage_id: None,
4346                    operand: None,
4347                },
4348                ManifestAction {
4349                    method: "apply".to_string(),
4350                    method_id: None,
4351                    action_batch_id: None,
4352                    operation: ManifestOperation::Toggle,
4353                    field: "enabled".to_string(),
4354                    storage_id: None,
4355                    operand: None,
4356                }
4357            ]
4358        );
4359
4360        let manifest_json = template_manifest_json(&manifest);
4361        let manifest_value: serde_json::Value =
4362            serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4363
4364        let actions = manifest_value["components"][0]["actions"]
4365            .as_array()
4366            .expect("manifest actions should be an array");
4367
4368        assert_eq!(
4369            actions
4370                .iter()
4371                .map(|action| action["operation"].as_str().expect("operation is a string"))
4372                .collect::<Vec<_>>(),
4373            vec!["add_assign", "decrement", "assign", "increment", "toggle"]
4374        );
4375        assert_eq!(actions[0]["method"], serde_json::json!("apply"));
4376        assert_eq!(actions[4]["method"], serde_json::json!("apply"));
4377        assert!(actions[1].get("operand").is_none());
4378        assert!(actions[3].get("operand").is_none());
4379        assert!(actions[4].get("operand").is_none());
4380    }
4381
4382    #[test]
4383    fn builds_template_manifest_for_dynamic_attribute_bindings() {
4384        let source = include_str!(
4385            "../../../fixtures/0015-dynamic-attributes/input/DynamicAttributeButton.tsx"
4386        );
4387
4388        let parsed = presolve_parser::parse_file(
4389            "fixtures/0015-dynamic-attributes/input/DynamicAttributeButton.tsx",
4390            source,
4391        );
4392
4393        let component_graph = build_component_graph(&parsed);
4394        let template_graph = build_template_graph(&component_graph);
4395        let manifest = build_template_manifest(&component_graph, &template_graph);
4396        let manifest_json = template_manifest_json(&manifest);
4397        let manifest_value: serde_json::Value =
4398            serde_json::from_str(&manifest_json).expect("manifest JSON should parse");
4399
4400        assert_eq!(
4401            manifest.components[0].template.nodes[1],
4402            ManifestNode::Binding {
4403                id: "n1".to_string(),
4404                expression: "this.disabled".to_string(),
4405                initial_value: Some(SerializableValue::Boolean(false)),
4406                target: Some(ManifestBindingTarget::Attribute),
4407                element: Some("n0".to_string()),
4408                attribute: Some("disabled".to_string()),
4409            }
4410        );
4411        assert_eq!(
4412            manifest_value["components"][0]["template"]["nodes"][1]["target"],
4413            serde_json::json!("attribute")
4414        );
4415        assert_eq!(
4416            manifest_value["components"][0]["template"]["nodes"][1]["element"],
4417            serde_json::json!("n0")
4418        );
4419        assert_eq!(
4420            manifest_value["components"][0]["template"]["nodes"][1]["attribute"],
4421            serde_json::json!("disabled")
4422        );
4423        assert!(manifest_value["components"][0]["template"]["nodes"][3]
4424            .get("target")
4425            .is_none());
4426    }
4427
4428    #[test]
4429    fn generates_standalone_page_with_embedded_manifest() {
4430        let source = include_str!("../../../fixtures/0004-nested-jsx/input/NestedCounter.tsx");
4431
4432        let parsed =
4433            presolve_parser::parse_file("fixtures/0004-nested-jsx/input/NestedCounter.tsx", source);
4434
4435        let component_graph = build_component_graph(&parsed);
4436        let template_graph = build_template_graph(&component_graph);
4437        let html = generate_static_html(&template_graph);
4438        let manifest = build_template_manifest(&component_graph, &template_graph);
4439        let page = generate_standalone_page("NestedCounter", &html, &manifest);
4440
4441        assert!(page.starts_with("<!doctype html>\n"));
4442        assert!(page.contains("<title>NestedCounter</title>"));
4443        assert!(page.contains("<section data-presolve-node=\"n0\">"));
4444        assert!(page.contains("id=\"presolve-template-manifest\""));
4445        assert!(page.contains("\"name\": \"NestedCounter\""));
4446        assert!(page.contains("<script src=\"./runtime.js\" defer></script>"));
4447    }
4448}