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
46 changes: 46 additions & 0 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,52 @@ describe("Footer changelog link", () => {
});
});

// #96 — skip-to-main-content link visible on focus
describe("Skip-to-main-content link (#96)", () => {
it("carries the skip-link class so the CSS can show it on focus", () => {
render(<App />);
const skipLink = screen.getByRole("link", { name: "Skip to main content" });
expect(skipLink).toHaveClass("skip-link");
});
});

// #97 — audit command reference section
describe("Audit command reference (#97)", () => {
it("renders a focusable audit-reference section", () => {
render(<App />);
expect(document.getElementById("audit-reference")).toHaveAttribute("tabindex", "-1");
expect(screen.getByRole("heading", { name: /audit workflow, at a glance/i })).toBeInTheDocument();
});

it("exposes the audit reference as a navigation link", () => {
render(<App />);
const link = screen.getByRole("link", { name: "Audit reference" });
expect(link).toHaveAttribute("href", "#audit-reference");
});

it("documents the ready-to-run command and its arguments", () => {
render(<App />);
expect(screen.getByLabelText("Audit command example")).toHaveTextContent("soroban-testkit audit");
expect(screen.getAllByText("--strict").length).toBeGreaterThan(0);
});
});

// #99 — accessible description for the generated command output
describe("Generated command output accessibility (#99)", () => {
it("wraps the output in a named region", () => {
render(<App />);
expect(screen.getByRole("region", { name: "Generated command output" })).toBeInTheDocument();
});

it("includes a visually-hidden description within the output region", () => {
render(<App />);
const region = screen.getByRole("region", { name: "Generated command output" });
const desc = document.getElementById(region.getAttribute("aria-describedby") ?? "");
expect(desc).toBeInTheDocument();
expect(desc?.textContent).toMatch(/copy this command/i);
});
});

// #116 — accessibility statement
describe("Accessibility statement", () => {
it("renders the accessibility link in the footer", () => {
Expand Down
65 changes: 64 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const navSections = [
{ id: "install", label: "Install" },
{ id: "modules", label: "Modules" },
{ id: "commands", label: "Commands" },
{ id: "audit-reference", label: "Audit reference" },
{ id: "limits-reference", label: "Limits reference" },
] as const;
const navSectionIds = navSections.map((section) => section.id);
Expand Down Expand Up @@ -1181,7 +1182,15 @@ export default function App() {
)}
</section>

<div className="output">
<div
className="output"
role="region"
aria-label="Generated command output"
aria-describedby="command-output-desc"
>
<span id="command-output-desc" className="sr-only">
Copy this command to your terminal to run the selected workflow.
</span>
<code data-testid="command-output">{highlightCommand(generated)}</code>
<button onClick={copy}>{copied ? "Copied" : "Copy"}</button>
</div>
Expand Down Expand Up @@ -1477,6 +1486,60 @@ export default function App() {
)}
</article>

<section
className="command-reference"
id="audit-reference"
tabIndex={-1}
aria-labelledby="audit-reference-heading"
>
<div className="reference-header">
<p className="eyebrow">Command reference</p>
<h2 id="audit-reference-heading">Audit workflow, at a glance.</h2>
<p>
Use <code>soroban-testkit audit</code> to scan contract sources for authentication
gaps, arithmetic pitfalls, and TTL misuse. The scan is read-only and runs entirely
offline.
</p>
</div>
<div className="reference-grid">
<article className="reference-card">
<h3>Ready-to-run command</h3>
<pre aria-label="Audit command example"><code>{"soroban-testkit audit \\\n ./src \\\n --strict"}</code></pre>
<p>
Replace <code>./src</code> with the path to your contract source directory. Omit{" "}
<code>--strict</code> to treat warnings as informational only.
</p>
</article>
<article className="reference-card">
<h3>Arguments</h3>
<dl className="reference-arguments">
<div>
<dt><code>path</code></dt>
<dd>
Contract source directory. Defaults to{" "}
<code>{localFileGuides.audit.defaultPath}</code>.
</dd>
</div>
<div>
<dt><code>--strict</code></dt>
<dd>Treat warning-level findings as errors and exit non-zero.</dd>
</div>
</dl>
</article>
<article className="reference-card">
<h3>Before you run it</h3>
<ol>
<li>Point the path at the directory that contains your <code>*.rs</code> source files.</li>
<li>Review auth and storage warnings first — they surface the most critical privilege issues.</li>
<li>Enable strict mode before release to make the audit gate your CI pipeline.</li>
</ol>
<p className="reference-note">
{localFileGuides.audit.offlineGuarantee}
</p>
</article>
</div>
</section>

<section
className="command-reference"
id="limits-reference"
Expand Down
19 changes: 19 additions & 0 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,22 @@ a { color: inherit; text-decoration: none; }
button, input, select { font: inherit; }
main { max-width: 1440px; margin: auto; overflow: hidden; }

/* Skip-to-main-content link (#96) — hidden until focused */
.skip-link {
position: absolute;
top: -100%;
left: var(--page-padding);
z-index: 999;
padding: var(--space-md) var(--space-lg);
background: var(--color-accent);
color: var(--color-accent-text-dark);
font-weight: 600;
border-radius: var(--radius-sm);
text-decoration: none;
transition: top .15s ease;
}
.skip-link:focus { top: var(--space-lg); }

/* Navigation with reduced-width adaptation */
nav {
height: 78px;
Expand Down Expand Up @@ -141,6 +157,8 @@ h1 { margin: var(--space-xl) 0 var(--space-3xl); font-size: clamp(3.4rem, 6.5vw,
.button { padding: 13px 19px; border-radius: var(--radius-base); font-weight: 600; font-size: .9rem; cursor: pointer; }
.primary { color: var(--color-accent-text-dark); background: var(--color-accent); border: 0; }
.secondary { border: 1px solid var(--color-border-strong); background: transparent; color: var(--color-text-primary); }
/* WCAG AA focus indicators for all button variants (#98) */
.button:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; }

/* Terminal */
.terminal { background: var(--color-surface-terminal); border: 1px solid var(--color-border-default); border-radius: var(--radius-xl); box-shadow: 0 30px 80px #0008; transform: rotate(1deg); overflow: hidden; font-family: var(--font-mono); }
Expand Down Expand Up @@ -288,6 +306,7 @@ input:invalid:focus, select:invalid:focus { border-color: #ff6b6b; box-shadow: 0
.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; }
.output button:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 3px; }
.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; }
Expand Down