diff --git a/CLAUDE.md b/CLAUDE.md index 11187e79..b7240226 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -27,6 +27,10 @@ scripts/codegen.sh regenerate the TS client from the Rust crate - Remove legacy compatibility code by default. Keep or add it only when explicitly requested. - In Rust format strings, prefer inlined variables: `"log value: {value:?}"` over `"log value: {:?}", value`. - **No `any` in TypeScript types**: If a type can't be expressed cleanly, stop and ask the user whether to (a) refactor or import the right type or (b) add a scoped `// eslint-disable-next-line @typescript-eslint/no-explicit-any` exception. Never silently leave `any`. +- Don't introduce typealias chains that just rename a public type from another crate (e.g. `pub type StorageError = crate::v01::HostLocalStorageReadError`). Use the canonical name directly. A typealias is only worth its indirection when it captures a real abstraction. +- After any code change, update `README.md` (and CLAUDE.md if the layout changed) so the top-level docs reflect what the repo actually contains. Stale docs are a regression. +- In codegen emitters, prefer `indoc::writedoc!` / `formatdoc!` over chains of `writeln!`. A single `writedoc!` with a multi-line raw string keeps the emitted shape visible in source instead of fragmenting it across one-line `writeln!` calls. Reserve `writeln!` for the genuinely-one-line case (a single import, a single statement inside a loop). +- In PR descriptions, issue comments, and other artifacts that outlive the conversation: describe the resulting state, not the transition between commits. Avoid "previously X, now Y", "we removed", "the old shim is gone", "this PR replaces", those read as ephemeral history once the PR is squash-merged. Write what the system *does* after the change, not what each commit *changed* on the way there. (Commit messages are the place for transition narrative; they survive in `git log` even after the squash.) ## First-time setup diff --git a/Cargo.lock b/Cargo.lock index 26ca7750..dabc1fb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -158,6 +158,38 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case 0.10.0", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -412,12 +444,27 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -524,6 +571,7 @@ dependencies = [ name = "truapi" version = "0.1.0" dependencies = [ + "derive_more", "futures", "hex", "parity-scale-codec", @@ -536,7 +584,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", - "convert_case", + "convert_case 0.6.0", "indoc", "serde", "serde_json", diff --git a/js/packages/truapi/src/transport.ts b/js/packages/truapi/src/transport.ts index 78262a99..8a92133e 100644 --- a/js/packages/truapi/src/transport.ts +++ b/js/packages/truapi/src/transport.ts @@ -2,6 +2,13 @@ import { err, ok, type Result, type ResultAsync } from "neverthrow"; import { str, u8, type ResultPayload } from "./scale.js"; +/** + * Coerce an unknown thrown value into an `Error` instance. + */ +function toError(error: unknown): Error { + return error instanceof Error ? error : new Error(String(error)); +} + /** * Handle returned by TrUAPI subscription APIs. **/ @@ -408,7 +415,7 @@ function createBaseProvider() { /** Transition to the closed state. Idempotent. */ close(error: unknown) { if (closedError) return; - closedError = error instanceof Error ? error : new Error(String(error)); + closedError = toError(error); for (const fn of [...onCloseCleanup]) { try { fn(); @@ -494,7 +501,7 @@ export function createIframeProvider(options: { target.postMessage(message, hostOrigin); } catch (error) { base.close(error); - throw error instanceof Error ? error : new Error(String(error)); + throw toError(error); } }, subscribe: base.subscribe, @@ -559,7 +566,7 @@ export function createMessagePortProvider( resolvedPort.postMessage(message); } catch (error) { base.close(error); - throw error instanceof Error ? error : new Error(String(error)); + throw toError(error); } } else { pending.push(message); diff --git a/rust/crates/truapi-codegen/src/rustdoc.rs b/rust/crates/truapi-codegen/src/rustdoc.rs index a9179e0a..fbbc7d68 100644 --- a/rust/crates/truapi-codegen/src/rustdoc.rs +++ b/rust/crates/truapi-codegen/src/rustdoc.rs @@ -92,7 +92,7 @@ pub struct WireAttrs { } /// Wire-shape classification of a trait method. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum MethodKind { /// One request, one response. Request, diff --git a/rust/crates/truapi/Cargo.toml b/rust/crates/truapi/Cargo.toml index b5d2bef8..f47da511 100644 --- a/rust/crates/truapi/Cargo.toml +++ b/rust/crates/truapi/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true description = "TrUAPI trait and type definitions" [dependencies] +derive_more = { version = "2", features = ["display"] } futures = "0.3" hex = "0.4" parity-scale-codec = { version = "3", features = ["derive"] } diff --git a/rust/crates/truapi/README.md b/rust/crates/truapi/README.md index 5a5b4737..f033f88d 100644 --- a/rust/crates/truapi/README.md +++ b/rust/crates/truapi/README.md @@ -8,7 +8,7 @@ _Source of truth for the TrUAPI protocol: shared traits, versioned types, and th It defines: -- **Versioned data types** under `v01`, `v02`, and `versioned`. +- **Versioned data types** under `v01` and `versioned`. - **Domain API traits** under `api/`, plus the composed `TrUApi` trait. - **Wire ids** via per-method `#[wire(id = N)]` annotations that pin the byte-level method table. - **Subscription primitives** through `Subscription` for streamed host responses. diff --git a/rust/crates/truapi/src/v01/permissions.rs b/rust/crates/truapi/src/v01/permissions.rs index f56bb536..b847103e 100644 --- a/rust/crates/truapi/src/v01/permissions.rs +++ b/rust/crates/truapi/src/v01/permissions.rs @@ -1,3 +1,4 @@ +use derive_more::Display; use parity_scale_codec::{Decode, Encode}; /// Device-capability permission requested from the host (RFC 0002). @@ -5,17 +6,26 @@ use parity_scale_codec::{Decode, Encode}; /// The user's decision is persisted indefinitely after the first prompt and /// survives app restarts, whether the decision was grant or deny; the host /// does not re-prompt on subsequent requests for the same capability. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, Display)] #[allow(clippy::upper_case_acronyms)] pub enum HostDevicePermissionRequest { + #[display("notifications")] Notifications, + #[display("camera")] Camera, + #[display("microphone")] Microphone, + #[display("bluetooth")] Bluetooth, + #[display("NFC")] NFC, + #[display("location")] Location, + #[display("clipboard")] Clipboard, + #[display("open URL")] OpenUrl, + #[display("biometrics")] Biometrics, } @@ -23,25 +33,34 @@ pub enum HostDevicePermissionRequest { /// /// `ChainSubmit`, `PreimageSubmit`, and `StatementSubmit` are also triggered /// implicitly by the corresponding business calls when not yet granted. -#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Display)] pub enum RemotePermission { /// Outbound HTTP/WebSocket access to a set of domains. + #[display("access to {}", domains.join(", "))] Remote { /// Domain patterns requested by the product. domains: Vec, }, /// WebRTC media access. + #[display("WebRTC connections")] WebRtc, /// Submitting transactions on behalf of the user via `remote_chain_transaction_broadcast`. + #[display("submit chain transactions")] ChainSubmit, /// Submitting preimages on behalf of the user via `remote_preimage_submit`. + #[display("submit preimages")] PreimageSubmit, /// Submitting statements on behalf of the user via `remote_statement_store_submit`. + #[display("submit statements")] StatementSubmit, } /// Batched remote-permission request (RFC 0002). -#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Display)] +#[display( + "{}", + permissions.iter().map(ToString::to_string).collect::>().join("; ") +)] pub struct RemotePermissionRequest { /// Permissions requested by the product. pub permissions: Vec, diff --git a/rust/crates/truapi/src/versioned/mod.rs b/rust/crates/truapi/src/versioned/mod.rs index 116c88cf..f7b12fd4 100644 --- a/rust/crates/truapi/src/versioned/mod.rs +++ b/rust/crates/truapi/src/versioned/mod.rs @@ -24,6 +24,7 @@ pub trait IntoVersion: Sized { macro_rules! versioned_type { ( $( + $(#[$enum_meta:meta])* pub enum $name:ident { $($body:tt)* } @@ -32,6 +33,7 @@ macro_rules! versioned_type { $( versioned_type! { @one + $(#[$enum_meta])* pub enum $name { $($body)* } @@ -41,13 +43,17 @@ macro_rules! versioned_type { ( @one + $(#[$enum_meta:meta])* pub enum $name:ident { + $(#[$variant_meta:meta])* V1 => $v1:ty $(,)? } ) => { + $(#[$enum_meta])* #[doc = concat!("Versioned envelope for [`", stringify!($name), "`].")] #[derive(Debug, Clone, PartialEq, Eq, parity_scale_codec::Encode, parity_scale_codec::Decode)] pub enum $name { + $(#[$variant_meta])* #[codec(index = 0)] V1($v1), } @@ -61,13 +67,17 @@ macro_rules! versioned_type { ( @one + $(#[$enum_meta:meta])* pub enum $name:ident { + $(#[$variant_meta:meta])* V1 $(,)? } ) => { + $(#[$enum_meta])* #[doc = concat!("Versioned envelope for [`", stringify!($name), "`].")] #[derive(Debug, Clone, PartialEq, Eq, parity_scale_codec::Encode, parity_scale_codec::Decode)] pub enum $name { + $(#[$variant_meta])* #[codec(index = 0)] V1, } diff --git a/rust/crates/truapi/src/versioned/permissions.rs b/rust/crates/truapi/src/versioned/permissions.rs index 71558a5d..53e97079 100644 --- a/rust/crates/truapi/src/versioned/permissions.rs +++ b/rust/crates/truapi/src/versioned/permissions.rs @@ -3,9 +3,13 @@ use crate::v01; versioned_type! { + #[derive(derive_more::Display)] + #[display("{_0}")] pub enum HostDevicePermissionRequest { V1 => v01::HostDevicePermissionRequest } pub enum HostDevicePermissionResponse { V1 => v01::HostDevicePermissionResponse } pub enum HostDevicePermissionError { V1 => v01::GenericError } + #[derive(derive_more::Display)] + #[display("{_0}")] pub enum RemotePermissionRequest { V1 => v01::RemotePermissionRequest } pub enum RemotePermissionResponse { V1 => v01::RemotePermissionResponse } pub enum RemotePermissionError { V1 => v01::GenericError }