Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ The interface does not execute contracts in the browser. Generated commands run
- **Required vs. Optional Setting Distinction**: Visual badges (`Required` / `Optional`) and `aria-required` attributes in the CLI workbench distinguishing mandatory parameters from optional configuration flags.
- **Audit workflow support**: An expandable, keyboard-accessible preflight checklist, strict-mode control, and advanced multiline command editor help users prepare and reproduce source audits.
- **Limits command reference**: A standalone, keyboard-accessible walkthrough builds the Sororail batch-payout contract and runs `soroban-testkit limits` against its `execute_equal` entry point, alongside argument defaults and offline/read-only behavior.
- **Enhanced Copy Options**: Multiple ways to copy commands:
- **Copy with Comments** (#144): Export commands with inline explanatory comments for documentation and learning.
- **Copy as Script** (#145): Generate complete, executable bash scripts with variables and error handling.
- **Workflow Management**:
- **Reset to Defaults** (#141): Quickly reset the active workflow to its default configuration.
- **Command History** (#143): Restore previously configured commands with one click (maintains last 10 commands).

## Development

Expand Down
128 changes: 123 additions & 5 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { type KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState }
import {
buildCommand,
formatCommandLine,
formatCommandWithComments,
formatCommandAsScript,
modules,
moduleCategories,
type CommandName,
Expand Down Expand Up @@ -235,6 +237,10 @@ export default function App() {
const [shell, setShell] = useState<"bash" | "zsh" | "fish" | "sh">("bash");
const [copied, setCopied] = useState(false);
const [copiedStep, setCopiedStep] = useState<number | null>(null);
const [copiedWithComments, setCopiedWithComments] = useState(false);
const [copiedAsScript, setCopiedAsScript] = useState(false);
// Issue #143: Command history for restore functionality
const [commandHistory, setCommandHistory] = useState<CommandOptions[]>([]);
// Copy buttons change their label, which screen readers often skip; this
// polite live region announces the result explicitly (#108).
const [announcement, setAnnouncement] = useState("");
Expand All @@ -261,14 +267,9 @@ export default function App() {
const [auditGuideOpen, setAuditGuideOpen] = useState(false);
const [checkedPrerequisites, setCheckedPrerequisites] = useState<Record<string, boolean>>({});
const comparePanelRef = useRef<HTMLDivElement>(null);
const [showBackToTop, setShowBackToTop] = useState(false);
const [categoryFilter, setCategoryFilter] = useState<string | null>(null);
const [selectedModule, setSelectedModule] = useState<string | null>(null);
// #117 — mobile nav toggle
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const mobileMenuRef = useRef<HTMLDivElement>(null);
// #118 — module table of contents
const [tocOpen, setTocOpen] = useState(false);
// #116 — accessibility statement modal
const [a11yOpen, setA11yOpen] = useState(false);
const [activeSection, setActiveSection] = useActiveSection(navSectionIds);
Expand Down Expand Up @@ -400,6 +401,56 @@ export default function App() {
setMultilineEditor(false);
};

// Issue #141: Reset to defaults for active workflow only
const resetToDefaults = () => {
if (!window.confirm(`Reset ${command} workflow to its default values?`)) return;

if (command === "coverage") {
setPath(".");
setFormat("html");
setOpen(true);
setCoverageOrder(defaultCoverageOrder);
} else if (command === "limits") {
setPath("");
setFn("batch_payout");
setRamp("recipients");
} else if (command === "audit") {
setPath("");
setStrict(true);
}
setShell("bash");
setMultilineEditor(false);
};

// Issue #143: Save current configuration to history
const saveToHistory = () => {
setCommandHistory((prev) => {
const newHistory = [{ ...options }, ...prev];
// Keep only last 10 commands
return newHistory.slice(0, 10);
});
};

// Issue #143: Restore previous command from history
const restorePrevious = () => {
if (commandHistory.length === 0) return;

const previous = commandHistory[0];
setCommand(previous.command);
setPath(previous.path);
setFormat(previous.format);
setOpen(previous.open);
setFn(previous.fn);
setRamp(previous.ramp);
setStrict(previous.strict);
if (previous.shell) setShell(previous.shell);
if (previous.optionalArgOrder) setCoverageOrder(previous.optionalArgOrder);

// Remove from history after restoring
setCommandHistory((prev) => prev.slice(1));
setAnnouncement("Previous command restored");
};

const applyAdvancedCommand = () => {
const tokens = advancedCommand.match(/"[^"]+"|'[^']+'|[^\s]+/g) ?? [];
if (tokens.length === 0) return;
Expand Down Expand Up @@ -439,6 +490,7 @@ export default function App() {

const copy = async () => {
await copyToClipboard(generated);
saveToHistory(); // Save to history when copying
setCopied(true);
setAnnouncement("Command copied to clipboard");
window.setTimeout(() => {
Expand All @@ -447,6 +499,32 @@ export default function App() {
}, 1600);
};

// Issue #144: Copy command with explanatory comments
const copyWithComments = async () => {
const commented = formatCommandWithComments(options);
await copyToClipboard(commented);
saveToHistory(); // Save to history when copying
setCopiedWithComments(true);
setAnnouncement("Command with comments copied to clipboard");
window.setTimeout(() => {
setCopiedWithComments(false);
setAnnouncement("");
}, 1600);
};

// Issue #145: Copy command as reusable shell script
const copyAsScript = async () => {
const script = formatCommandAsScript(options);
await copyToClipboard(script);
saveToHistory(); // Save to history when copying
setCopiedAsScript(true);
setAnnouncement("Shell script copied to clipboard");
window.setTimeout(() => {
setCopiedAsScript(false);
setAnnouncement("");
}, 1600);
};

const copyStep = async (index: number, stepCommand: string) => {
await copyToClipboard(stepCommand);
setCopiedStep(index);
Expand Down Expand Up @@ -883,6 +961,26 @@ export default function App() {
<button type="button" className="button secondary" onClick={resetBuilder}>
Reset builder
</button>
{/* Issue #141: Reset to defaults for active workflow */}
<button
type="button"
className="button secondary"
onClick={resetToDefaults}
aria-label={`Reset ${command} workflow to defaults`}
>
Reset {command} to defaults
</button>
{/* Issue #143: Restore previous command */}
<button
type="button"
className="button secondary"
onClick={restorePrevious}
disabled={commandHistory.length === 0}
aria-label="Restore previous command"
title={commandHistory.length === 0 ? "No previous commands" : `Restore previous ${commandHistory[0]?.command || ''} command`}
>
Restore previous ({commandHistory.length})
</button>
</div>

<section className="summary-card" aria-labelledby="command-summary-heading">
Expand Down Expand Up @@ -1117,6 +1215,26 @@ export default function App() {
<button onClick={copy}>{copied ? "Copied" : "Copy"}</button>
</div>

{/* Issue #144 & #145: Additional copy options */}
<div className="copy-options">
<button
type="button"
className="button secondary"
onClick={copyWithComments}
aria-label="Copy command with explanatory comments"
>
{copiedWithComments ? "Copied with comments" : "Copy with comments"}
</button>
<button
type="button"
className="button secondary"
onClick={copyAsScript}
aria-label="Copy as shell script"
>
{copiedAsScript ? "Script copied" : "Copy as script"}
</button>
</div>

<div className="advanced-command-block">
<button type="button" className="button secondary" onClick={toggleAdvancedEditor}>
{multilineEditor ? "Hide advanced editor" : "Advanced arguments"}
Expand Down
10 changes: 10 additions & 0 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,16 @@ 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; }

/* Issue #144 & #145: Copy options styling */
.copy-options { display: flex; gap: var(--space-md); margin-block-start: var(--space-md); flex-wrap: wrap; }
.copy-options button { flex: 1; min-width: 200px; }
.copy-options button:disabled { opacity: 0.5; cursor: not-allowed; }

/* Issue #141 & #143: Command actions styling */
.command-actions { display: flex; gap: var(--space-md); margin-block-start: var(--space-md); flex-wrap: wrap; }
.command-actions button:disabled { opacity: 0.5; cursor: not-allowed; }

.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
Loading