From 4f9b237da5468d9aa8301432ed9967384d152ecb Mon Sep 17 00:00:00 2001 From: Imod7 Date: Tue, 14 Jul 2026 17:09:02 +0200 Subject: [PATCH 1/2] fix: updates in diagnosis report + submit report button --- playground/CLAUDE.md | 4 +- playground/src/components/DiagnosisView.tsx | 21 +++++-- playground/src/lib/auto-test.ts | 10 ++- playground/src/lib/diagnosis-report.ts | 67 ++++++++++++++++----- rust/crates/truapi/src/api/signing.rs | 2 +- 5 files changed, 77 insertions(+), 27 deletions(-) diff --git a/playground/CLAUDE.md b/playground/CLAUDE.md index 093e4c60..aa834130 100644 --- a/playground/CLAUDE.md +++ b/playground/CLAUDE.md @@ -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 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 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 · N skipped**` summary line, and one row per method — skipped rows included with the `⏭` marker (the matrix aggregator maps any non-`✅`/`❌` cell to "not measured", so they stay out of the status grid). Deterministic for a given set of results (no timestamp). `renderReportMarkdown(…, { dropSuccessDetails })` emits a compact variant (success-row details blanked, failures/skips 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 / skipped counts, 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`. | diff --git a/playground/src/components/DiagnosisView.tsx b/playground/src/components/DiagnosisView.tsx index 0200a0b6..da6b47b9 100644 --- a/playground/src/components/DiagnosisView.tsx +++ b/playground/src/components/DiagnosisView.tsx @@ -44,10 +44,11 @@ export function DiagnosisView({ const [copied, setCopied] = useState(false); const [expandedId, setExpandedId] = useState(null); - const { rows, hasResults, passCount, failCount } = useMemo(() => { + const { rows, hasResults, passCount, failCount, skipCount } = useMemo(() => { const out: Row[] = []; let pass = 0; let fail = 0; + let skip = 0; for (const svc of services) { for (const m of svc.methods) { const id = `${svc.name}/${m.name}`; @@ -55,6 +56,7 @@ export function DiagnosisView({ const status = entry?.status ?? "idle"; if (status === "pass") pass++; else if (status === "fail") fail++; + else if (status === "skipped") skip++; out.push({ id, service: svc.name, @@ -69,6 +71,7 @@ export function DiagnosisView({ hasResults: Object.keys(testResults).length > 0, passCount: pass, failCount: fail, + skipCount: skip, }; }, [services, testResults]); @@ -93,12 +96,17 @@ export function DiagnosisView({ // Open a pre-filled GitHub issue carrying the report; the diagnosis-report // workflow writes it to diagnosis-reports/.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/skip 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 }); }; @@ -164,6 +172,7 @@ export function DiagnosisView({ data-has-fail={!isRunning && failCount > 0} > {passCount} success · {failCount} failed + {skipCount > 0 ? ` · ${skipCount} skipped` : ""} )} {hasResults && !isRunning && ( diff --git a/playground/src/lib/auto-test.ts b/playground/src/lib/auto-test.ts index 56ed0bbb..11c92d34 100644 --- a/playground/src/lib/auto-test.ts +++ b/playground/src/lib/auto-test.ts @@ -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) { diff --git a/playground/src/lib/diagnosis-report.ts b/playground/src/lib/diagnosis-report.ts index 0dcda973..daa50e15 100644 --- a/playground/src/lib/diagnosis-report.ts +++ b/playground/src/lib/diagnosis-report.ts @@ -44,25 +44,42 @@ export function detectHostMode(): HostMode { export function renderReportMarkdown( services: ServiceInfo[], results: Record, - 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; + let skip = 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)} |`); + if (status === "pass") pass++; + else if (status === "fail") fail++; + else if (status === "skipped") skip++; + // Skipped rows carry the ⏭ marker; the compatibility-matrix aggregator + // maps any non-✅/❌ cell to "not measured", so they stay out of the matrix. + // The issue-URL variant drops success-row details (bulky response + // payloads) to keep the URL under GitHub's length limit; failures and + // skips keep their (short) details. + const detail = + meta.dropSuccessDetails && status === "pass" ? "" : detailCell(entry); + rows.push(`| \`${id}\` | ${ICON[status]} | ${detail} |`); } } + + const lines: string[] = []; + lines.push(`## Truapi ${mode} Diagnosis`); + lines.push(""); + lines.push( + `**${pass} success · ${fail} failed${skip > 0 ? ` · ${skip} skipped` : ""}**`, + ); + lines.push(""); + lines.push("| Method | Status | Details |"); + lines.push("| --- | --- | --- |"); + lines.push(...rows); return lines.join("\n"); } @@ -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 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._`, + ); } diff --git a/rust/crates/truapi/src/api/signing.rs b/rust/crates/truapi/src/api/signing.rs index 699609f1..3e6b43ab 100644 --- a/rust/crates/truapi/src/api/signing.rs +++ b/rust/crates/truapi/src/api/signing.rs @@ -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", From 166f13bb5f5ef51a7633c57a817851042ecb70b9 Mon Sep 17 00:00:00 2001 From: Imod7 Date: Wed, 15 Jul 2026 18:04:27 +0200 Subject: [PATCH 2/2] revert the skipped handling introduced earlier --- playground/CLAUDE.md | 4 ++-- playground/src/components/DiagnosisView.tsx | 19 +++++++++------ playground/src/lib/diagnosis-report.ts | 26 ++++++++++----------- playground/tests/e2e/dotli-diagnosis.ts | 7 +++++- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/playground/CLAUDE.md b/playground/CLAUDE.md index aa834130..db388e28 100644 --- a/playground/CLAUDE.md +++ b/playground/CLAUDE.md @@ -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 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 · N skipped**` summary line, and one row per method — skipped rows included with the `⏭` marker (the matrix aggregator maps any non-`✅`/`❌` cell to "not measured", so they stay out of the status grid). Deterministic for a given set of results (no timestamp). `renderReportMarkdown(…, { dropSuccessDetails })` emits a compact variant (success-row details blanked, failures/skips 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/diagnosis-report.ts` | Renders the diagnosis results as a copy-pasteable GitHub-flavoured markdown table: a `## Truapi 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, a summary showing success / failed / skipped counts, 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/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`. | diff --git a/playground/src/components/DiagnosisView.tsx b/playground/src/components/DiagnosisView.tsx index da6b47b9..520bc3f2 100644 --- a/playground/src/components/DiagnosisView.tsx +++ b/playground/src/components/DiagnosisView.tsx @@ -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; } @@ -44,24 +47,27 @@ export function DiagnosisView({ const [copied, setCopied] = useState(false); const [expandedId, setExpandedId] = useState(null); - const { rows, hasResults, passCount, failCount, skipCount } = useMemo(() => { + const { rows, hasResults, passCount, failCount } = useMemo(() => { const out: Row[] = []; let pass = 0; let fail = 0; - let skip = 0; for (const svc of services) { 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++; - else if (status === "skipped") skip++; out.push({ id, service: svc.name, method: m.name, status, + skipped: rawStatus === "skipped", output: entry?.output, }); } @@ -71,7 +77,6 @@ export function DiagnosisView({ hasResults: Object.keys(testResults).length > 0, passCount: pass, failCount: fail, - skipCount: skip, }; }, [services, testResults]); @@ -98,7 +103,7 @@ export function DiagnosisView({ // opens the link via `navigate_to` (a sandboxed app can't `window.open`). // 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/skip details. + // GitHub's URL-length limit while keeping failure details. const handleSubmitReport = () => { void navigator.clipboard?.writeText(reportMarkdown).catch(() => {}); const mode = detectHostMode(); @@ -172,7 +177,6 @@ export function DiagnosisView({ data-has-fail={!isRunning && failCount > 0} > {passCount} success · {failCount} failed - {skipCount > 0 ? ` · ${skipCount} skipped` : ""} )} {hasResults && !isRunning && ( @@ -215,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 diff --git a/playground/src/lib/diagnosis-report.ts b/playground/src/lib/diagnosis-report.ts index daa50e15..8dfb4d2c 100644 --- a/playground/src/lib/diagnosis-report.ts +++ b/playground/src/lib/diagnosis-report.ts @@ -49,33 +49,33 @@ export function renderReportMarkdown( const mode = meta.mode ?? detectHostMode(); let pass = 0; let fail = 0; - let skip = 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"; - if (status === "pass") pass++; - else if (status === "fail") fail++; - else if (status === "skipped") skip++; - // Skipped rows carry the ⏭ marker; the compatibility-matrix aggregator - // maps any non-✅/❌ cell to "not measured", so they stay out of the matrix. + // 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 and - // skips keep their (short) details. + // payloads) to keep the URL under GitHub's length limit; failures keep + // their (short) details. const detail = - meta.dropSuccessDetails && status === "pass" ? "" : detailCell(entry); - rows.push(`| \`${id}\` | ${ICON[status]} | ${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${skip > 0 ? ` · ${skip} skipped` : ""}**`, - ); + lines.push(`**${pass} success · ${fail} failed**`); lines.push(""); lines.push("| Method | Status | Details |"); lines.push("| --- | --- | --- |"); diff --git a/playground/tests/e2e/dotli-diagnosis.ts b/playground/tests/e2e/dotli-diagnosis.ts index f0c9c2b4..207b65b4 100644 --- a/playground/tests/e2e/dotli-diagnosis.ts +++ b/playground/tests/e2e/dotli-diagnosis.ts @@ -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();