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