feat: execute HTTP and query runtime contracts

This commit is contained in:
donghyeon-ka
2026-07-26 14:05:12 +09:00
parent 8aaaa033c0
commit ad55e21a3d
29 changed files with 1326 additions and 185 deletions
+23 -9
View File
@@ -65,7 +65,7 @@ export function TerminalErrorSurface({ userMessageKey, action, onAction }) {
data-message-key={userMessageKey}
>
<h2 id={messageId}>{errorMessage(userMessageKey)}</h2>
{action !== "none" && (
{action !== "none" && onAction && (
<Button onClick={onAction}>{actionLabels[action]}</Button>
)}
</section>
@@ -76,10 +76,18 @@ export function TerminalErrorSurface({ userMessageKey, action, onAction }) {
* @param {{
* state: ReturnType<typeof import("../../application/view-models/async-state.js").deriveAsyncState>,
* children?: React.ReactNode,
* onAction?: () => void
* onAction?: () => void,
* onRetry?: () => void,
* onResolveConflict?: () => void
* }} props
*/
export function AsyncSurface({ state, children, onAction }) {
export function AsyncSurface({
state,
children,
onAction,
onRetry,
onResolveConflict,
}) {
if (state.base === "initial-loading") return <LoadingSurface />;
if (state.base === "empty") return <EmptySurface />;
if (state.base === "terminal-error" && state.failure) {
@@ -87,18 +95,24 @@ export function AsyncSurface({ state, children, onAction }) {
<TerminalErrorSurface
userMessageKey={state.failure.userMessageKey}
action={state.failure.action}
onAction={onAction}
onAction={onRetry ?? onAction}
/>
);
}
return (
<section aria-busy={state.overlay.refreshing || state.overlay.mutationPending}>
{state.indicator && (
<p role="status" aria-live="polite">
{state.indicator}
</p>
)}
{state.indicator ? (
<div role="status" aria-live="polite">
<span>{state.indicator}</span>
{state.indicator === "stale-degraded" && onRetry ? (
<Button onClick={onRetry}>다시 시도</Button>
) : null}
{state.indicator === "mutation-conflict" && onResolveConflict ? (
<Button onClick={onResolveConflict}>충돌 해결</Button>
) : null}
</div>
) : null}
{children}
</section>
);