diff --git a/docs/develop/standalone-activities-interactive-demo.mdx b/docs/develop/standalone-activities-interactive-demo.mdx new file mode 100644 index 0000000000..5750c3b3ed --- /dev/null +++ b/docs/develop/standalone-activities-interactive-demo.mdx @@ -0,0 +1,83 @@ +--- +id: standalone-activities-interactive-demo +title: Standalone Activities Demo +sidebar_label: Standalone Activities +toc_max_heading_level: 3 +keywords: + - standalone activity + - interactive demo +tags: + - Standalone Activities + - Temporal SDKs +description: An interactive overview of Temporal Standalone Activities. +--- + +:::tip SUPPORT, STABILITY, and DEPENDENCY INFO + +[Standalone Activities](/standalone-activity) are available as a +[Public Preview](/evaluate/development-production-features/release-stages#public-preview) feature +in the Go, Python, and .NET SDKs, and as a +[Pre-release](/evaluate/development-production-features/release-stages#pre-release) feature in the +Java and TypeScript SDKs. + +Standalone Activities require Temporal CLI v1.7.0 or higher and Temporal Server v1.31.0 or higher. + +::: + +Standalone Activities let you run a single Activity straight from your application without +writing a Workflow. Your code calls the Temporal Client, the Server durably enqueues the request +for a Worker to pick up, and the result comes back through a handle that your code can wait on or +check later. + +Try the demo below to walk through the full flow, tweak the retry and timeout settings, and watch +the SDK code and CLI command update as you go. + +import { StandaloneActivityDemo } from '@site/src/components'; + + + +--- + +## How it works + +When you call `client.ExecuteActivity()` (or the equivalent in your SDK), the following happens: + +1. **Connect**: Your application opens a connection to the Temporal Server using a Temporal Client + configured with your namespace and credentials. +2. **Schedule**: The Server durably persists the Activity Task on the specified Task Queue so that + the request survives Worker restarts and network interruptions. +3. **Poll**: A Worker that is polling that Task Queue picks up the Activity Task and prepares to + execute it. +4. **Execute**: The Worker runs your Activity function with the provided arguments and reports the + outcome back to the Server. +5. **Return**: The Server stores the result and returns it to the original caller through the + `ActivityHandle` that was created when the Activity was scheduled. + +Because the Server durably persists the Activity, it survives Worker restarts and network +interruptions. If the Activity fails, the Server automatically retries it according to the Retry +Policy you configure. + +### Standalone vs Workflow Activities + +| | Workflow Activity | Standalone Activity | +|---|---|---| +| Orchestrated by | A Workflow Definition | Your application code (via the Temporal Client) | +| Started with | SDK-specific (for example, `workflow.ExecuteActivity()` in Go) | SDK-specific (for example, `client.ExecuteActivity()` in Go, `client.StartActivityAsync()` in .NET) | +| Retry policy | Set in `ActivityOptions` inside your Workflow | Set in `StartActivityOptions` when calling the client | +| Visibility | Shown in the Workflow's Event History | Shown in the Activity list and count views | +| Use case | Multi-step orchestration with multiple Activities | Single, independent jobs like sending an email or processing a webhook | + +The Activity function and Worker registration are **identical** for both approaches, and only the +execution path that triggers the Activity differs between them. + +--- + +## Next steps + +For complete API reference and advanced usage, see the SDK-specific guides: + +- [Standalone Activities with the Go SDK](/develop/go/activities/standalone-activities) +- [Standalone Activities with the Java SDK](/develop/java/activities/standalone-activities) +- [Standalone Activities with the Python SDK](/develop/python/activities/standalone-activities) +- [Standalone Activities with the TypeScript SDK](/develop/typescript/activities/standalone-activities) +- [Standalone Activities with the .NET SDK](/develop/dotnet/activities/standalone-activities) diff --git a/sidebars.js b/sidebars.js index e606b87475..b9136644c0 100644 --- a/sidebars.js +++ b/sidebars.js @@ -1661,6 +1661,14 @@ module.exports = { 'web-ui', ], }, + { + type: 'category', + label: 'Interactive Demos', + collapsed: true, + items: [ + 'develop/standalone-activities-interactive-demo', + ], + }, 'glossary', 'with-ai', // { diff --git a/src/components/elements/StandaloneActivityDemo.js b/src/components/elements/StandaloneActivityDemo.js new file mode 100644 index 0000000000..b6348dce7b --- /dev/null +++ b/src/components/elements/StandaloneActivityDemo.js @@ -0,0 +1,624 @@ +import CodeBlock from '@theme/CodeBlock'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import styles from './standalone-activity-demo.module.css'; + +// --------------------------------------------------------------------------- +// Code generation +// --------------------------------------------------------------------------- + +const GREETING = 'Hello'; + +function generateSdkCode(language, config) { + const { activityId, taskQueue, name, timeout, timeoutType, simulateFailures, maxRetries } = config; + const expectedResult = `${GREETING}, ${name}!`; + + if (language === 'go') { + const timeoutField = + timeoutType === 'start_to_close' + ? `StartToCloseTimeout: ${timeout} * time.Second,` + : `ScheduleToCloseTimeout: ${timeout} * time.Second,`; + const retryImport = + simulateFailures && maxRetries > 0 + ? '// import "go.temporal.io/sdk/temporal"\n' + : ''; + const retryPolicy = + simulateFailures && maxRetries > 0 + ? `\n\tRetryPolicy: &temporal.RetryPolicy{\n\t\tMaximumAttempts: ${maxRetries + 1},\n\t},` + : ''; + return `${retryImport}activityOptions := client.StartActivityOptions{ +\tID: "${activityId}", +\tTaskQueue: "${taskQueue}", +\t${timeoutField}${retryPolicy} +} + +handle, err := c.ExecuteActivity(ctx, activityOptions, +\thelloworld.Activity, "${name}") +if err != nil { +\tlog.Fatalln("Unable to execute activity", err) +} + +var result string +err = handle.Get(ctx, &result) +// result: "${expectedResult}"`; + } + + if (language === 'python') { + const timeoutField = + timeoutType === 'start_to_close' + ? `start_to_close_timeout=timedelta(seconds=${timeout}),` + : `schedule_to_close_timeout=timedelta(seconds=${timeout}),`; + const retryImport = + simulateFailures && maxRetries > 0 + ? '# from temporalio.common import RetryPolicy\n' + : ''; + const retryPolicy = + simulateFailures && maxRetries > 0 + ? `\n retry_policy=RetryPolicy(\n maximum_attempts=${maxRetries + 1},\n ),` + : ''; + return `${retryImport}result = await client.execute_activity( + compose_greeting, + args=[ComposeGreetingInput("${GREETING}", "${name}")], + id="${activityId}", + task_queue="${taskQueue}", + ${timeoutField}${retryPolicy} +) +# result: "${expectedResult}"`; + } + + if (language === 'dotnet') { + const timeoutField = + timeoutType === 'start_to_close' + ? `StartToCloseTimeout = TimeSpan.FromSeconds(${timeout}),` + : `ScheduleToCloseTimeout = TimeSpan.FromSeconds(${timeout}),`; + const retryImport = + simulateFailures && maxRetries > 0 + ? '// using Temporalio.Common;\n' + : ''; + const retryPolicy = + simulateFailures && maxRetries > 0 + ? `\n RetryPolicy = new() { MaximumAttempts = ${maxRetries + 1} },` + : ''; + return `${retryImport}var result = await client.ExecuteActivityAsync( + () => MyActivities.ComposeGreetingAsync( + new ComposeGreetingInput("${GREETING}", "${name}")), + new("${activityId}", "${taskQueue}") + { + ${timeoutField}${retryPolicy} + }); +// result: "${expectedResult}"`; + } + + return ''; +} + +function generateCliCode(language, config) { + const { activityId, taskQueue, name, timeout, timeoutType } = config; + const timeoutFlag = + timeoutType === 'start_to_close' + ? `--start-to-close-timeout ${timeout}s` + : `--schedule-to-close-timeout ${timeout}s`; + + let activityType; + let inputFlag; + if (language === 'go') { + activityType = 'Activity'; + inputFlag = `--input '"${name}"'`; + } else if (language === 'python') { + activityType = 'compose_greeting'; + inputFlag = `--input '{"greeting": "${GREETING}", "name": "${name}"}'`; + } else { + activityType = 'ComposeGreeting'; + inputFlag = `--input '{"Greeting": "${GREETING}", "Name": "${name}"}'`; + } + + return `temporal activity execute \\ + --type ${activityType} \\ + --activity-id ${activityId} \\ + --task-queue ${taskQueue} \\ + ${timeoutFlag} \\ + ${inputFlag}`; +} + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +const LANGUAGES = [ + { id: 'go', label: 'Go' }, + { id: 'python', label: 'Python' }, + { id: 'dotnet', label: '.NET' }, +]; + +const FLOW_NODES = [ + { label: 'Client', sub: 'Your App' }, + { label: 'Server', sub: 'Temporal' }, + { label: 'Task Queue', sub: '' }, + { label: 'Worker', sub: '' }, + { label: 'Activity', sub: 'Function' }, +]; + +const IDLE_NODES = ['pending', 'pending', 'pending', 'pending', 'pending']; + +const DEFAULT_CONFIG = { + activityId: 'my-activity-id', + taskQueue: 'my-task-queue', + name: 'World', + timeout: 10, + timeoutType: 'start_to_close', + simulateFailures: false, + failCount: 1, + maxRetries: 2, +}; + +// --------------------------------------------------------------------------- +// Main component +// --------------------------------------------------------------------------- + +export default function StandaloneActivityDemo() { + const [language, setLanguage] = useState('go'); + const [config, setConfig] = useState({ ...DEFAULT_CONFIG }); + + const [sim, setSim] = useState({ + running: false, + nodeStates: [...IDLE_NODES], + log: [], + status: 'idle', // 'idle' | 'running' | 'completed' | 'failed' + result: null, + }); + + const [history, setHistory] = useState([]); + + const runIdRef = useRef(0); + const logScrollRef = useRef(null); + + // Auto-scroll log to bottom + useEffect(() => { + if (logScrollRef.current) { + logScrollRef.current.scrollTop = logScrollRef.current.scrollHeight; + } + }, [sim.log]); + + const updateConfig = useCallback((key, value) => { + setConfig((prev) => ({ ...prev, [key]: value })); + }, []); + + const handleExecute = useCallback(() => { + const runId = ++runIdRef.current; + const isCancelled = () => runIdRef.current !== runId; + const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + + const startTime = Date.now(); + const logEntries = []; + + const elapsed = () => ((Date.now() - startTime) / 1000).toFixed(2); + + /** Push a log entry and update React state atomically with nodeStates */ + const update = (nodeStates, msg, type = 'info') => { + if (isCancelled()) return; + if (msg) logEntries.push({ time: elapsed(), msg, type }); + setSim((prev) => ({ + ...prev, + running: true, + nodeStates, + log: [...logEntries], + status: 'running', + })); + }; + + /** Push a log-only entry (no node state change) */ + const addLog = (msg, type = 'info') => { + if (isCancelled()) return; + logEntries.push({ time: elapsed(), msg, type }); + setSim((prev) => ({ ...prev, log: [...logEntries] })); + }; + + // Reset UI immediately + setSim({ + running: true, + nodeStates: [...IDLE_NODES], + log: [], + status: 'running', + result: null, + }); + + (async () => { + let attempt = 1; + + // ── Step 0: connect ────────────────────────────────────────────────── + update( + ['active', 'pending', 'pending', 'pending', 'pending'], + 'Connecting to Temporal Server at localhost:7233...' + ); + await sleep(400); + if (isCancelled()) return; + + // ── Step 1: schedule ───────────────────────────────────────────────── + update( + ['completed', 'active', 'pending', 'pending', 'pending'], + `Scheduling activity "${config.activityId}" on task queue "${config.taskQueue}"...` + ); + await sleep(600); + if (isCancelled()) return; + + update(['completed', 'completed', 'pending', 'pending', 'pending'], null); + + // ── Retry loop ─────────────────────────────────────────────────────── + while (true) { + if (isCancelled()) return; + + // Step 2: worker poll + update( + ['completed', 'completed', 'active', 'pending', 'pending'], + attempt === 1 + ? 'Worker polling task queue for activity tasks...' + : `[Retry ${attempt - 1}] Worker polling task queue...` + ); + await sleep(550); + if (isCancelled()) return; + + // Step 3: execute + update( + ['completed', 'completed', 'completed', 'active', 'pending'], + `[Attempt ${attempt}] Executing Activity("${config.name}")...` + ); + await sleep(750); + if (isCancelled()) return; + + const shouldFail = config.simulateFailures && attempt <= config.failCount; + const maxAttempts = config.maxRetries + 1; + + if (shouldFail) { + const retriesLeft = maxAttempts - attempt; + update( + ['completed', 'completed', 'pending', 'failed', 'pending'], + `[Attempt ${attempt}/${maxAttempts}] Activity failed: ApplicationError`, + 'error' + ); + await sleep(350); + if (isCancelled()) return; + + if (retriesLeft <= 0) { + logEntries.push({ + time: elapsed(), + msg: `Activity exhausted all ${maxAttempts} attempt(s). No more retries.`, + type: 'error', + }); + setSim((prev) => ({ + ...prev, + running: false, + status: 'failed', + log: [...logEntries], + })); + setHistory((prev) => + [ + { + activityId: config.activityId, + status: 'Failed', + duration: `${((Date.now() - startTime) / 1000).toFixed(1)}s`, + attempts: attempt, + result: '—', + timestamp: new Date().toLocaleTimeString(), + }, + ...prev, + ].slice(0, 10) + ); + return; + } + + addLog( + `Scheduling retry in 1s… (${retriesLeft} retry attempt${retriesLeft > 1 ? 's' : ''} remaining)`, + 'warn' + ); + await sleep(1000); + if (isCancelled()) return; + + attempt++; + continue; // jump back to worker poll + } + + // ── Success ─────────────────────────────────────────────────────── + update( + ['completed', 'completed', 'completed', 'completed', 'active'], + `[Attempt ${attempt}] Activity function returned successfully!`, + 'success' + ); + await sleep(400); + if (isCancelled()) return; + + const result = `${GREETING}, ${config.name}!`; + logEntries.push({ time: elapsed(), msg: `Result: "${result}"`, type: 'success' }); + + setSim({ + running: false, + nodeStates: ['completed', 'completed', 'completed', 'completed', 'completed'], + log: [...logEntries], + status: 'completed', + result, + }); + + setHistory((prev) => + [ + { + activityId: config.activityId, + status: 'Completed', + duration: `${((Date.now() - startTime) / 1000).toFixed(1)}s`, + attempts: attempt, + result, + timestamp: new Date().toLocaleTimeString(), + }, + ...prev, + ].slice(0, 10) + ); + return; + } + })(); + }, [config]); + + const codeLanguage = language === 'dotnet' ? 'csharp' : language; + + const failureNote = + config.simulateFailures + ? config.failCount > config.maxRetries + ? `All ${config.maxRetries + 1} attempt(s) will fail because failCount exceeds maxRetries.` + : `Activity will fail ${config.failCount} time(s), then succeed on attempt ${config.failCount + 1}.` + : null; + + return ( +
+ {/* Language tabs */} +
+ {LANGUAGES.map(({ id, label }) => ( + + ))} +
+ +
+ {/* ── Left column: configure + code ── */} +
+
+

Configure Activity

+
+ updateConfig('activityId', v)} + /> + updateConfig('taskQueue', v)} + /> + updateConfig('name', v)} + /> + updateConfig('timeout', Number(v))} + /> +
+ + +
+
+
+ +
+

Failure Simulation

+
+
+ +
+ {config.simulateFailures && ( + <> + updateConfig('failCount', Number(v))} + /> + updateConfig('maxRetries', Number(v))} + /> + + )} +
+ {failureNote &&

{failureNote}

} +
+ +
+

SDK Code

+ {generateSdkCode(language, config)} +
+ +
+

CLI Command

+ {generateCliCode(language, config)} +
+
+ + {/* ── Right column: simulation ── */} +
+
+ +
+ +
+

Execution Flow

+
+ {FLOW_NODES.map((node, i) => ( + +
+
{node.label}
+ {node.sub && ( +
{node.sub}
+ )} +
+ {i < FLOW_NODES.length - 1 && ( +
+ › +
+ )} +
+ ))} +
+
+ +
+

Activity Log

+
+ {sim.log.length === 0 ? ( +
+ Click "Execute Activity" to run the simulation +
+ ) : ( + sim.log.map((entry, i) => ( +
+ [{entry.time}s] + {entry.msg} +
+ )) + )} +
+ + {sim.status === 'completed' && sim.result && ( +
+ Result: "{sim.result}" +
+ )} + {sim.status === 'failed' && ( +
+ Activity failed after exhausting all retry attempts +
+ )} +
+ + {history.length > 0 && ( +
+

+ Activity History{' '} + ({history.length}) +

+
+ + + + + + + + + + + + {history.map((h, i) => ( + + + + + + + + ))} + +
Activity IDStatusDurationAttemptsResult
{h.activityId} + + {h.status} + + {h.duration}{h.attempts}{h.result}
+
+
+ )} +
+
+
+ ); +} + +// --------------------------------------------------------------------------- +// Sub-components +// --------------------------------------------------------------------------- + +function ConfigField({ label, value, onChange, type = 'text', min, max }) { + return ( +
+ + onChange(e.target.value)} + /> +
+ ); +} diff --git a/src/components/elements/standalone-activity-demo.module.css b/src/components/elements/standalone-activity-demo.module.css new file mode 100644 index 0000000000..53f360ab0a --- /dev/null +++ b/src/components/elements/standalone-activity-demo.module.css @@ -0,0 +1,416 @@ +/* ── Root ───────────────────────────────────────────────────────────────── */ + +.demo { + font-family: var(--ifm-font-family-base); +} + +/* ── Language tabs ──────────────────────────────────────────────────────── */ + +.languageTabs { + display: flex; + gap: 6px; + margin-bottom: 16px; + border-bottom: 2px solid var(--ifm-color-emphasis-200); + padding-bottom: 10px; +} + +.langTab { + padding: 5px 18px; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 0; + background: var(--ifm-background-color); + color: var(--ifm-font-color-base); + cursor: pointer; + font-size: 0.875rem; + font-weight: 500; + transition: background 0.15s, color 0.15s, border-color 0.15s; +} + +.langTab:hover { + background: var(--ifm-color-emphasis-100); +} + +.langTabActive { + background: var(--ifm-color-primary); + color: #fff; + border-color: var(--ifm-color-primary); +} + +/* ── Two-column layout ──────────────────────────────────────────────────── */ + +.columns { + display: flex; + gap: 24px; + align-items: flex-start; +} + +.leftCol, +.rightCol { + flex: 1; + min-width: 0; +} + +@media (max-width: 900px) { + .columns { + flex-direction: column; + } +} + +/* ── Section ────────────────────────────────────────────────────────────── */ + +.section { + margin-bottom: 20px; +} + +.sectionTitle { + font-size: 0.95rem; + font-weight: 600; + margin: 0 0 8px; + color: var(--ifm-font-color-base); +} + +/* ── Config form ────────────────────────────────────────────────────────── */ + +.configGrid { + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 0; + overflow: hidden; +} + +.configRow { + display: flex; + align-items: center; + padding: 7px 12px; + border-bottom: 1px solid var(--ifm-color-emphasis-200); + background: var(--ifm-background-surface-color); +} + +.configRow:last-child { + border-bottom: none; +} + +.configLabel { + flex: 1; + font-size: 0.83rem; + color: var(--ifm-font-color-secondary); + user-select: none; +} + +.configInput, +.configSelect { + width: 150px; + padding: 4px 8px; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 0; + background: var(--ifm-background-color); + color: var(--ifm-font-color-base); + font-size: 0.83rem; +} + +.checkbox { + margin-right: 6px; + cursor: pointer; +} + +.simNote { + font-size: 0.8rem; + color: var(--ifm-font-color-secondary); + margin: 8px 0 0; + padding: 8px 12px; + background: var(--ifm-color-emphasis-100); + border-radius: 0; + line-height: 1.4; +} + +/* ── Execute button ─────────────────────────────────────────────────────── */ + +.executeBtn { + width: 100%; + padding: 12px 24px; + background: var(--ifm-color-primary); + color: #fff; + border: none; + border-radius: 0; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + transition: background 0.2s; +} + +.executeBtn:hover:not(.executeBtnDisabled) { + background: var(--ifm-color-primary-dark); +} + +.executeBtnDisabled { + opacity: 0.65; + cursor: not-allowed; +} + +/* ── Spinner ────────────────────────────────────────────────────────────── */ + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.spinner { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid rgba(255, 255, 255, 0.35); + border-top-color: #fff; + border-radius: 50%; + animation: spin 0.75s linear infinite; + flex-shrink: 0; +} + +/* ── Flow diagram ───────────────────────────────────────────────────────── */ + +.flowDiagram { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 12px; + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 0; + overflow-x: auto; + gap: 4px; +} + +.flowNode { + display: flex; + flex-direction: column; + align-items: center; + padding: 8px 10px; + border-radius: 0; + border: 2px solid var(--ifm-color-emphasis-300); + background: var(--ifm-background-color); + min-width: 68px; + text-align: center; + transition: border-color 0.3s, background 0.3s, opacity 0.3s; +} + +.flowNodeLabel { + font-size: 0.75rem; + font-weight: 600; + white-space: nowrap; + transition: color 0.3s; +} + +.flowNodeSub { + font-size: 0.63rem; + color: var(--ifm-font-color-secondary); + margin-top: 2px; + white-space: nowrap; +} + +/* Node states */ + +.flowNode_pending { + opacity: 0.38; +} + +@keyframes nodePulse { + 0%, + 100% { + box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.45); + } + 50% { + box-shadow: 0 0 0 7px rgba(59, 130, 246, 0); + } +} + +.flowNode_active { + border-color: #3b82f6; + background: rgba(59, 130, 246, 0.08); + animation: nodePulse 1.4s ease-in-out infinite; +} + +.flowNode_active .flowNodeLabel { + color: #3b82f6; +} + +.flowNode_completed { + border-color: #22c55e; + background: rgba(34, 197, 94, 0.08); +} + +.flowNode_completed .flowNodeLabel { + color: #15803d; +} + +.flowNode_failed { + border-color: #ef4444; + background: rgba(239, 68, 68, 0.08); +} + +.flowNode_failed .flowNodeLabel { + color: #dc2626; +} + +/* Arrows */ + +.flowArrow { + font-size: 1.6rem; + color: var(--ifm-color-emphasis-300); + flex-shrink: 0; + line-height: 1; + transition: color 0.3s; + user-select: none; +} + +.flowArrowLit { + color: #22c55e; +} + +/* ── Activity log ───────────────────────────────────────────────────────── */ + +.log { + height: 190px; + overflow-y: auto; + background: var(--ifm-background-surface-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 0; + padding: 10px 12px; + font-family: var(--ifm-font-family-monospace); + font-size: 0.78rem; + line-height: 1.55; +} + +.logPlaceholder { + color: var(--ifm-font-color-secondary); + font-style: italic; + padding: 4px 0; +} + +.logLine { + display: flex; + gap: 8px; + padding: 1px 0; +} + +.logTime { + color: var(--ifm-font-color-secondary); + flex-shrink: 0; +} + +.logLine_info .logMsg { + color: var(--ifm-font-color-base); +} + +.logLine_success .logMsg { + color: #15803d; + font-weight: 600; +} + +.logLine_error .logMsg { + color: #dc2626; +} + +.logLine_warn .logMsg { + color: #b45309; +} + +/* Result banners */ + +.resultSuccess { + margin-top: 8px; + padding: 9px 14px; + background: rgba(34, 197, 94, 0.1); + border: 1px solid rgba(34, 197, 94, 0.5); + border-radius: 0; + color: #15803d; + font-size: 0.88rem; +} + +.resultFailed { + margin-top: 8px; + padding: 9px 14px; + background: rgba(239, 68, 68, 0.08); + border: 1px solid rgba(239, 68, 68, 0.45); + border-radius: 0; + color: #dc2626; + font-size: 0.88rem; +} + +/* ── Activity history table ─────────────────────────────────────────────── */ + +.historyCount { + font-weight: 400; + color: var(--ifm-font-color-secondary); + font-size: 0.85rem; +} + +.historyWrapper { + overflow-x: auto; + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 0; +} + +.historyTable { + width: 100%; + border-collapse: collapse; + font-size: 0.82rem; +} + +.historyTable th { + padding: 7px 12px; + text-align: left; + background: var(--ifm-background-surface-color); + border-bottom: 1px solid var(--ifm-color-emphasis-200); + font-weight: 600; + color: var(--ifm-font-color-secondary); + font-size: 0.72rem; + text-transform: uppercase; + letter-spacing: 0.05em; + white-space: nowrap; +} + +.historyTable td { + padding: 7px 12px; + border-bottom: 1px solid var(--ifm-color-emphasis-100); + vertical-align: middle; +} + +.historyTable tr:last-child td { + border-bottom: none; +} + +.historyId { + font-family: var(--ifm-font-family-monospace); + font-size: 0.78rem; +} + +.historyResult { + font-family: var(--ifm-font-family-monospace); + font-size: 0.78rem; + color: var(--ifm-font-color-secondary); +} + +.badgeSuccess { + display: inline-block; + padding: 2px 8px; + background: rgba(34, 197, 94, 0.12); + color: #15803d; + border-radius: 0; + font-size: 0.74rem; + font-weight: 600; + white-space: nowrap; +} + +.badgeFailed { + display: inline-block; + padding: 2px 8px; + background: rgba(239, 68, 68, 0.1); + color: #dc2626; + border-radius: 0; + font-size: 0.74rem; + font-weight: 600; + white-space: nowrap; +} diff --git a/src/components/index.js b/src/components/index.js index 9c82476efc..19e980ffee 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,5 +1,6 @@ // Website components export { default as RetrySimulator } from './elements/RetrySimulator'; +export { default as StandaloneActivityDemo } from './elements/StandaloneActivityDemo'; export { default as HomePageHero } from './elements/HomePageHero'; export { SdkLogos } from './elements/SdkLogos'; export { SdkLogosAsBlocks } from './elements/SdkLogosAsBlocks';