Working on exporable
diff --git a/superset/explorables/base.py b/superset/explorables/base.py
index c13b5dd..00939c8 100644
--- a/superset/explorables/base.py
+++ b/superset/explorables/base.py
@@ -27,6 +27,7 @@
 from datetime import datetime
 from typing import Any, Protocol, runtime_checkable
 
+from superset.common.query_object import QueryObject
 from superset.models.helpers import QueryResult
 from superset.superset_typing import QueryObjectDict
 
@@ -50,25 +51,15 @@
     # Core Query Interface
     # =========================================================================
 
-    def query(self, query_obj: QueryObjectDict) -> QueryResult:
+    def get_query_result(self, query_object: QueryObject) -> QueryResult:
         """
         Execute a query and return results.
 
         This is the primary method for data retrieval. It takes a query
-        object dictionary describing what data to fetch (columns, metrics,
-        filters, time range, etc.) and returns a QueryResult containing
-        a pandas DataFrame with the results.
+        object describing what data to fetch (columns, metrics, filters, time range,
+        etc.) and returns a QueryResult containing a pandas DataFrame with the results.
 
-        :param query_obj: Dictionary describing the query with keys like:
-            - columns: list of column names to select
-            - metrics: list of metrics to compute
-            - filter: list of filter clauses
-            - from_dttm/to_dttm: time range
-            - granularity: time column for grouping
-            - groupby: columns to group by
-            - orderby: ordering specification
-            - row_limit/row_offset: pagination
-            - extras: additional parameters
+        :param query_obj: QueryObject describing the query
 
         :return: QueryResult containing:
             - df: pandas DataFrame with query results
@@ -77,7 +68,6 @@
             - status: QueryStatus (SUCCESS/FAILED)
             - error_message: error details if query failed
         """
-        ...
 
     def get_query_str(self, query_obj: QueryObjectDict) -> str:
         """
@@ -90,7 +80,6 @@
         :param query_obj: Dictionary describing the query
         :return: String representation of the query (SQL, GraphQL, etc.)
         """
-        ...
 
     # =========================================================================
     # Identity & Metadata
@@ -109,7 +98,6 @@
 
         :return: Unique identifier string
         """
-        ...
 
     @property
     def type(self) -> str:
@@ -121,7 +109,6 @@
 
         :return: Type identifier string
         """
-        ...
 
     @property
     def columns(self) -> list[Any]:
@@ -137,7 +124,6 @@
 
         :return: List of column metadata objects
         """
-        ...
 
     @property
     def column_names(self) -> list[str]:
@@ -149,7 +135,36 @@
 
         :return: List of column name strings
         """
-        ...
+
+    @property
+    def data(self) -> dict[str, Any]:
+        """
+        Full metadata representation sent to the frontend.
+
+        This property returns a dictionary containing all the metadata
+        needed by the Explore UI, including columns, metrics, and
+        other configuration.
+
+        Required keys in the returned dictionary:
+        - id: unique identifier (int or str)
+        - uid: unique string identifier
+        - name: display name
+        - type: explorable type ('table', 'query', 'semantic_view', etc.)
+        - columns: list of column metadata dicts (with column_name, type, etc.)
+        - metrics: list of metric metadata dicts (with metric_name, expression, etc.)
+        - database: database metadata dict (with id, backend, etc.)
+
+        Optional keys:
+        - description: human-readable description
+        - schema: schema name (if applicable)
+        - catalog: catalog name (if applicable)
+        - cache_timeout: default cache timeout
+        - offset: timezone offset
+        - owners: list of owner IDs
+        - verbose_map: dict mapping column/metric names to display names
+
+        :return: Dictionary with complete explorable metadata
+        """
 
     # =========================================================================
     # Caching
@@ -165,7 +180,6 @@
 
         :return: Cache timeout in seconds, or None for system default
         """
-        ...
 
     @property
     def changed_on(self) -> datetime | None:
@@ -177,7 +191,6 @@
 
         :return: Datetime of last modification, or None
         """
-        ...
 
     def get_extra_cache_keys(self, query_obj: QueryObjectDict) -> list[Hashable]:
         """
@@ -191,7 +204,6 @@
         :param query_obj: The query being executed
         :return: List of additional hashable values for cache key
         """
-        ...
 
     # =========================================================================
     # Security
@@ -208,7 +220,6 @@
 
         :return: Permission identifier string
         """
-        ...
 
     @property
     def schema_perm(self) -> str | None:
@@ -220,7 +231,6 @@
 
         :return: Schema permission string, or None
         """
-        ...
 
     # =========================================================================
     # Time/Date Handling
@@ -236,4 +246,3 @@
 
         :return: Timezone offset in seconds (0 for UTC)
         """
-        ...
diff --git a/superset/semantic_layers/mapper.py b/superset/semantic_layers/mapper.py
index 68c6c65..a9ee68f 100644
--- a/superset/semantic_layers/mapper.py
+++ b/superset/semantic_layers/mapper.py
@@ -24,14 +24,17 @@
 
 """
 
-from datetime import datetime
+from datetime import datetime, timedelta
+from time import time
 from typing import Any, cast, Sequence, TypeGuard
 
 import numpy as np
 
+from superset.common.db_query_status import QueryStatus
 from superset.common.query_object import QueryObject
 from superset.common.utils.time_range_utils import get_since_until_from_query_object
 from superset.connectors.sqla.models import BaseDatasource
+from superset.models.helpers import QueryResult
 from superset.semantic_layers.types import (
     AdhocExpression,
     AdhocFilter,
@@ -86,16 +89,19 @@
     series_limit_metric: str | None
 
 
-def get_results(query_object: QueryObject) -> SemanticResult:
+def get_results(query_object: QueryObject) -> QueryResult:
     """
     Run 1+ queries based on `QueryObject` and return the results.
 
     :param query_object: The QueryObject containing query specifications
-    :return: SemanticResult with combined DataFrame and all requests
+    :return: QueryResult compatible with Superset's query interface
     """
     if not validate_query_object(query_object):
         raise ValueError("QueryObject must have a datasource defined.")
 
+    # Track execution time
+    start_time = time()
+
     semantic_view = query_object.datasource.implementation
     dispatcher = (
         semantic_view.get_row_count
@@ -126,10 +132,16 @@
 
     # If no time offsets, return the main result as-is
     if not query_object.time_offsets or len(queries) <= 1:
-        return SemanticResult(
+        semantic_result = SemanticResult(
             requests=all_requests,
             results=main_df,
         )
+        duration = timedelta(seconds=time() - start_time)
+        return map_semantic_result_to_query_result(
+            semantic_result,
+            query_object,
+            duration,
+        )
 
     # Get metric names from the main query
     # These are the columns that will be renamed with offset suffixes
@@ -197,7 +209,58 @@
             if duplicate_cols:
                 main_df = main_df.drop(columns=duplicate_cols)
 
-    return SemanticResult(requests=all_requests, results=main_df)
+    # Convert final result to QueryResult
+    semantic_result = SemanticResult(requests=all_requests, results=main_df)
+    duration = timedelta(seconds=time() - start_time)
+    return map_semantic_result_to_query_result(
+        semantic_result,
+        query_object,
+        duration,
+    )
+
+
+def map_semantic_result_to_query_result(
+    semantic_result: SemanticResult,
+    query_object: ValidatedQueryObject,
+    duration: timedelta,
+) -> QueryResult:
+    """
+    Convert a SemanticResult to a QueryResult.
+
+    :param semantic_result: Result from the semantic layer
+    :param query_object: Original QueryObject (for passthrough attributes)
+    :param duration: Time taken to execute the query
+    :return: QueryResult compatible with Superset's query interface
+    """
+    # Get the query string from requests (typically one or more SQL queries)
+    query_str = ""
+    if semantic_result.requests:
+        # Join all requests for display (could be multiple for time comparisons)
+        query_str = "\n\n".join(
+            f"-- {req.type}\n{req.definition}" for req in semantic_result.requests
+        )
+
+    return QueryResult(
+        # Core data
+        df=semantic_result.results,
+        query=query_str,
+        duration=duration,
+        # Template filters - not applicable to semantic layers
+        # (semantic layers don't use Jinja templates)
+        applied_template_filters=None,
+        # Filter columns - not applicable to semantic layers
+        # (semantic layers handle filter validation internally)
+        applied_filter_columns=None,
+        rejected_filter_columns=None,
+        # Status - always success if we got here
+        # (errors would raise exceptions before reaching this point)
+        status=QueryStatus.SUCCESS,
+        error_message=None,
+        errors=None,
+        # Time range - pass through from original query_object
+        from_dttm=query_object.from_dttm,
+        to_dttm=query_object.to_dttm,
+    )
 
 
 def map_query_object(query_object: ValidatedQueryObject) -> list[SemanticQuery]:
diff --git a/superset/semantic_layers/models.py b/superset/semantic_layers/models.py
index b146007..e9ab970 100644
--- a/superset/semantic_layers/models.py
+++ b/superset/semantic_layers/models.py
@@ -28,11 +28,14 @@
 from sqlalchemy.orm import relationship
 from sqlalchemy_utils import UUIDType
 
-from superset.models.helpers import AuditMixinNullable
+from superset.common.query_object import QueryObject
+from superset.models.helpers import AuditMixinNullable, QueryResult
+from superset.semantic_layers.mapper import get_results
 from superset.semantic_layers.types import (
     SemanticLayerImplementation,
     SemanticViewImplementation,
 )
+from superset.superset_typing import QueryObjectDict
 from superset.utils import core as utils
 
 
@@ -69,7 +72,9 @@
         return self.name or str(self.uuid)
 
     @property
-    def implementation(self) -> SemanticLayerImplementation[Any]:
+    def implementation(
+        self,
+    ) -> SemanticLayerImplementation[Any, SemanticViewImplementation]:
         """
         Return semantic layer implementation.
         """
@@ -138,3 +143,17 @@
     # =========================================================================
     # Explorable protocol implementation
     # =========================================================================
+
+    def get_query_result(self, query_object: QueryObject) -> QueryResult:
+        return get_results(query_object)
+
+    def get_query_str(self, query_obj: QueryObjectDict) -> str:
+        return "Not implemented for semantic layers"
+
+    @property
+    def uid(self) -> str:
+        return self.implementation.uid()
+
+    @property
+    def type(self) -> str:
+        return "semantic_view"
diff --git a/superset/semantic_layers/snowflake/semantic_layer.py b/superset/semantic_layers/snowflake/semantic_layer.py
index 9b3591d..8f99f67 100644
--- a/superset/semantic_layers/snowflake/semantic_layer.py
+++ b/superset/semantic_layers/snowflake/semantic_layer.py
@@ -18,7 +18,7 @@
 from __future__ import annotations
 
 from textwrap import dedent
-from typing import Any, Literal
+from typing import Any, Literal, TYPE_CHECKING
 
 from pydantic import create_model, Field
 from snowflake.connector import connect
@@ -28,11 +28,15 @@
 from superset.semantic_layers.snowflake.utils import get_connection_parameters
 from superset.semantic_layers.types import (
     SemanticLayerImplementation,
-    SemanticViewImplementation,
 )
 
+if TYPE_CHECKING:
+    from superset.semantic_layers.snowflake.semantic_view import SnowflakeSemanticView
 
-class SnowflakeSemanticLayer(SemanticLayerImplementation[SnowflakeConfiguration]):
+
+class SnowflakeSemanticLayer(
+    SemanticLayerImplementation[SnowflakeConfiguration, SnowflakeSemanticView]
+):
     id = "snowflake"
     name = "Snowflake Semantic Layer"
     description = "Connect to semantic views stored in Snowflake."
@@ -204,7 +208,7 @@
     def get_semantic_views(
         self,
         runtime_configuration: dict[str, Any],
-    ) -> set[SemanticViewImplementation]:
+    ) -> set[SnowflakeSemanticView]:
         """
         Get the semantic views available in the semantic layer.
         """
diff --git a/superset/semantic_layers/types.py b/superset/semantic_layers/types.py
index 54b555a..8233c7b 100644
--- a/superset/semantic_layers/types.py
+++ b/superset/semantic_layers/types.py
@@ -289,11 +289,12 @@
     GROUP_OTHERS = "GROUP_OTHERS"
 
 
-LayerConfigT = TypeVar("LayerConfigT", bound=BaseModel)
+ConfigT = TypeVar("ConfigT", bound=BaseModel, contravariant=True)
+SemanticViewT = TypeVar("SemanticViewT", bound="SemanticViewImplementation")
 
 
 @runtime_checkable
-class SemanticLayerImplementation(Protocol[LayerConfigT]):
+class SemanticLayerImplementation(Protocol[ConfigT, SemanticViewT]):
     """
     A protocol for semantic layers.
     """
@@ -302,7 +303,7 @@
     def from_configuration(
         cls,
         configuration: dict[str, Any],
-    ) -> SemanticLayerImplementation[LayerConfigT]:
+    ) -> SemanticLayerImplementation[ConfigT, SemanticViewT]:
         """
         Create a semantic layer from its configuration.
         """
@@ -310,7 +311,7 @@
     @classmethod
     def get_configuration_schema(
         cls,
-        configuration: LayerConfigT | None = None,
+        configuration: ConfigT | None = None,
     ) -> dict[str, Any]:
         """
         Get the JSON schema for the configuration needed to add the semantic layer.
@@ -334,7 +335,7 @@
     @classmethod
     def get_runtime_schema(
         cls,
-        configuration: LayerConfigT,
+        configuration: ConfigT,
         runtime_data: dict[str, Any] | None = None,
     ) -> dict[str, Any]:
         """
@@ -361,7 +362,7 @@
     def get_semantic_views(
         self,
         runtime_configuration: dict[str, Any],
-    ) -> set[SemanticViewImplementation]:
+    ) -> set[SemanticViewT]:
         """
         Get the semantic views available in the semantic layer.
 
@@ -373,7 +374,7 @@
         self,
         name: str,
         additional_configuration: dict[str, Any],
-    ) -> SemanticViewImplementation:
+    ) -> SemanticViewT:
         """
         Get a specific semantic view by its name and additional configuration.
         """