feat: complete Epic 1 production foundation

This commit is contained in:
2026-08-25 01:24:11 +03:00
parent 767428436d
commit 182bde8ac0
298 changed files with 35719 additions and 5299 deletions
+111 -10
View File
@@ -1,3 +1,25 @@
var wizardOperationLoadGeneration = 0;
var wizardLifecycleActionBusy = false;
var wizardTestConfirmationToken = null;
function operationErrorCode(error) {
return error && error.payload && error.payload.error
? error.payload.error.code
: '';
}
function showStaleOperationError(error) {
if (operationErrorCode(error) !== 'operation_stale_version'
&& operationErrorCode(error) !== 'operation_precondition_required') {
return false;
}
showWizardLiveStatus(tKey('wizard.stale.title'), tKey('wizard.stale.body'), true);
if (window.CrankUi) {
window.CrankUi.error(tKey('wizard.stale.body'), tKey('wizard.stale.title'));
}
return true;
}
function buildToolDescription() {
var snapshot = wizardCurrentVersion && wizardCurrentVersion.snapshot
? wizardCurrentVersion.snapshot
@@ -98,6 +120,7 @@ async function saveOperation(stayOnPage) {
try {
await persistCurrentDraft(stayOnPage);
} catch (error) {
if (showStaleOperationError(error)) return;
if (window.CrankUi) {
window.CrankUi.error(error.message || tKey('wizard.error.save'), tKey('wizard.error.save_title'));
}
@@ -106,14 +129,23 @@ async function saveOperation(stayOnPage) {
async function loadOperationForEdit() {
if (!wizardWorkspaceId || !wizardEditId) return;
var detail = await window.CrankApi.getOperation(wizardWorkspaceId, wizardEditId);
var generation = ++wizardOperationLoadGeneration;
var requestedWorkspaceId = wizardWorkspaceId;
var requestedOperationId = wizardEditId;
var detail = await window.CrankApi.getOperation(requestedWorkspaceId, requestedOperationId);
if (generation !== wizardOperationLoadGeneration
|| requestedWorkspaceId !== wizardWorkspaceId
|| requestedOperationId !== wizardEditId) return;
wizardProtocol = detail.protocol || 'rest';
await loadWizardPanels([3]);
var draftVersion = await window.CrankApi.getOperationVersion(
wizardWorkspaceId,
wizardEditId,
requestedWorkspaceId,
requestedOperationId,
detail.draft_version_ref.version
);
if (generation !== wizardOperationLoadGeneration
|| requestedWorkspaceId !== wizardWorkspaceId
|| requestedOperationId !== wizardEditId) return;
wizardCurrentOperation = detail;
wizardCurrentVersion = draftVersion;
prefillWizardFromEdit(detail, draftVersion);
@@ -150,6 +182,8 @@ function bindWizardLiveActions() {
bindLiveAction('wizard-run-test', tKey('wizard.busy.test'), runWizardTest);
bindLiveAction('wizard-run-quality', tKey('wizard.busy.quality'), analyzeWizardQuality);
bindClick('wizard-copy-test-response', copyTestResponseToOutputSample);
bindClick('wizard-copy-request-id', function() { copyCorrelationId('wizard-test-request-id'); });
bindClick('wizard-copy-trace-id', function() { copyCorrelationId('wizard-test-trace-id'); });
bindClick('wizard-copy-agent-preview', copyAgentFacingPreview);
bindLiveAction('wizard-export-yaml', tKey('wizard.busy.export_yaml'), exportWizardYaml);
bindLiveAction('wizard-import-yaml', tKey('wizard.busy.import_yaml'), importWizardYaml);
@@ -180,6 +214,7 @@ function bindClick(id, handler) {
function bindLiveAction(id, busyLabel, handler) {
var element = document.getElementById(id);
if (!element) return;
element.dataset.lifecycleAction = 'true';
element.addEventListener('click', function(event) {
event.preventDefault();
runWizardLiveAction(element, busyLabel, handler);
@@ -233,11 +268,17 @@ function bindApprovalPolicyControls() {
}
async function runWizardLiveAction(button, busyLabel, handler) {
if (!button || button.dataset.busy === 'true') {
if (!button || button.dataset.busy === 'true' || wizardLifecycleActionBusy) {
return;
}
var originalLabel = button.textContent;
wizardLifecycleActionBusy = true;
var lifecycleButtons = Array.from(document.querySelectorAll('[data-lifecycle-action="true"]'));
lifecycleButtons.forEach(function(element) {
element.disabled = true;
element.setAttribute('aria-busy', 'true');
});
button.dataset.busy = 'true';
button.disabled = true;
button.classList.add('is-busy');
@@ -246,6 +287,7 @@ async function runWizardLiveAction(button, busyLabel, handler) {
try {
await handler();
} catch (error) {
if (showStaleOperationError(error)) return;
showWizardLiveStatus(
tKey('wizard.live.failed_title'),
error && error.message ? error.message : tKey('wizard.live.failed_body'),
@@ -258,8 +300,12 @@ async function runWizardLiveAction(button, busyLabel, handler) {
);
}
} finally {
wizardLifecycleActionBusy = false;
lifecycleButtons.forEach(function(element) {
element.disabled = false;
element.removeAttribute('aria-busy');
});
button.dataset.busy = 'false';
button.disabled = false;
button.classList.remove('is-busy');
button.textContent = originalLabel;
}
@@ -515,6 +561,15 @@ function copyAgentFacingPreview() {
}
}
function copyCorrelationId(elementId) {
var element = document.getElementById(elementId);
var value = element ? element.textContent : '';
if (!value || !navigator.clipboard || !navigator.clipboard.writeText) return;
navigator.clipboard.writeText(value).then(function() {
showWizardLiveStatus(tKey('wizard.test.id_copied'), tKey('wizard.test.id_copied_body'));
});
}
function severityLabel(severity) {
if (severity === 'error') return tKey('wizard.quality.severity_error');
if (severity === 'warning') return tKey('wizard.quality.severity_warning');
@@ -637,7 +692,7 @@ function renderImportQualityFindings(versionDocument) {
var empty = document.getElementById('wizard-quality-empty');
if (empty) {
empty.textContent = 'Рекомендации из OpenAPI import. Запустите проверку качества, чтобы пересчитать их по текущему черновику.';
empty.textContent = tKey('wizard.quality.import_findings_hint');
empty.hidden = false;
}
}
@@ -679,7 +734,12 @@ async function importWizardYaml() {
showWizardLiveStatus(tKey('wizard.yaml.none'), tKey('wizard.yaml.none_body'), true);
return;
}
var imported = await window.CrankApi.importOperation(wizardWorkspaceId, yamlDocument, 'upsert');
var imported = await window.CrankApi.importOperation(
wizardWorkspaceId,
yamlDocument,
'upsert',
wizardEditId
);
if (Array.isArray(imported.warnings) && imported.warnings.length > 0) {
sessionStorage.setItem('crank_import_guidance', JSON.stringify(imported.warnings));
}
@@ -688,6 +748,7 @@ async function importWizardYaml() {
}
async function publishWizardOperation() {
if (!confirm(tKey('wizard.publish.confirm'))) return;
var report = await analyzeWizardQuality();
if (report.blocking) {
throw new Error(tKey('wizard.quality.blocking_error'));
@@ -703,6 +764,7 @@ async function publishWizardOperation() {
tKey('wizard.publish.done'),
tfKey('wizard.publish.done_body', { version: published.published_version })
);
if (window.CrankOnboarding) window.CrankOnboarding.signalRefresh();
}
function handleImportYamlFileSelection(event) {
@@ -724,6 +786,17 @@ function describeWizardTestResult(result) {
};
}
function localizeWizardTestErrors(errors) {
return (Array.isArray(errors) ? errors : []).map(function(error) {
if (!error || !error.code) return error;
var key = 'execution.error.' + error.code;
var localized = tKey(key);
return Object.assign({}, error, {
message: localized === key ? error.message : localized,
});
});
}
async function uploadInputSampleFromWizard() {
await persistCurrentDraft(true);
await window.CrankApi.uploadInputSample(
@@ -759,21 +832,49 @@ async function generateDraftFromWizard() {
}
async function runWizardTest() {
var generation = wizardOperationLoadGeneration;
var requestedWorkspaceId = wizardWorkspaceId;
var requestedOperationId = wizardEditId;
await persistCurrentDraft(true);
var result = await window.CrankApi.runOperationTest(wizardWorkspaceId, wizardEditId, {
if (generation !== wizardOperationLoadGeneration
|| requestedWorkspaceId !== wizardWorkspaceId
|| requestedOperationId !== wizardEditId) return;
var result = await window.CrankApi.runOperationTest(requestedWorkspaceId, requestedOperationId, {
version: currentDraftVersion(),
input: parseStructuredText(textValue('wizard-test-input')),
confirmation_token: wizardTestConfirmationToken,
locale: localStorage.getItem('crank_lang') || 'en',
});
if (generation !== wizardOperationLoadGeneration
|| requestedWorkspaceId !== wizardWorkspaceId
|| requestedOperationId !== wizardEditId) return;
var confirmationError = Array.isArray(result.errors)
? result.errors.find(function(error) { return error && error.code === 'confirmation_required'; })
: null;
wizardTestConfirmationToken = confirmationError && confirmationError.context
? confirmationError.context.confirmation_token || null
: null;
wizardTestResponsePreview = result.response_preview;
setTextareaValue('wizard-test-request-preview', result.request_preview);
setTextareaValue('wizard-test-response-preview', result.response_preview);
setTextareaValue('wizard-test-errors', result.errors && result.errors.length ? result.errors : []);
setTextareaValue('wizard-test-errors', localizeWizardTestErrors(result.errors));
var status = describeWizardTestResult(result);
var requestId = document.getElementById('wizard-test-request-id');
var traceId = document.getElementById('wizard-test-trace-id');
var correlationRoot = document.getElementById('wizard-test-correlation');
if (requestId) requestId.textContent = result.request_id || '';
if (traceId) traceId.textContent = result.trace_id || '';
if (correlationRoot) correlationRoot.hidden = !(result.request_id && result.trace_id);
var correlation = tfKey('wizard.test.correlation', {
requestId: result.request_id || '—',
traceId: result.trace_id || '—'
});
showWizardLiveStatus(
status.title,
status.body,
status.body + '\n' + correlation,
status.isError
);
if (window.CrankOnboarding) window.CrankOnboarding.signalRefresh();
}
function copyTestResponseToOutputSample() {