Skip to content
Open
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
4 changes: 2 additions & 2 deletions playground/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ The Diagnosis screen emits a per-host markdown report via "Copy report". Aggrega
| `src/lib/example-runner.ts` | Transpiles each rustdoc `ts` example via sucrase, runs it inside an `AsyncFunction` with `truapi`, `console`, rxjs, and an ambient `assert` as bindings. Failure is explicit: an example fails iff it throws (via `assert(...)`, a timeout, or any uncaught error); `console.*` is pure output. A tracking Proxy auto-unsubscribes inner `.subscribe(...)` calls so subscriptions clean up when the run ends or the user navigates away. |
| `src/lib/monaco-setup.ts` | Configures Monaco's TS worker: registers the bundled `@parity/truapi` types (`truapi-dts`), every rxjs `.d.ts`, and an ambient block (`declare const truapi: Client`, `assert`, `crypto`, `Uint8Array` hex helpers) so examples typecheck without manual imports. Defines the light/dark themes that match the design tokens. |
| `src/lib/auto-test.ts` | Runs each method's example and reports pass / fail. A method passes when its example resolves within the timeout and fails when it throws (the thrown/`assert` message plus any logs become the failure output); unary and subscription examples are awaited identically. `runDiagnosis` runs every method one at a time, in service order; methods that prompt the user (signing, permission/resource requests) block on their host dialog before the run continues. `runSingleTest` replays one method (used by the Diagnosis row replay). |
| `src/lib/diagnosis-report.ts` | Renders the diagnosis results as a copy-pasteable GitHub-flavoured markdown table: a `## Truapi <Web\|Desktop\|Android\|iOS> Diagnosis` title (host mode via `detectHostMode` — a native host (Electron UA or `__HOST_WEBVIEW_MARK__`) is split by user-agent into Desktop / Android / iOS, a browser iframe ⇒ Web) and one method/status row per method. Deterministic for a given set of results (no timestamp). Consumed by the explorer's matrix aggregator. |
| `src/lib/diagnosis-report.ts` | Renders the diagnosis results as a copy-pasteable GitHub-flavoured markdown table: a `## Truapi <Web\|Desktop\|Android\|iOS> Diagnosis` title (host mode via `detectHostMode` — a native host (Electron UA or `__HOST_WEBVIEW_MARK__`) is split by user-agent into Desktop / Android / iOS, a browser iframe ⇒ Web) a `**N success · N failed**` summary line, and one row per method. Skipped methods are reported as failed (`❌`) with the skip reason in the Details column, so every truapi method stays in the compatibility matrix (the aggregator keeps only `✅`/`❌` cells). Deterministic for a given set of results (no timestamp). `renderReportMarkdown(…, { dropSuccessDetails })` emits a compact variant (success-row details blanked, failures kept) for the length-limited issue URL; `reportIssueUrl` caps the whole URL and falls back to a "paste from clipboard" body when a report is still too large. Consumed by the explorer's matrix aggregator. |
| `src/lib/host-api-bridge.ts` | Just `stringify`, the JSON-with-bigint helper shared across components. |
| `src/components/ExampleEditor.tsx` | Monaco editor wrapper. Auto-folds `// #region helpers` blocks on mount. |
| `src/components/MethodView.tsx` | Per-method view: signature link to cargo doc, Example / Output tabs, status LED, Run / Stop buttons. Output is the example's `console.*` log; an explicit `assert`/error throw flips the LED to error and shows the thrown message. |
| `src/components/DiagnosisView.tsx` | Diagnosis screen (own sidebar entry): purpose + login/phone instructions, a Run button, a live per-method log (queued → processing… → success/failed) with per-row expand + replay, and a Copy report button. |
| `src/components/DiagnosisView.tsx` | Diagnosis screen (own sidebar entry): purpose + login/phone instructions, a Run button, a live per-method log (queued → processing… → success/failed) with per-row expand + replay, a summary showing success / failed counts (skipped methods are shown as failed, with the skip reason as their detail), and Copy report / Submit report buttons. Submit copies the full report to the clipboard and opens a pre-filled GitHub issue carrying a compact variant of it. |
| `src/components/ServiceTable.tsx` / `CommandPalette.tsx` | Method browser and ⌘K search. The browser also hosts the Diagnosis entry. |
| `src/app/page.tsx` | Root: connection status, selection state, deep-link sync via `pushState` + `popstate`. |

Expand Down
26 changes: 20 additions & 6 deletions playground/src/components/DiagnosisView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ interface Row {
service: string;
method: string;
status: TestStatus;
// A skipped method is displayed as failed; this flags it so the e2e gate can
// tell an intentional skip apart from a genuine failure.
skipped: boolean;
output?: string;
}

Expand Down Expand Up @@ -52,14 +55,19 @@ export function DiagnosisView({
for (const m of svc.methods) {
const id = `${svc.name}/${m.name}`;
const entry = testResults[id];
const status = entry?.status ?? "idle";
const rawStatus = entry?.status ?? "idle";
// Skipped methods are shown as failed (their skip reason is the detail)
// so the view stays a pass/fail summary and every method is accounted
// for — matching the compatibility matrix.
const status = rawStatus === "skipped" ? "fail" : rawStatus;
if (status === "pass") pass++;
else if (status === "fail") fail++;
out.push({
id,
service: svc.name,
method: m.name,
status,
skipped: rawStatus === "skipped",
output: entry?.output,
});
}
Expand Down Expand Up @@ -93,12 +101,17 @@ export function DiagnosisView({
// Open a pre-filled GitHub issue carrying the report; the diagnosis-report
// workflow writes it to diagnosis-reports/<host>.md and opens a PR. The host
// opens the link via `navigate_to` (a sandboxed app can't `window.open`).
// Copy the report to the clipboard first as a fallback if the body is
// truncated.
// The full report goes to the clipboard (lossless fallback); the URL carries
// a compact variant with success-row details dropped, so it stays under
// GitHub's URL-length limit while keeping failure details.
const handleSubmitReport = () => {
const report = reportMarkdown;
void navigator.clipboard?.writeText(report).catch(() => {});
const url = reportIssueUrl(report, detectHostMode());
void navigator.clipboard?.writeText(reportMarkdown).catch(() => {});
const mode = detectHostMode();
const compact = renderReportMarkdown(services, testResults, {
mode,
dropSuccessDetails: true,
});
const url = reportIssueUrl(compact, mode);
// No-op outside a host container; navigation is best-effort.
void getClientSync()?.system.navigateTo({ url });
};
Expand Down Expand Up @@ -206,6 +219,7 @@ export function DiagnosisView({
className="diag__row"
data-testid="diagnosis-row"
data-status={r.status}
data-skipped={r.skipped ? "true" : undefined}
data-expandable={expandable}
onClick={
expandable
Expand Down
10 changes: 8 additions & 2 deletions playground/src/lib/auto-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,17 @@ async function runOne({
const id = `${serviceName}/${method.name}`;

if (SKIPPED_SERVICES.has(serviceName)) {
onUpdate(id, { status: "skipped" });
onUpdate(id, {
status: "skipped",
output: `${serviceName} service not yet wired up by hosts`,
});
return;
}
if (SKIPPED_METHODS.has(id)) {
onUpdate(id, { status: "skipped" });
onUpdate(id, {
status: "skipped",
output: "host surface intentionally deferred",
});
return;
}
if (!method.exampleSource) {
Expand Down
67 changes: 51 additions & 16 deletions playground/src/lib/diagnosis-report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,42 @@ export function detectHostMode(): HostMode {
export function renderReportMarkdown(
services: ServiceInfo[],
results: Record<string, TestEntry>,
meta: { mode?: HostMode } = {},
meta: { mode?: HostMode; dropSuccessDetails?: boolean } = {},
): string {
const mode = meta.mode ?? detectHostMode();
const lines: string[] = [];
lines.push(`## Truapi ${mode} Diagnosis`);
lines.push("");
lines.push("| Method | Status | Details |");
lines.push("| --- | --- | --- |");
let pass = 0;
let fail = 0;
const rows: string[] = [];
for (const svc of services) {
for (const m of svc.methods) {
const id = `${svc.name}/${m.name}`;
const entry = results[id];
const status = entry?.status ?? "idle";
// Skipped methods (e.g. services the host hasn't wired up) are omitted
// from the report entirely so they never reach the compatibility matrix.
if (status === "skipped") continue;
lines.push(`| \`${id}\` | ${ICON[status]} | ${detailCell(entry)} |`);
// Skipped methods are reported as failed so every truapi method stays in
// the compatibility matrix (the aggregator keeps only ✅/❌ cells); the
// reason the method was skipped travels in the Details column.
const reportStatus = status === "skipped" ? "fail" : status;
if (reportStatus === "pass") pass++;
else if (reportStatus === "fail") fail++;
// The issue-URL variant drops success-row details (bulky response
// payloads) to keep the URL under GitHub's length limit; failures keep
// their (short) details.
const detail =
meta.dropSuccessDetails && reportStatus === "pass"
? ""
: detailCell(entry);
rows.push(`| \`${id}\` | ${ICON[reportStatus]} | ${detail} |`);
}
}

const lines: string[] = [];
lines.push(`## Truapi ${mode} Diagnosis`);
lines.push("");
lines.push(`**${pass} success · ${fail} failed**`);
lines.push("");
lines.push("| Method | Status | Details |");
lines.push("| --- | --- | --- |");
lines.push(...rows);
return lines.join("\n");
}

Expand All @@ -76,16 +93,34 @@ function detailCell(entry: TestEntry | undefined): string {
// diagnosis-report workflow turns each into a PR under diagnosis-reports/.
const REPORT_ISSUE_URL = "https://github.com/paritytech/truapi/issues/new";

// GitHub / browsers reject issue URLs beyond ~8 KB. Cap the whole URL below
// that; a report with many verbose failures can still exceed it even after
// success-row details are dropped.
const MAX_ISSUE_URL_LENGTH = 7000;

/**
* Pre-filled GitHub issue URL carrying `report` for a given host `mode`. The
* title format and the report's `## Truapi <mode> Diagnosis` heading are what
* the workflow parses, so they live here next to `renderReportMarkdown`.
*
* If the report is still too large to fit in the URL, fall back to a short
* placeholder body — the caller copies the full report to the clipboard, so
* the user pastes it into the issue instead.
*/
export function reportIssueUrl(report: string, mode: HostMode): string {
const params = new URLSearchParams({
labels: "diagnosis-report",
title: `Diagnosis report: ${mode}`,
body: report,
});
return `${REPORT_ISSUE_URL}?${params.toString()}`;
const buildUrl = (body: string): string => {
const params = new URLSearchParams({
labels: "diagnosis-report",
title: `Diagnosis report: ${mode}`,
body,
});
return `${REPORT_ISSUE_URL}?${params.toString()}`;
};

const full = buildUrl(report);
if (full.length <= MAX_ISSUE_URL_LENGTH) return full;
return buildUrl(
`_Diagnosis report was too large to prefill (${report.length} chars) — ` +
`it has been copied to your clipboard, please paste it here._`,
);
}
7 changes: 6 additions & 1 deletion playground/tests/e2e/dotli-diagnosis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,8 +534,13 @@ async function runDiagnosisOnce(page: Page): Promise<{
if (report.trim().length === 0) {
throw new Error("diagnosis report markdown is empty");
}
// Skipped methods render as failed (and appear failed in the matrix), but they
// are intentional gaps — exclude them from the CI hard-fail gate so only
// genuine failures fail the run.
const failedMethods = await frame
.locator('[data-testid="diagnosis-row"][data-status="fail"] .diag__name')
.locator(
'[data-testid="diagnosis-row"][data-status="fail"]:not([data-skipped="true"]) .diag__name',
)
.allInnerTexts();

await frame.locator('[data-testid="diagnosis-copy-report"]').click();
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/truapi/src/api/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ pub trait Signing: Send + Sync {
/// blockNumber: "0x00000000",
/// era: "0x00",
/// genesisHash: PASEO_NEXT_V2_ASSET_HUB.genesis,
/// method: "0x0000",
/// method: "0x00003448656c6c6f2c20776f726c6421",
/// nonce: "0x00000000",
/// signedExtensions: [],
/// specVersion: "0x00000000",
Expand Down