From bede40041136a1166a37b53ed8302997003afef1 Mon Sep 17 00:00:00 2001 From: Chidubemkingsley Date: Sun, 18 Oct 2026 20:40:54 +0100 Subject: [PATCH] Add workflow guides and input UX --- README.md | 24 +++++++++ package-lock.json | 91 ++++++++++++++++++--------------- package.json | 5 +- src/App.test.tsx | 33 ++++++++++++ src/App.tsx | 127 +++++++++++++++++++++++++++++++++++++++++++--- src/styles.css | 70 +++++++++++++++++++++++++ 6 files changed, 299 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index dcdea19..2921135 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,30 @@ The interface does not execute contracts in the browser. Generated commands run - **Module Filter & Empty State**: Search and filter testing modules by name or description with real-time feedback and an accessible empty-state layout with quick filter reset. - **Responsive & Reduced-Width Navigation**: Accessible navigation layout supporting truncated and responsive labels without overflowing viewport on mobile or narrow screens. - **Required vs. Optional Setting Distinction**: Visual badges (`Required` / `Optional`) and `aria-required` attributes in the CLI workbench distinguishing mandatory parameters from optional configuration flags. +- **Clear Input Controls**: Fast keyboard- and mouse-friendly clearing for project paths, function names, and ramp parameters to recover from mistakes without retyping values. +- **Command Length Indicator**: Displays the current generated command length in characters so terminal copy workflows are easier to reason about before execution. +- **Workflow Guides**: Built-in walkthroughs for the Coverage report guide and the Audit workflow tutorial to explain what to look for in generated reports and how to apply the flow in practice. + +## Coverage report guide + +Use the coverage workflow to answer three questions quickly: + +1. Does the crate actually execute the risky paths you care about? +2. Which branches or lines remain untested after the most recent change? +3. Are the low-coverage hotspots aligned with the contract risk model? + +A healthy report usually combines several signals: broad branch coverage, a few high-value untested edge cases, and a clear reduction in missing assertions around security-sensitive paths. + +## Audit workflow tutorial + +The audit workflow is best used as a structured validation pass rather than a single command: + +1. Point the audit at the exact contract directory or workspace area that changed. +2. Review auth, arithmetic, and TTL findings before anything else. +3. Verify each warning with a targeted regression or invariant test. +4. Use `--strict` when the release gate should fail on warning-level issues. + +This keeps the output actionable without turning the scanner into a noisy blocker. ## Development diff --git a/package-lock.json b/package-lock.json index 77ae40b..40594d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ }, "devDependencies": { "@eslint/js": "latest", + "@playwright/test": "latest", + "@testing-library/dom": "^10.4.2", "@testing-library/jest-dom": "latest", "@testing-library/react": "latest", "@types/react": "latest", @@ -811,6 +813,22 @@ "url": "https://github.com/sponsors/oxc-project" } }, + "node_modules/@playwright/test": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.64.1.tgz", + "integrity": "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@rolldown/binding-android-arm-eabi": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz", @@ -914,9 +932,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -933,9 +948,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -952,9 +964,6 @@ "cpu": [ "ppc64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -971,9 +980,6 @@ "cpu": [ "s390x" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -990,9 +996,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1009,9 +1012,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1081,7 +1081,6 @@ "integrity": "sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -1165,8 +1164,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/chai": { "version": "5.2.3", @@ -1583,7 +1581,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -1594,7 +1591,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -1867,8 +1863,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.5.451", @@ -2633,9 +2628,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2656,9 +2648,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2679,9 +2668,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2702,9 +2688,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -2790,7 +2773,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -2995,6 +2977,35 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.64.1.tgz", + "integrity": "sha512-HCrVhHr5QQuwYoswEUGTb7gGp522yflbv/ST8nF4sFWGuLjQCt17Q/EubpGGSNKYn1tsTCPZxqHhHCfVURu7UA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.64.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.64.1.tgz", + "integrity": "sha512-BC4s8y6SEVA6Q1rSxZTto8hI1KUI3xXlHGZgvGqP4IqPdI/a6FX9H29IDNz7vKo9l21b6LfYosScPL6M3xxyDQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/postcss": { "version": "8.5.29", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.29.tgz", @@ -3039,7 +3050,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -3105,8 +3115,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redent": { "version": "3.0.0", diff --git a/package.json b/package.json index cbffd69..8f7ac0d 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,14 @@ }, "dependencies": { "@vitejs/plugin-react": "latest", - "vite": "latest", "react": "latest", - "react-dom": "latest" + "react-dom": "latest", + "vite": "latest" }, "devDependencies": { "@eslint/js": "latest", "@playwright/test": "latest", + "@testing-library/dom": "^10.4.2", "@testing-library/jest-dom": "latest", "@testing-library/react": "latest", "@types/react": "latest", diff --git a/src/App.test.tsx b/src/App.test.tsx index 76d1bbd..a1020e0 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -178,3 +178,36 @@ describe("Visual distinction between required and optional settings (#196)", () expect(screen.getByText("Optional")).toBeInTheDocument(); }); }); + +describe("Workflow guidance and input controls", () => { + it("shows coverage and audit workflow guidance panels with keyboard-accessible summaries", () => { + render(); + + expect(screen.getByRole("heading", { name: /coverage report guide/i })).toBeInTheDocument(); + expect(screen.getByRole("heading", { name: /audit workflow tutorial/i })).toBeInTheDocument(); + expect(screen.getByText(/command length/i)).toBeInTheDocument(); + }); + + it("clears path, function, and ramp fields with dedicated controls", () => { + render(); + + const pathInput = screen.getByLabelText(/project or contract path/i); + fireEvent.change(pathInput, { target: { value: "./custom/path" } }); + fireEvent.click(screen.getByRole("button", { name: /clear/i })); + expect(pathInput).toHaveValue(""); + + fireEvent.click(screen.getByRole("button", { name: "limits" })); + + const fnInput = screen.getByLabelText(/function/i); + const rampInput = screen.getByLabelText(/ramp parameter/i); + + fireEvent.change(fnInput, { target: { value: "withdraw" } }); + fireEvent.change(rampInput, { target: { value: "balances" } }); + + fireEvent.click(screen.getAllByRole("button", { name: /clear/i })[0]); + expect(fnInput).toHaveValue(""); + + fireEvent.click(screen.getAllByRole("button", { name: /clear/i })[0]); + expect(rampInput).toHaveValue(""); + }); +}); diff --git a/src/App.tsx b/src/App.tsx index 0e00718..0158451 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,8 @@ const descriptions: Record = { audit: "Scan contract sources for common auth, arithmetic, and TTL risks.", }; +const fieldClasses = "field-input"; + export default function App() { const [command, setCommand] = useState("coverage"); const [path, setPath] = useState("."); @@ -18,6 +20,8 @@ export default function App() { const [strict, setStrict] = useState(true); const [copied, setCopied] = useState(false); const [moduleFilter, setModuleFilter] = useState(""); + const [coverageGuideOpen, setCoverageGuideOpen] = useState(true); + const [auditGuideOpen, setAuditGuideOpen] = useState(true); const generated = useMemo( () => buildCommand({ command, path, format, open, fn, ramp, strict }), @@ -149,11 +153,25 @@ export default function App() { Project or contract path Required - setPath(event.target.value)} - aria-required="true" - /> +
+ setPath(event.target.value)} + aria-label="Project or contract path" + aria-required="true" + /> + {path && ( + + )} +
{command === "coverage" && <> } {command === "audit" && } -
{generated}
+
+
{generated}
+
+ Command length: {generated.length} characters +
+
+
+
+
+

Coverage report guide

+ +
+ {coverageGuideOpen && ( +
+
    +
  1. Start with the local project path so the tool can read the crate and tests in context.
  2. +
  3. Choose HTML output when you want a visual report and open it immediately after generation.
  4. +
  5. Read branch and line coverage together: low execution density usually explains weak confidence more than raw percentages.
  6. +
  7. Use the report to prioritize untested edge cases before shipping a contract update.
  8. +
+
+ )} +
+ +
+
+

Audit workflow tutorial

+ +
+ {auditGuideOpen && ( +
+
    +
  1. Point the audit at the contract source directory or workspace root most relevant to the change.
  2. +
  3. Review auth and storage access warnings first; they often explain failure modes and privilege issues.
  4. +
  5. Use the strict mode toggle when you want warning-level findings to block release readiness.
  6. +
  7. Pair each finding with a focused regression test before finalizing the deployment checklist.
  8. +
+
+ )} +
+
+ ); diff --git a/src/styles.css b/src/styles.css index 258df30..2531636 100644 --- a/src/styles.css +++ b/src/styles.css @@ -249,14 +249,84 @@ label { display: grid; gap: 9px; margin-bottom: var(--space-2xl); color: var(--c } .row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-xl); } +.field-with-clear { + display: flex; + align-items: center; + gap: 10px; +} +.field-input { width: 100%; } input, select { width: 100%; padding: var(--space-md) 13px; color: var(--color-text-primary); background: var(--color-input-bg); border: 1px solid var(--color-border-input); border-radius: var(--radius-sm); outline: none; } input:focus, select:focus { border-color: var(--color-accent); } +.clear-field-button { + border: 1px solid var(--color-border-strong); + background: transparent; + color: var(--color-text-secondary); + padding: 8px 10px; + border-radius: var(--radius-sm); + cursor: pointer; + white-space: nowrap; +} +.clear-field-button:hover, .clear-field-button:focus-visible { + color: var(--color-accent); + border-color: var(--color-accent); +} .check { display: flex; align-items: center; justify-content: space-between; } .check-content { display: flex; align-items: center; gap: var(--space-sm); } .check input { width: auto; accent-color: var(--color-accent); } +.output-wrap { display: grid; gap: 10px; } .output { display: flex; align-items: center; gap: 15px; padding: var(--space-lg); background: var(--color-surface-output); border-radius: var(--radius-md); } .output code { flex: 1; color: var(--color-accent); font: .75rem/1.6 var(--font-mono); overflow-wrap: anywhere; } .output button { color: var(--color-canvas); background: var(--color-accent); border: 0; border-radius: var(--radius-xs); padding: var(--space-sm) var(--space-md); cursor: pointer; font-weight: 600; } +.command-meta { + color: var(--color-text-secondary); + font: 500 .72rem var(--font-mono); + letter-spacing: .04em; + text-transform: uppercase; +} +.guides { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 24px; + padding: 0 var(--page-padding) var(--space-section); +} +.guide-card { + border: 1px solid var(--color-border-default); + border-radius: var(--radius-lg); + background: var(--color-surface); + padding: 24px; +} +.guide-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 12px; +} +.guide-header h3 { + margin: 0; + color: var(--color-accent); + font: 500 1.1rem var(--font-mono); +} +.guide-toggle { + background: transparent; + border: 1px solid var(--color-border-strong); + border-radius: var(--radius-sm); + color: var(--color-text-primary); + padding: 8px 10px; + cursor: pointer; +} +.guide-toggle:hover, .guide-toggle:focus-visible { + border-color: var(--color-accent); + color: var(--color-accent); +} +.guide-panel { + color: var(--color-text-body); + line-height: 1.7; +} +.guide-panel ol { + margin: 0; + padding-left: 1.2rem; +} .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; } footer { display: flex; justify-content: space-between; padding: 34px var(--page-padding); border-top: 1px solid var(--color-border-subtle); color: var(--color-text-muted); font-size: .78rem; }