Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,15 @@ enum class PairingDeeplinkScheme {

/** Trusted kind of executable attached to a product connection. */
enum class ProductExecutionKind {
SPA,
CHAT;
APP,
WIDGET,
WORKER;

internal fun toNative(): UniFfiProductExecutionKind =
when (this) {
SPA -> UniFfiProductExecutionKind.SPA
CHAT -> UniFfiProductExecutionKind.CHAT
APP -> UniFfiProductExecutionKind.APP
WIDGET -> UniFfiProductExecutionKind.WIDGET
WORKER -> UniFfiProductExecutionKind.WORKER
}
}

Expand All @@ -115,7 +117,7 @@ enum class ProductExecutionKind {
*/
data class RuntimeConfig(
val productId: String,
val executionKind: ProductExecutionKind = ProductExecutionKind.SPA,
val executionKind: ProductExecutionKind = ProductExecutionKind.APP,
val hostName: String,
val hostIcon: String? = null,
val hostVersion: String? = null,
Expand Down
11 changes: 6 additions & 5 deletions explorer/src/data/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,15 @@ export interface MethodInfo {
errorType?: string;
}

/** Trusted executable kind required to reach a service. */
export type ProductExecutionKind = "Spa" | "Chat";

/** A grouping of related methods. */
export interface ServiceInfo {
name: string;
/** Executable kind the host must attach, or unrestricted when absent. */
requiredExecution?: ProductExecutionKind;
/**
* Executable kind the host must attach, or unrestricted when absent. Typed
* as a string because each archived version records the kind name that
* version declared, not today's variants.
*/
requiredExecution?: string;
methods: MethodInfo[];
}

Expand Down
32 changes: 22 additions & 10 deletions explorer/src/pages/CompatibilityPage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Fragment, useState } from "react";
import { Link, useOutletContext } from "react-router-dom";
import { Check, ChevronDown, Minus, X } from "lucide-react";
import type { ProductExecutionKind, VersionEntry } from "../data/types";
import type { VersionEntry } from "../data/types";
import { methodPath } from "../data/registry";
import { chatCompatibility, compatibility } from "../data/compatibility";

/**
* Execution kinds that serve the Chat modality. `Chat` is the name protocol
* versions up to 0.9.0 declared; `Worker` is the name that replaced it.
*/
const CHAT_EXECUTIONS: ReadonlySet<string> = new Set(["Chat", "Worker"]);
import type {
CompatibilityMatrix,
CompatStatus,
Expand Down Expand Up @@ -86,18 +92,18 @@ export default function CompatibilityPage() {

<div className="space-y-10">
<CompatibilitySection
title="SPA compatibility"
description="API coverage measured from the visible SPA execution."
execution="Spa"
title="App compatibility"
description="API coverage measured from a visible App or Widget execution."
executions={null}
matrix={compatibility}
version={version}
expandedId={expandedId}
onToggle={setExpandedId}
/>
<CompatibilitySection
title="Chat compatibility"
description="Chat API coverage measured from the product's native Chat worker."
execution="Chat"
description="Chat API coverage measured from the product's Worker execution."
executions={CHAT_EXECUTIONS}
matrix={chatCompatibility}
version={version}
expandedId={expandedId}
Expand All @@ -111,15 +117,20 @@ export default function CompatibilityPage() {
function CompatibilitySection({
title,
description,
execution,
executions,
matrix,
version,
expandedId,
onToggle,
}: {
title: string;
description: string;
execution: ProductExecutionKind;
/**
* Kinds whose gated services belong in this section, or `null` for the
* ungated ones. A set rather than one name because each archived version
* records the kind name that version declared.
*/
executions: ReadonlySet<string> | null;
matrix: CompatibilityMatrix;
version: VersionEntry;
expandedId: string | null;
Expand Down Expand Up @@ -154,8 +165,9 @@ function CompatibilitySection({
<tbody>
{version.services
.filter((service) =>
execution === "Chat"
? service.requiredExecution === "Chat"
executions
? service.requiredExecution !== undefined &&
executions.has(service.requiredExecution)
: service.requiredExecution === undefined,
)
.map((service) => ({
Expand Down
18 changes: 16 additions & 2 deletions ios/truapi-host/Sources/TrUAPIHost/TrUAPIHost.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public struct RuntimeConfig: Sendable {

public init(
productId: String,
executionKind: ProductExecutionKind = .spa,
executionKind: ProductExecutionKind = .app,
hostName: String,
hostIcon: String? = nil,
hostVersion: String? = nil,
Expand Down Expand Up @@ -901,7 +901,7 @@ public protocol TrUAPIProductExecutionProtocol: AnyObject, Sendable {
func sessionChatIdentityKey() throws -> Data?
}

/// One SPA or Chat executable connected to a shared host runtime.
/// One App, Widget, or Worker executable connected to a shared host runtime.
public final class TrUAPIProductExecution: TrUAPIProductExecutionProtocol, @unchecked Sendable {
private let inner: NativeProductExecution
private let callbackRetainer: HostCallbacks
Expand Down Expand Up @@ -1193,4 +1193,18 @@ private final class CustomRendererStreamObserver: NativeCustomRendererObserver,
func onComplete() {
continuation.finish()
}

/// The product could not serve the render, so the last tree yielded is
/// partial. Finishing with an error keeps that distinct from a clean end.
func onError(reason: String) {
continuation.finish(throwing: CustomRendererStreamError(reason: reason))
}
}

/// A render the product declined or could not encode.
public struct CustomRendererStreamError: Error, CustomStringConvertible {
/// Why the product ended the render.
public let reason: String

public var description: String { reason }
}
32 changes: 24 additions & 8 deletions ios/truapi-host/Sources/TrUAPIHost/truapi_platform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1871,18 +1871,28 @@ public func FfiConverterTypePermissionAuthorizationStatus_lower(_ value: Permiss

/**
* Trusted kind of product executable attached to a TrUAPI connection.
*
* Mirrors the executable kinds a product manifest declares. The variants are
* capability classes: a connection reaches an execution-gated service only
* when its kind matches exactly, so `App` and `Widget` carry the same
* capability and differ only in how the host presents them, and `Worker` is
* the only kind that may serve the Chat modality.
*/

public enum ProductExecutionKind: Equatable, Hashable {

/**
* Visible single-page application entrypoint such as `app/index.html`.
* Visible full-page entrypoint such as `app/index.html`.
*/
case app
/**
* Visible embedded surface such as a dashboard card.
*/
case spa
case widget
/**
* Headless worker executable that provides the Chat modality.
* Headless executable that serves the Chat modality.
*/
case chat
case worker



Expand All @@ -1904,9 +1914,11 @@ public struct FfiConverterTypeProductExecutionKind: FfiConverterRustBuffer {
let variant: Int32 = try readInt(&buf)
switch variant {

case 1: return .spa
case 1: return .app

case 2: return .widget

case 2: return .chat
case 3: return .worker

default: throw UniffiInternalError.unexpectedEnumCase
}
Expand All @@ -1916,13 +1928,17 @@ public struct FfiConverterTypeProductExecutionKind: FfiConverterRustBuffer {
switch value {


case .spa:
case .app:
writeInt(&buf, Int32(1))


case .chat:
case .widget:
writeInt(&buf, Int32(2))


case .worker:
writeInt(&buf, Int32(3))

}
}
}
Expand Down
66 changes: 64 additions & 2 deletions ios/truapi-host/Sources/TrUAPIHost/truapi_server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2480,6 +2480,14 @@ public protocol NativeProductExecutionProtocol: AnyObject, Sendable {
*/
func permissionAuthorizationStatus(request: PermissionAuthorizationRequest) throws -> PermissionAuthorizationStatus

/**
* Resolve a product's hard-subtree public key for hosts naming the account
* a review will sign with. Answers from the cache, the persisted slot, or
* the Account Holder, and `timeout_ms` bounds that wait. Exceeding it is
* an error; `None` means no active session.
*/
func productSubtreePublicKey(productId: String, timeoutMs: UInt32?) throws -> Bytes32?

/**
* Publish one native Chat action, buffering it until the product
* connection subscribes.
Expand Down Expand Up @@ -2664,6 +2672,23 @@ open func permissionAuthorizationStatus(request: PermissionAuthorizationRequest)
FfiConverterTypePermissionAuthorizationRequest_lower(request),uniffiCallStatus
)
})
}

/**
* Resolve a product's hard-subtree public key for hosts naming the account
* a review will sign with. Answers from the cache, the persisted slot, or
* the Account Holder, and `timeout_ms` bounds that wait. Exceeding it is
* an error; `None` means no active session.
*/
open func productSubtreePublicKey(productId: String, timeoutMs: UInt32?)throws -> Bytes32? {
return try FfiConverterOptionTypeBytes32.lift(try rustCallWithError(FfiConverterTypeHostRejection_lift) {
uniffiCallStatus in
uniffi_truapi_server_fn_method_nativeproductexecution_product_subtree_public_key(
self.uniffiCloneHandle(),
FfiConverterString.lower(productId),
FfiConverterOptionUInt32.lower(timeoutMs),uniffiCallStatus
)
})
}

/**
Expand Down Expand Up @@ -5829,10 +5854,17 @@ public protocol NativeCustomRendererObserver: AnyObject, Sendable {
func onUpdate(node: CustomRendererNode)

/**
* Report that the renderer stream ended.
* Report that the renderer stream ended without drawing further trees.
* The last tree delivered stands.
*/
func onComplete()

/**
* Report that the product could not serve this render. The last tree
* delivered, if any, is partial and must not be treated as final.
*/
func onError(reason: String)

}


Expand Down Expand Up @@ -5897,6 +5929,30 @@ fileprivate struct UniffiCallbackInterfaceNativeCustomRendererObserver {
}


let writeReturn = { () }
uniffiTraitInterfaceCall(
callStatus: uniffiCallStatus,
makeCall: makeCall,
writeReturn: writeReturn
)
},
onError: { (
uniffiHandle: UInt64,
reason: RustBuffer,
uniffiOutReturn: UnsafeMutableRawPointer,
uniffiCallStatus: UnsafeMutablePointer<RustCallStatus>
) in
let makeCall = {
() throws -> () in
guard let uniffiObj = try? FfiConverterCallbackInterfaceNativeCustomRendererObserver.handleMap.get(handle: uniffiHandle) else {
throw UniffiInternalError.unexpectedStaleHandle
}
return uniffiObj.onError(
reason: try FfiConverterString.lift(reason)
)
}


let writeReturn = { () }
uniffiTraitInterfaceCall(
callStatus: uniffiCallStatus,
Expand Down Expand Up @@ -6558,6 +6614,9 @@ private let initializationResult: InitializationResult = {
if (uniffi_truapi_server_checksum_method_nativeproductexecution_permission_authorization_status() != 18097) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_truapi_server_checksum_method_nativeproductexecution_product_subtree_public_key() != 63238) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_truapi_server_checksum_method_nativeproductexecution_publish_chat_action() != 31503) {
return InitializationResult.apiChecksumMismatch
}
Expand Down Expand Up @@ -6687,7 +6746,10 @@ private let initializationResult: InitializationResult = {
if (uniffi_truapi_server_checksum_method_nativecustomrendererobserver_on_update() != 1079) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_truapi_server_checksum_method_nativecustomrendererobserver_on_complete() != 43817) {
if (uniffi_truapi_server_checksum_method_nativecustomrendererobserver_on_complete() != 32694) {
return InitializationResult.apiChecksumMismatch
}
if (uniffi_truapi_server_checksum_method_nativecustomrendererobserver_on_error() != 21230) {
return InitializationResult.apiChecksumMismatch
}

Expand Down
Loading