apiV4Js constant

String const apiV4Js

API v4 console JavaScript code

Implementation

static const String apiV4Js = r'''(function () {
  const REGISTRAR_ENVS = {
      dev: 'https://my.atsign.wtf',
      prod: 'https://my.atsign.com'
  };
  const STATUS_ENVS = {
      dev: 'https://directory.atsign.wtf',
      prod: 'https://wavi.ng'
  };
  const REQUEST_TIMEOUT_MS = 15000;

  const REGISTER_ATSIGNS_ENDPOINT = {
      id: 'register-atsigns',
      title: 'Register Atsigns',
      method: 'POST',
      baseType: 'registrar',
      path: '/api/app/v4/register-atsign/',
      authRequired: true,
      type: 'batch-register',
      params: [
          {
              name: 'atSigns',
              label: 'Atsigns',
              required: true,
              type: 'textarea',
              placeholder: 'meow01\nmeow02\nmeow03',
              help: 'One Atsign per line. Do not include the prefix/postfix. The prefix/postfix is already implied by the Authorization API Key. So for example, entering "test01", "test02", and "test03" as your Atsigns will provision "test01_np", "test02_np", and "test03_np". Do not include the @ symbol.'
          },
          { name: 'startAtServer', required: false, type: 'select', options: ['true', 'false'], defaultValue: 'true', help: 'Optional' }
      ]
  };

  const PRIMARY_ENDPOINTS = [
      {
          id: 'lookup-atsign-v4',
          title: 'Lookup an Atsign',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v4/register-atsign/',
          authRequired: true,
          params: [
              {
                  name: 'atSign',
                  label: 'Atsign',
                  required: false,
                  type: 'text',
                  placeholder: 'meow01',
                  help: 'Optional. Do not include the prefix/postfix. The prefix/postfix is already implied by the Authorization API Key. So for example, entering "test01" as your Atsign may provision "test01_np". Do not include the @ symbol.'
              },
              { name: 'operation', required: true, type: 'select', options: ['lookup'], defaultValue: 'lookup', disabled: true, help: 'Required' },
              { name: 'startAtServer', required: false, type: 'select', options: ['true', 'false'], defaultValue: 'true', help: 'Optional' }
          ]
      },
      {
          id: 'reset-atsigns',
          title: 'Reset Atsigns',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v4/reset-atsign/',
          authRequired: true,
          type: 'batch-reset',
          params: [
              {
                  name: 'atSigns',
                  label: 'Atsigns',
                  required: true,
                  type: 'textarea',
                  placeholder: 'meow01_jttest\nmeow02_jttest',
                  help: 'One Atsign per line. Include the full prefix/postfix. For example, if your postfix is _jttest, enter meow01_jttest. This intentionally differs from the register endpoint.'
              }
          ]
      },
      {
          id: 'delete-atsigns',
          title: 'Delete Atsigns',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v4/manage-atsigns',
          authRequired: true,
          type: 'delete-atsigns',
          params: [
              {
                  name: 'atSigns',
                  label: 'Atsigns',
                  required: true,
                  type: 'textarea',
                  placeholder: 'meow01_jttest\nmeow02_jttest',
                  help: 'Required. One Atsign per line. Include the full prefix/postfix, the same way the reset endpoint does. The Authorization API Key can only delete Atsigns it created, so anything else comes back as skipped. Do not include the @ symbol. A name that has already been deleted once is refused on later deletes, so avoid reusing one.'
              }
          ]
      }
  ];

  const OTHER_ENDPOINTS = [
      {
          id: 'get-free-atsign-get',
          title: 'Get Free Atsign',
          method: 'GET',
          baseType: 'registrar',
          path: '/api/app/v4/get-free-atsign/',
          authRequired: true,
          params: []
      },
      {
          id: 'get-free-atsign-post',
          title: 'Get Free Atsign by Category',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v4/get-free-atsign/',
          authRequired: true,
          params: [
              { name: 'category', required: false, type: 'csv-array', placeholder: 'animals,movies', help: 'Comma-separated list' }
          ]
      },
      {
          id: 'register-person',
          title: 'Register Person',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v4/register-person/',
          authRequired: true,
          params: [
              { name: 'atsign', label: 'Atsign', required: true, type: 'text', placeholder: 'wisefrog', help: 'Generated free Atsign' },
              { name: 'email', required: true, type: 'text', placeholder: 'user@example.com', help: 'Required' },
              { name: 'oldEmail', required: false, type: 'text', placeholder: 'old@example.com', help: 'Optional' }
          ]
      },
      {
          id: 'validate-person',
          title: 'Validate Person',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v4/validate-person/',
          authRequired: true,
          params: [
              { name: 'atsign', label: 'Atsign', required: true, type: 'text', placeholder: 'wisefrog', help: 'Required' },
              { name: 'email', required: true, type: 'text', placeholder: 'user@example.com', help: 'Required' },
              { name: 'otp', required: true, type: 'text', placeholder: 'AB12', help: '4-character code' },
              { name: 'confirmation', required: false, type: 'boolean', placeholder: 'false', help: 'Optional, defaults to false' }
          ]
      },
      {
          id: 'authenticate-person-atsign',
          title: 'Authenticate Person with Atsign',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v4/authenticate/atsign',
          authRequired: true,
          params: [
              { name: 'atsign', label: 'Atsign', required: true, type: 'text', placeholder: '@alice', help: 'Registered Atsign' }
          ]
      },
      {
          id: 'validate-person-atsign-otp',
          title: 'Validate Person with Atsign and OTP',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v4/authenticate/atsign/activate',
          authRequired: true,
          params: [
              { name: 'atsign', label: 'Atsign', required: true, type: 'text', placeholder: '@alice', help: 'Registered Atsign' },
              { name: 'otp', required: true, type: 'text', placeholder: 'AB12', help: '4-character code' }
          ]
      },
      {
          id: 'get-atsign-v3',
          title: 'Get Atsign and ActivationKey (v3)',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v3/get-atsign/',
          authRequired: true,
          params: [
              { name: 'atSign', label: 'Atsign', required: false, type: 'text', placeholder: 'embergarden', help: 'Optional' },
              { name: 'ActivationKey', required: false, type: 'text', placeholder: '4V3GbA4u', help: 'Optional' }
          ]
      },
      {
          id: 'get-atsign-v4',
          title: 'Get Atsign and ActivationKey (v4)',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v4/get-atsign/',
          authRequired: true,
          params: [
              { name: 'atSign', label: 'Atsign', required: false, type: 'text', placeholder: 'emberly', help: 'Optional' },
              { name: 'ActivationKey', required: false, type: 'text', placeholder: '4V3GbA4u', help: 'Optional' }
          ]
      },
      {
          id: 'activate-atsign',
          title: 'Activate an Atsign',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v4/activate-atsign/',
          authRequired: false,
          params: [
              { name: 'atSign', label: 'Atsign', required: true, type: 'text', placeholder: '@alice', help: 'Required' },
              { name: 'activationKey', required: true, type: 'text', placeholder: '4V3GbA4u', help: 'Required' }
          ]
      },
      {
          id: 'register-atsign-v3',
          title: 'Register an Atsign (v3)',
          method: 'POST',
          baseType: 'registrar',
          path: '/api/app/v3/register-atsign/',
          authRequired: true,
          params: [
              { name: 'atSign', label: 'Atsign', required: false, type: 'text', placeholder: 'mydog_01', help: 'Optional' },
              { name: 'operation', required: true, type: 'select', options: ['lookup', 'register'], help: 'Required' }
          ]
      }
  ];

  const ALL_ENDPOINTS = [...PRIMARY_ENDPOINTS, ...OTHER_ENDPOINTS];

  function envOptions(baseType) {
      const source = baseType === 'status' ? STATUS_ENVS : REGISTRAR_ENVS;
      return Object.keys(source)
          .map((key) => `<option value="${key}" ${key === 'prod' ? 'selected' : ''}>${key} - ${source[key]}</option>`)
          .join('');
  }

  function resolveBase(baseType, env) {
      return baseType === 'status' ? STATUS_ENVS[env] : REGISTRAR_ENVS[env];
  }

  function paramInput(param) {
      if (param.type === 'select') {
          return `
              <select data-param-name="${param.name}" ${param.disabled ? 'disabled' : ''}>
                  <option value=""></option>
                  ${param.options.map((opt) => `<option value="${opt}" ${param.defaultValue === opt ? 'selected' : ''}>${opt}</option>`).join('')}
              </select>
          `;
      }

      if (param.type === 'boolean') {
          return `
              <select data-param-name="${param.name}">
                  <option value=""></option>
                  <option value="true">true</option>
                  <option value="false">false</option>
              </select>
          `;
      }

      if (param.type === 'textarea') {
          return `<textarea data-param-name="${param.name}" rows="8" placeholder="${param.placeholder || ''}"></textarea>`;
      }

      return `<input type="text" data-param-name="${param.name}" placeholder="${param.placeholder || ''}" />`;
  }

  function requiredSummary(endpoint) {
      const required = endpoint.params.filter((p) => p.required).map((p) => p.name);
      return required.length ? `Required parameters: ${required.join(', ')}` : 'Required parameters: none';
  }

  function normalizeParamHelpText(helpText) {
      if (!helpText) {
          return '';
      }

      return String(helpText).replace(/^(required|optional)\.?\s*/i, '').trim();
  }

  function endpointMarkup(endpoint) {
      return `
          <div class="api-v4-row" data-endpoint-id="${endpoint.id}">
              <button type="button" class="api-v4-row-head" data-role="toggle" aria-expanded="false">
                  <div>
                      <div class="api-v4-row-title">${endpoint.title}</div>
                      <div class="api-v4-param-summary">${requiredSummary(endpoint)}</div>
                  </div>
                  <div class="api-v4-row-route">
                      <span class="api-v4-method api-v4-method-${endpoint.method.toLowerCase()}">${endpoint.method}</span>
                      <code>${endpoint.path}</code>
                      <span class="api-v4-toggle-indicator" aria-hidden="true"></span>
                  </div>
              </button>

              <div class="api-v4-row-body" data-role="body" hidden>
                  <div class="api-v4-param-grid">
                      <label class="api-v4-field">
                          <span>Environment</span>
                          <select data-role="env">${envOptions(endpoint.baseType)}</select>
                      </label>
                      <label class="api-v4-field">
                          <span>Authorization API Key ${endpoint.authRequired ? '<small>(required)</small>' : '<small>(not required for this call)</small>'}</span>
                          <input type="password" data-role="api-key" placeholder="Your registrar API key" ${endpoint.authRequired ? '' : ''} />
                      </label>
                      ${endpoint.params.map((param) => `
                          <label class="api-v4-field">
                              <span>${param.label || param.name} <small>${param.required ? 'required' : 'optional'}${normalizeParamHelpText(param.help) ? ` ยท ${normalizeParamHelpText(param.help)}` : ''}</small></span>
                              ${paramInput(param)}
                          </label>
                      `).join('')}
                  </div>

                  <div class="api-v4-inline-warning" data-role="warning" hidden></div>

                  <div class="api-v4-actions">
                      <button type="button" data-role="send">Send</button>
                      <button type="button" class="api-v4-secondary" data-role="clear">Clear Output</button>
                  </div>

                  <div class="api-v4-result" data-role="result" hidden></div>
                  <div class="api-v4-batch-results" data-role="batch-results" hidden></div>
                  <div class="api-v4-status" data-role="status">Ready</div>
                  <pre class="api-v4-output" data-role="output"></pre>
              </div>
          </div>
      `;
  }

  function otherSectionMarkup() {
      if (OTHER_ENDPOINTS.length === 0) {
          return '';
      }

      return `
          <details class="api-v4-section">
              <summary>Other</summary>
              <div class="api-v4-section-body">
                  ${OTHER_ENDPOINTS.map(endpointMarkup).join('')}
              </div>
          </details>
      `;
  }

  function parseJsonObject(value, fieldName) {
      const trimmed = value.trim();
      if (!trimmed) return {};
      try {
          const parsed = JSON.parse(trimmed);
          if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
              return parsed;
          }
          throw new Error(`${fieldName} must be a JSON object`);
      } catch (err) {
          throw new Error(`Invalid ${fieldName}: ${err.message}`);
      }
  }

  function collectPayload(row, endpoint) {
      const payload = {};
      const warning = row.querySelector('[data-role="warning"]');
      if (warning) {
          warning.hidden = true;
          warning.textContent = '';
      }

      for (const param of endpoint.params) {
          const el = row.querySelector(`[data-param-name="${param.name}"]`);
          let raw = (el ? el.value : '').trim();

          if (!raw) {
              if (param.required) {
                  throw new Error(`Missing required parameter: ${param.name}`);
              }
              continue;
          }

          if ((endpoint.id === 'reset-atsigns' || endpoint.id === 'delete-atsigns') && param.name === 'atSigns') {
              const normalizedValues = raw
                  .split('\n')
                  .map((value) => normalizeAtSignForRequest(value.trim()))
                  .filter(Boolean);
              payload[param.name] = normalizedValues;
              if (el) {
                  el.value = normalizedValues.join('\n');
              }
              if (warning && normalizedValues.some((value, index) => value !== raw.split('\n').map((entry) => entry.trim()).filter(Boolean)[index])) {
                  warning.textContent = 'Warning: leading @ was removed from one or more Atsigns before sending.';
                  warning.hidden = false;
              }
              continue;
          }

          if (param.type === 'csv-array') {
              payload[param.name] = raw.split(',').map((v) => v.trim()).filter(Boolean);
          } else if (param.type === 'boolean') {
              payload[param.name] = raw === 'true';
          } else {
              payload[param.name] = raw;
          }
      }

      return payload;
  }

  function buildUrl(endpoint, env, payload) {
      let path = endpoint.path;
      if (path.includes('{atSign}')) {
          path = path.replace('{atSign}', encodeURIComponent(payload.atSign || payload.atsign || ''));
          delete payload.atSign;
      }
      return `${resolveBase(endpoint.baseType, env)}${path}`;
  }

  function renderPrimarySummary(row, endpoint, parsedBody) {
      const result = row.querySelector('[data-role="result"]');
      if (!result) return;

      if (
          !PRIMARY_ENDPOINTS.some((item) => item.id === endpoint.id) ||
          endpoint.id === 'reset-atsigns' ||
          !parsedBody ||
          typeof parsedBody !== 'object'
      ) {
          result.hidden = true;
          result.innerHTML = '';
          return;
      }

      const apiStatus = parsedBody.status === 'success' ? 'success' : 'error';
      const badgeLabel = apiStatus === 'success' ? 'Success' : 'Error';
      let summaryText = parsedBody.message || 'Request completed.';

      if (endpoint.id === 'lookup-atsign-v4' && parsedBody.message) {
          summaryText = parsedBody.message;
      }

      const cramkeyBlock = parsedBody.cramkey
          ? `<div class="api-v4-cramkey-block"><div class="api-v4-cramkey-label">cramkey</div><code>${escapeHtml(parsedBody.cramkey)}</code></div>`
          : '';

      result.innerHTML = `
          <div class="api-v4-result-card api-v4-result-${apiStatus}">
              <div class="api-v4-result-head">
                  <span class="api-v4-result-badge api-v4-result-badge-${apiStatus}">${badgeLabel}</span>
                  <span class="api-v4-result-text">${escapeHtml(summaryText)}</span>
              </div>
              ${cramkeyBlock}
          </div>
      `;
      result.hidden = false;
  }

  function escapeHtml(value) {
      return String(value)
          .replaceAll('&', '&amp;')
          .replaceAll('<', '&lt;')
          .replaceAll('>', '&gt;')
          .replaceAll('"', '&quot;')
          .replaceAll("'", '&#39;');
  }

  function extractAtSignFromCramkey(cramkey) {
      if (!cramkey || typeof cramkey !== 'string') {
          return '';
      }

      const separatorIndex = cramkey.indexOf(':');
      return separatorIndex >= 0 ? cramkey.substring(0, separatorIndex) : cramkey;
  }

  function extractCramKeyValue(cramkey) {
      if (!cramkey || typeof cramkey !== 'string') {
          return '';
      }

      const separatorIndex = cramkey.indexOf(':');
      return separatorIndex >= 0 ? cramkey.substring(separatorIndex + 1) : cramkey;
  }

  function parseRequestBody(data) {
      const request = data && data.request ? data.request : {};
      const body = request.body;
      if (!body || typeof body !== 'string') {
          return {};
      }

      try {
          const parsed = JSON.parse(body);
          return parsed && typeof parsed === 'object' ? parsed : {};
      } catch (_) {
          return {};
      }
  }

  function transactionTypeLabel(type) {
      if (type === 'register-atsign') {
          return 'Register an Atsign';
      }
      if (type === 'reset-atsign') {
          return 'Reset an Atsign';
      }
      if (type === 'delete-atsign') {
          return 'Delete Atsigns';
      }
      return type || 'Transaction';
  }

  function summarizeTransaction(item) {
      const data = item.data || {};
      const request = data.request || {};
      const response = data.response || {};
      const responseBody = response.body || {};
      const requestBody = parseRequestBody(data);
      const cramkey = responseBody.cramkey || '';
      const cramAtSign = extractAtSignFromCramkey(cramkey);
      const requestedAtSign = requestBody.atSign ? `@${String(requestBody.atSign).replace(/^@/, '')}` : '';
      const requestedAtSigns = Array.isArray(requestBody.atSigns)
          ? requestBody.atSigns.map((value) => `@${String(value).replace(/^@/, '')}`).join(', ')
          : '';
      const label = cramAtSign || requestedAtSign || requestedAtSigns || item.filename || 'Transaction';
      const apiStatus = responseBody.status || (response.ok ? 'success' : 'error');
      const message = responseBody.message || '';
      const createdAt = data.createdAtUtc || '';

      return {
          data,
          request,
          response,
          responseBody,
          cramkey,
          label,
          apiStatus,
          message,
          createdAt,
          typeLabel: transactionTypeLabel(data.type)
      };
  }

  const HISTORY_PAGE_SIZE = 25;
  let historyEvents = [];
  let historyVisibleCount = HISTORY_PAGE_SIZE;

  function formatHistoryTimestamp(value) {
      if (!value) {
          return '';
      }

      try {
          return new Date(value).toLocaleString();
      } catch (_) {
          return value;
      }
  }

  function historyTypeLabel(type) {
      if (type === 'register-atsign' || type === 'reset-atsign' || type === 'delete-atsign') {
          return transactionTypeLabel(type);
      }

      return String(type || 'event').replaceAll('_', ' ');
  }

  function historySourceLabel(source) {
      return source === 'super-api-key' ? 'Super API Key' : 'Local';
  }

  function formatHistoryData(data) {
      if (!data || typeof data !== 'object') {
          return '';
      }

      const entries = Object.entries(data);
      if (entries.length === 0) {
          return '';
      }

      return entries
          .map(([key, value]) => `${key}: ${typeof value === 'string' ? value : JSON.stringify(value)}`)
          .join(' | ');
  }

  function getHistoryFilters() {
      const searchInput = document.getElementById('history-search-input');
      const typeFilter = document.getElementById('history-type-filter');
      const sourceFilter = document.getElementById('history-source-filter');

      return {
          search: (searchInput ? searchInput.value : '').trim().toLowerCase(),
          type: typeFilter ? typeFilter.value : 'all',
          source: sourceFilter ? sourceFilter.value : 'all'
      };
  }

  function historySearchText(event) {
      return [
          event.type,
          historyTypeLabel(event.type),
          event.atSign,
          event.summary,
          event.status,
          historySourceLabel(event.source)
      ].join(' ').toLowerCase();
  }

  function getFilteredHistoryEvents() {
      const filters = getHistoryFilters();

      return historyEvents.filter((event) => {
          if (filters.source !== 'all' && event.source !== filters.source) {
              return false;
          }

          if (filters.type !== 'all' && event.type !== filters.type) {
              return false;
          }

          if (filters.search && !historySearchText(event).includes(filters.search)) {
              return false;
          }

          return true;
      });
  }

  function populateHistoryTypeFilter() {
      const select = document.getElementById('history-type-filter');
      if (!select) return;

      const previous = select.value;
      const types = Array.from(new Set(historyEvents.map((event) => event.type).filter(Boolean)));
      types.sort((a, b) => historyTypeLabel(a).localeCompare(historyTypeLabel(b)));

      select.innerHTML = ['<option value="all">All operations</option>']
          .concat(types.map((type) => `<option value="${escapeHtml(type)}">${escapeHtml(historyTypeLabel(type))}</option>`))
          .join('');
      select.value = types.includes(previous) ? previous : 'all';
  }

  function bindTransactionCramKeyCopy(container) {
      container.querySelectorAll('[data-copy-transaction-cramkey]').forEach((button) => {
          button.addEventListener('click', async function (event) {
              event.preventDefault();
              event.stopPropagation();

              const value = button.getAttribute('data-copy-transaction-cramkey') || '';
              await navigator.clipboard.writeText(value);
              const originalText = button.textContent;
              button.textContent = 'Copied';
              setTimeout(() => {
                  button.textContent = originalText;
              }, 1500);
          });
      });
  }

  function renderHistoryTransactionBody(item) {
      const summary = summarizeTransaction(item);

      return `
          <div class="api-v4-transaction-meta"><strong>URL:</strong> ${escapeHtml(summary.request.url || '')}</div>
          <div class="api-v4-transaction-meta"><strong>Path:</strong> ${escapeHtml(item.path || '')}</div>
          ${summary.cramkey ? `
              <div class="api-v4-transaction-cramkey">
                  <code>${escapeHtml(summary.cramkey)}</code>
                  <button type="button" class="api-v4-secondary" data-copy-transaction-cramkey="${escapeHtml(extractCramKeyValue(summary.cramkey))}">Copy CRAM Key</button>
              </div>
          ` : ''}
          <details class="api-v4-transaction-raw">
              <summary>Raw JSON</summary>
              <pre class="api-v4-transaction-json">${escapeHtml(JSON.stringify(summary.data, null, 2))}</pre>
          </details>
      `;
  }

  function renderHistoryEventCard(event) {
      const transaction = event.origin === 'transaction' && event.data ? event.data.transaction : null;
      const title = event.atSign || historyTypeLabel(event.type);
      const subtitle = event.summary || historyTypeLabel(event.type);
      const badgeClass = event.status === 'success' ? 'success' : 'error';
      const sourceClass = event.source === 'super-api-key' ? 'super' : 'local';
      const details = transaction ? '' : formatHistoryData(event.data);

      return `
          <details class="api-v4-transaction-card">
              <summary class="api-v4-transaction-summary">
                  <div class="api-v4-transaction-title-wrap">
                      <div class="api-v4-transaction-title">${escapeHtml(title)}</div>
                      <div class="api-v4-transaction-subtitle">${escapeHtml(subtitle)}</div>
                  </div>
                  <div class="api-v4-transaction-summary-right">
                      <span class="history-source-chip history-source-chip-${sourceClass}">${escapeHtml(historySourceLabel(event.source))}</span>
                      <span class="api-v4-result-badge api-v4-result-badge-${badgeClass}">${escapeHtml(event.status || '')}</span>
                      <span class="api-v4-transaction-created">${escapeHtml(formatHistoryTimestamp(event.createdAtUtc))}</span>
                  </div>
              </summary>
              <div class="api-v4-transaction-body">
                  <div class="api-v4-transaction-meta"><strong>Operation:</strong> ${escapeHtml(historyTypeLabel(event.type))}</div>
                  ${event.atSign ? `<div class="api-v4-transaction-meta"><strong>Atsign:</strong> ${escapeHtml(event.atSign)}</div>` : ''}
                  ${transaction ? renderHistoryTransactionBody(transaction) : ''}
                  ${details ? `<div class="api-v4-transaction-meta">${escapeHtml(details)}</div>` : ''}
              </div>
          </details>
      `;
  }

  function renderHistoryEvents() {
      const list = document.getElementById('history-events-list');
      const matchCount = document.getElementById('history-match-count');
      const loadMore = document.getElementById('history-events-load-more-btn');
      if (!list) return;

      const filtered = getFilteredHistoryEvents();

      if (matchCount) {
          matchCount.textContent = historyEvents.length === 0
              ? ''
              : `${filtered.length} of ${historyEvents.length}`;
      }

      if (filtered.length === 0) {
          const message = historyEvents.length === 0
              ? 'No history yet.'
              : 'No entries match the current filters.';
          list.innerHTML = `<div class="api-v4-transaction-card"><div class="api-v4-transaction-meta">${message}</div></div>`;
          if (loadMore) loadMore.hidden = true;
          return;
      }

      list.innerHTML = filtered.slice(0, historyVisibleCount).map(renderHistoryEventCard).join('');

      if (loadMore) {
          loadMore.hidden = historyVisibleCount >= filtered.length;
      }

      bindTransactionCramKeyCopy(list);
  }

  function filterHistory() {
      historyVisibleCount = HISTORY_PAGE_SIZE;
      renderHistoryEvents();
  }

  function loadMoreHistoryEvents() {
      historyVisibleCount += HISTORY_PAGE_SIZE;
      renderHistoryEvents();
  }

  async function loadHistory() {
      const status = document.getElementById('history-status');
      const list = document.getElementById('history-events-list');
      if (!status || !list) return;

      status.textContent = 'Loading history...';

      try {
          const response = await fetch('/api/history');
          const data = await response.json();
          if (!response.ok || data.error) {
              throw new Error(data.error || 'Failed to load history');
          }

          historyEvents = Array.isArray(data.events) ? data.events : [];
          historyVisibleCount = HISTORY_PAGE_SIZE;
          populateHistoryTypeFilter();
          renderHistoryEvents();
          status.textContent = `Loaded ${historyEvents.length} history ${historyEvents.length === 1 ? 'entry' : 'entries'}`;
      } catch (err) {
          historyEvents = [];
          status.textContent = `Load failed: ${err.message}`;
          list.innerHTML = '<div class="api-v4-transaction-card"><div class="api-v4-transaction-meta">Failed to load history.</div></div>';
      }
  }

  async function openTransactionsFolder() {
      const status = document.getElementById('history-status') || document.getElementById('api-v4-save-status');
      if (status) status.textContent = 'Opening transactions folder...';

      try {
          const response = await fetch('/api/transactions/open-folder', {
              method: 'POST'
          });
          const data = await response.json();
          if (!response.ok || data.error) {
              throw new Error(data.error || 'Failed to open transactions folder');
          }
          if (status) status.textContent = `Opened transactions folder: ${data.path}`;
      } catch (err) {
          if (status) status.textContent = `Open failed: ${err.message}`;
      }
  }

  async function persistSuccessfulTransaction(endpoint, payload) {
      const allowed = {
          'register-atsign-v3': 'register-atsign',
          'register-atsigns': 'register-atsign',
          'reset-atsigns': 'reset-atsign',
          'delete-atsigns': 'delete-atsign'
      };

      const type = allowed[endpoint.id];
      if (!type) {
          return null;
      }

      const responseBody = payload && payload.response ? payload.response.body : null;
      if (!responseBody || responseBody.status !== 'success') {
          return null;
      }

      const response = await fetch('/api/transactions/save', {
          method: 'POST',
          headers: {
              'Content-Type': 'application/json'
          },
          body: JSON.stringify({
              type,
              transaction: payload
          })
      });

          const data = await response.json();
          if (!response.ok || data.error) {
              throw new Error(data.error || 'Failed to save register transaction');
          }

      if (data.cramKeyPath) {
          const saveStatus = document.getElementById('api-v4-save-status');
          if (saveStatus) {
              saveStatus.textContent = `Saved CRAM key file: ${data.cramKeyPath}`;
          }
      }

      return data.path || null;
  }

  function normalizeAtSignForRequest(value) {
      return value.startsWith('@') ? value.substring(1) : value;
  }

  function buildRequestDetails(endpoint, env, apiKey, payload) {
      const url = buildUrl(endpoint, env, payload);
      const headers = {
          'Content-Type': 'application/json'
      };

      if (endpoint.authRequired && !apiKey) {
          throw new Error('Authorization API key is required for this call');
      }

      if (apiKey) {
          headers.authorization = apiKey;
      }

      const requestInit = {
          method: endpoint.method,
          headers
      };

      if (endpoint.method !== 'GET') {
          requestInit.body = JSON.stringify(payload);
      }

      return { url, headers, requestInit };
  }

  async function ensureInternalDartApiReady() {
      let response;
      try {
          response = await fetch('/api/health', {
              cache: 'no-store',
              headers: {
                  'Cache-Control': 'no-cache, no-store, must-revalidate',
                  Pragma: 'no-cache'
              }
          });
      } catch (_) {
          throw new Error('Internal Dart service is unreachable');
      }

      let payload = null;
      try {
          payload = await response.json();
      } catch (_) {
          payload = null;
      }

      if (!response.ok || !payload || payload.status !== 'ok') {
          throw new Error(payload && payload.error ? payload.error : 'Internal Dart service is unavailable');
      }
  }

  async function executeApiRequest(endpoint, env, apiKey, payload) {
      await ensureInternalDartApiReady();

      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
      const details = buildRequestDetails(endpoint, env, apiKey, payload);
      details.requestInit.signal = controller.signal;

      let response;
      try {
          response = await fetch(details.url, details.requestInit);
      } finally {
          clearTimeout(timeoutId);
      }

      const rawText = await response.text();
      let parsedBody = rawText;
      try {
          parsedBody = rawText ? JSON.parse(rawText) : '';
      } catch (_) {}

      const responseHeaders = {};
      response.headers.forEach((value, key) => {
          responseHeaders[key] = value;
      });

      return {
          response,
          parsedBody,
          resultPayload: {
              request: {
                  environment: env,
                  url: details.url,
                  method: endpoint.method,
                  headers: details.headers,
                  body: details.requestInit.body || null
              },
              response: {
                  status: response.status,
                  statusText: response.statusText,
                  ok: response.ok,
                  headers: responseHeaders,
                  body: parsedBody
              }
          }
      };
  }

  async function sendBatchRequest(row, endpoint, config) {
      const statusOutput = row.querySelector('[data-role="status"]');
      const output = row.querySelector('[data-role="output"]');
      const result = row.querySelector('[data-role="result"]');
      const batchResults = row.querySelector('[data-role="batch-results"]');
      const button = row.querySelector('[data-role="send"]');
      const warning = row.querySelector('[data-role="warning"]');
      const startedAt = Date.now();
      statusOutput.textContent = `Preparing batch ${config.action} request...`;
      button.disabled = true;
      output.textContent = '';
      if (result) {
          result.hidden = true;
          result.innerHTML = '';
      }
      if (batchResults) {
          batchResults.hidden = true;
          batchResults.innerHTML = '';
      }
      if (warning) {
          warning.hidden = true;
          warning.textContent = '';
      }

      try {
          const env = row.querySelector('[data-role="env"]').value;
          const apiKey = row.querySelector('[data-role="api-key"]').value.trim();
          const payload = collectPayload(row, endpoint);
          const atSigns = (payload.atSigns || []).filter(Boolean);

          if (atSigns.length === 0) {
              throw new Error(`Enter at least one Atsign for batch ${config.action}`);
          }

          const preview = atSigns.join('\n');
          const confirmed = config.confirm
              ? config.confirm(atSigns, preview)
              : window.confirm(`Are you really sure you want to ${config.action} this list of Atsigns?\n\n${preview}`);
          if (!confirmed) {
              statusOutput.textContent = `Batch ${config.action} cancelled`;
              return;
          }

          if (batchResults) {
              batchResults.hidden = false;
              batchResults.innerHTML = atSigns
                  .map((atSign) => `
                      <div class="api-v4-batch-item" data-batch-atsign="${escapeHtml(atSign)}">
                          <div class="api-v4-batch-item-head">
                              <span class="api-v4-batch-atsign">${escapeHtml(atSign)}</span>
                              <span class="api-v4-batch-state">Queued</span>
                          </div>
                          <div class="api-v4-batch-message"></div>
                          ${config.showCramkey ? '<div class="api-v4-batch-cramkey" hidden></div>' : ''}
                      </div>
                  `)
                  .join('');
          }

          const allResults = [];
          let successCount = 0;

          for (let index = 0; index < atSigns.length; index += 1) {
              const atSign = atSigns[index];
              statusOutput.textContent = `Processing ${index + 1} of ${atSigns.length}...`;
              const item = batchResults
                  ? Array.from(batchResults.querySelectorAll('[data-batch-atsign]')).find((element) => element.getAttribute('data-batch-atsign') === atSign)
                  : null;
              if (item) {
                  item.querySelector('.api-v4-batch-state').textContent = 'Sending...';
              }

              const itemPayload = config.buildItemPayload(atSign, payload);

              try {
                  const executed = await executeApiRequest(endpoint, env, apiKey, itemPayload);
                  allResults.push(executed.resultPayload);
                  const transactionPath = await persistSuccessfulTransaction(endpoint, executed.resultPayload);
                  const apiStatus = executed.parsedBody && executed.parsedBody.status === 'success' ? 'success' : 'error';

                  if (item) {
                      item.classList.add(`api-v4-batch-item-${apiStatus}`);
                      item.querySelector('.api-v4-batch-state').textContent = apiStatus === 'success' ? 'Success' : 'Error';
                      item.querySelector('.api-v4-batch-message').textContent = executed.parsedBody.message || (apiStatus === 'success' ? config.successMessage : 'Request completed');
                      if (config.showCramkey && executed.parsedBody.cramkey) {
                          const cramkeyEl = item.querySelector('.api-v4-batch-cramkey');
                          cramkeyEl.hidden = false;
                          cramkeyEl.innerHTML = `
                              <code>${escapeHtml(executed.parsedBody.cramkey)}</code>
                              <button type="button" class="api-v4-secondary" data-copy-cramkey="${escapeHtml(executed.parsedBody.cramkey)}">Copy</button>
                          `;
                      }
                      if (transactionPath) {
                          item.querySelector('.api-v4-batch-message').textContent += ` | Saved: ${transactionPath}`;
                      }
                  }

                  if (apiStatus === 'success') {
                      successCount += 1;
                  }
              } catch (err) {
                  allResults.push({ atSign, error: err && err.message ? err.message : String(err) });
                  if (item) {
                      item.classList.add('api-v4-batch-item-error');
                      item.querySelector('.api-v4-batch-state').textContent = 'Error';
                      item.querySelector('.api-v4-batch-message').textContent = err && err.message ? err.message : String(err);
                  }
              }
          }

          if (config.showCramkey && batchResults) {
              batchResults.querySelectorAll('[data-copy-cramkey]').forEach((copyBtn) => {
                  copyBtn.addEventListener('click', async function () {
                      const value = copyBtn.getAttribute('data-copy-cramkey') || '';
                      await navigator.clipboard.writeText(value);
                      copyBtn.textContent = 'Copied';
                      setTimeout(() => {
                          copyBtn.textContent = 'Copy';
                      }, 1500);
                  });
              });
          }

          const elapsed = Date.now() - startedAt;
          statusOutput.textContent = `Batch completed: ${successCount}/${atSigns.length} succeeded (${elapsed} ms). One transaction file was created per attempted Atsign.`;
          output.textContent = JSON.stringify(allResults, null, 2);
      } catch (err) {
          const elapsed = Date.now() - startedAt;
          if (err && err.name === 'AbortError') {
              statusOutput.textContent = `Batch timed out after ${REQUEST_TIMEOUT_MS / 1000} seconds`;
              output.textContent = 'The batch request exceeded the 15 second timeout and was cancelled.';
          } else {
              statusOutput.textContent = `Batch failed (${elapsed} ms)`;
              output.textContent = err && err.stack ? err.stack : String(err);
          }
      } finally {
          button.disabled = false;
      }
  }

  function sendBatchResetRequest(row, endpoint) {
      return sendBatchRequest(row, endpoint, {
          action: 'reset',
          showCramkey: false,
          successMessage: 'Reset successfully',
          confirm(atSigns, preview) {
              const answer = window.prompt(
                  `You are about to reset ${atSigns.length} Atsign(s):\n\n${preview}\n\nType RESET to confirm.`
              );
              return answer !== null && answer.trim() === 'RESET';
          },
          buildItemPayload(atSign) {
              return { atSign };
          }
      });
  }

  function normalizeAtSignKey(value) {
      return String(value || '').trim().replace(/^@/, '').toLowerCase();
  }

  function sameAtSign(left, right) {
      return normalizeAtSignKey(left) === normalizeAtSignKey(right);
  }

  function extractAtSignNames(value) {
      if (!Array.isArray(value)) {
          return [];
      }

      return value
          .map((item) => {
              if (item && typeof item === 'object') {
                  return String(item.atSign || item.atsign || item.name || '');
              }
              return String(item || '');
          })
          .map((item) => item.trim())
          .filter(Boolean);
  }

  // The registrar reports a generic top-level message and puts the real
  // reason on each entry of failed/skippedatSigns, so keep those by Atsign.
  function extractAtSignMessages(value) {
      const messages = new Map();
      if (!Array.isArray(value)) {
          return messages;
      }

      value.forEach((item) => {
          if (!item || typeof item !== 'object') {
              return;
          }
          const name = normalizeAtSignKey(item.atSign || item.atsign || item.name || '');
          const message = String(item.message || item.reason || '').trim();
          if (name && message) {
              messages.set(name, message);
          }
      });
      return messages;
  }

  function renderDeleteItems(container, atSigns) {
      if (!container) return;

      container.hidden = false;
      container.innerHTML = atSigns
          .map((atSign) => `
              <div class="api-v4-batch-item" data-batch-atsign="${escapeHtml(atSign)}">
                  <div class="api-v4-batch-item-head">
                      <span class="api-v4-batch-atsign">${escapeHtml(atSign)}</span>
                      <span class="api-v4-batch-state">Queued</span>
                  </div>
                  <div class="api-v4-batch-message"></div>
              </div>
          `)
          .join('');
  }

  function updateDeleteItem(container, atSign, state, message) {
      if (!container) return;

      const item = Array.from(container.querySelectorAll('[data-batch-atsign]'))
          .find((element) => sameAtSign(element.getAttribute('data-batch-atsign'), atSign));
      if (!item) return;

      item.classList.add(state === 'Deleted' ? 'api-v4-batch-item-success' : 'api-v4-batch-item-error');
      item.querySelector('.api-v4-batch-state').textContent = state;
      item.querySelector('.api-v4-batch-message').textContent = message;
  }

  async function moveStaleLocalKeys(deletedAtSigns, warning) {
      if (!warning) return;

      let results;
      try {
          const response = await fetch('/api/atsigns/stale', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ atSigns: deletedAtSigns })
          });
          const data = await response.json();
          if (!response.ok || data.error) {
              throw new Error(data.error || 'Failed to move stale keys');
          }
          results = data.results || [];
      } catch (err) {
          warning.textContent = 'Could not move local key files to stale/. '
              + 'You may want to manually check ~/.atsign/keys/ for stale files. '
              + (err.message || '');
          warning.hidden = false;
          return;
      }

      const moved = results.filter((r) => r.status === 'moved');
      const errors = results.filter((r) => r.status === 'error');
      if (moved.length === 0 && errors.length === 0) return;

      const parts = [];
      if (moved.length > 0) {
          parts.push(`Moved ${moved.length} local key file(s) to stale/: `
              + moved.map((r) => r.to).join(', ') + '.'
              + ' If any of these Atsigns also exist in another atDirectory,'
              + ' retrieve the key file from stale/ and move it back to ~/.atsign/keys/.');
      }
      if (errors.length > 0) {
          parts.push(`Failed to move ${errors.length} file(s): `
              + errors.map((r) => `${r.atSign} (${r.error})`).join(', ') + '.');
      }
      warning.textContent = parts.join(' ');
      warning.hidden = false;
  }

  async function sendDeleteAtSignsRequest(row, endpoint) {
      const statusOutput = row.querySelector('[data-role="status"]');
      const output = row.querySelector('[data-role="output"]');
      const batchResults = row.querySelector('[data-role="batch-results"]');
      const button = row.querySelector('[data-role="send"]');
      const warning = row.querySelector('[data-role="warning"]');
      const startedAt = Date.now();
      const executedPayloads = [];

      clearRowOutput(row);
      statusOutput.textContent = 'Preparing delete request...';
      button.disabled = true;

      try {
          const env = row.querySelector('[data-role="env"]').value;
          const apiKey = row.querySelector('[data-role="api-key"]').value.trim();
          const payload = collectPayload(row, endpoint);
          const atSigns = (payload.atSigns || []).filter(Boolean);

          if (atSigns.length === 0) {
              throw new Error('Enter at least one Atsign to delete');
          }

          const preview = atSigns.join('\n');
          const confirmation = window.prompt(`Type DELETE to permanently delete ${atSigns.length} Atsign(s). This cannot be undone.\n\n${preview}`, '');
          if (confirmation === null) {
              statusOutput.textContent = 'Delete cancelled';
              output.textContent = 'Delete request cancelled before sending.';
              return;
          }

          if (confirmation.trim() !== 'DELETE') {
              throw new Error('Confirmation did not match. Type exactly: DELETE');
          }

          renderDeleteItems(batchResults, atSigns);

          statusOutput.textContent = 'Requesting delete token...';
          const tokenExecuted = await executeApiRequest(endpoint, env, apiKey, {
              atSigns,
              operation: 'deletetoken'
          });
          executedPayloads.push(tokenExecuted.resultPayload);

          const tokenBody = tokenExecuted.parsedBody && typeof tokenExecuted.parsedBody === 'object' ? tokenExecuted.parsedBody : {};
          const tokenData = tokenBody.data && typeof tokenBody.data === 'object' ? tokenBody.data : {};
          const skipped = extractAtSignNames(tokenData.skippedatSigns);
          const skippedMessages = extractAtSignMessages(tokenData.skippedatSigns);
          const token = tokenData.token ? String(tokenData.token) : '';

          skipped.forEach((atSign) => {
              updateDeleteItem(batchResults, atSign, 'Skipped',
                  skippedMessages.get(normalizeAtSignKey(atSign))
                  || 'The Authorization API Key did not create this Atsign, so it cannot delete it.');
          });

          const deletable = atSigns.filter((atSign) => !skipped.some((entry) => sameAtSign(entry, atSign)));
          if (deletable.length === 0) {
              throw new Error('The Authorization API Key did not create any of these Atsigns, so none of them can be deleted');
          }

          if (!token) {
              throw new Error(tokenBody.message || 'Registrar did not return a delete token');
          }

          statusOutput.textContent = `Deleting ${deletable.length} of ${atSigns.length}...`;
          const deleteExecuted = await executeApiRequest(endpoint, env, apiKey, {
              token,
              atSigns: deletable,
              operation: 'delete'
          });
          executedPayloads.push(deleteExecuted.resultPayload);

          const deleteBody = deleteExecuted.parsedBody && typeof deleteExecuted.parsedBody === 'object' ? deleteExecuted.parsedBody : {};
          const deleteData = deleteBody.data && typeof deleteBody.data === 'object' ? deleteBody.data : {};
          const failed = extractAtSignNames(deleteData.failed);
          // The spec also allows ineligible Atsigns to come back as skipped
          // on this call, though the registrar has only been seen using failed.
          const skippedOnDelete = extractAtSignNames(deleteData.skippedatSigns);
          const reasons = new Map([
              ...extractAtSignMessages(deleteData.failed),
              ...extractAtSignMessages(deleteData.skippedatSigns)
          ]);
          const deleted = Array.isArray(deleteData.deleted)
              ? extractAtSignNames(deleteData.deleted)
              : (deleteBody.status === 'success'
                  ? deletable.filter((atSign) => !failed.some((entry) => sameAtSign(entry, atSign)))
                  : []);

          deleted.forEach((atSign) => {
              updateDeleteItem(batchResults, atSign, 'Deleted', 'Deleted from the registrar.');
          });

          const unresolved = deletable.filter((atSign) => !deleted.some((entry) => sameAtSign(entry, atSign)));
          const failureReasons = [];
          unresolved.forEach((atSign) => {
              const reason = reasons.get(normalizeAtSignKey(atSign))
                  || deleteBody.message
                  || 'The registrar did not report this Atsign as deleted.';
              if (!failureReasons.includes(reason)) {
                  failureReasons.push(reason);
              }
              const state = skippedOnDelete.some((entry) => sameAtSign(entry, atSign)) ? 'Skipped' : 'Failed';
              updateDeleteItem(batchResults, atSign, state, reason);
          });

          output.textContent = JSON.stringify(executedPayloads, null, 2);

          let transactionPath = null;
          try {
              transactionPath = await persistSuccessfulTransaction(endpoint, deleteExecuted.resultPayload);
          } catch (persistError) {
              if (warning) {
                  warning.textContent = `Warning: the delete request completed, but the transaction file was not saved: ${persistError.message}`;
                  warning.hidden = false;
              }
          }

          if (deleted.length > 0) {
              await moveStaleLocalKeys(deleted, warning);
          }

          const elapsed = Date.now() - startedAt;
          statusOutput.textContent = `Deleted ${deleted.length}/${atSigns.length} (${elapsed} ms).`
              + (skipped.length ? ` Skipped ${skipped.length}.` : '')
              + (unresolved.length ? ` Failed ${unresolved.length}.` : '')
              // One shared reason is the common case, and it is the only thing
              // worth reading; per-Atsign reasons stay on the rows below.
              + (failureReasons.length === 1 ? ` ${failureReasons[0]}` : '')
              + (transactionPath ? ` Transaction saved to: ${transactionPath}` : '');
      } catch (err) {
          const elapsed = Date.now() - startedAt;
          const details = executedPayloads.length ? `${JSON.stringify(executedPayloads, null, 2)}\n\n` : '';
          if (err && err.name === 'AbortError') {
              statusOutput.textContent = `Request timed out after ${REQUEST_TIMEOUT_MS / 1000} seconds`;
              output.textContent = `${details}The request exceeded the 15 second timeout and was cancelled.`;
          } else {
              statusOutput.textContent = `Delete failed (${elapsed} ms)`;
              output.textContent = details + (err && err.stack ? err.stack : String(err));
          }
      } finally {
          button.disabled = false;
      }
  }

  async function sendRequest(row, endpoint) {
      const statusOutput = row.querySelector('[data-role="status"]');
      const output = row.querySelector('[data-role="output"]');
      const button = row.querySelector('[data-role="send"]');
      const startedAt = Date.now();
      statusOutput.textContent = 'Sending request...';
      button.disabled = true;

      try {
          const env = row.querySelector('[data-role="env"]').value;
          const apiKey = row.querySelector('[data-role="api-key"]').value.trim();
          const payload = collectPayload(row, endpoint);

          const executed = await executeApiRequest(endpoint, env, apiKey, payload);
          const parsedBody = executed.parsedBody;
          const resultPayload = executed.resultPayload;

          let transactionPath = null;
          try {
              transactionPath = await persistSuccessfulTransaction(endpoint, resultPayload);
          } catch (persistError) {
              const warning = row.querySelector('[data-role="warning"]');
              if (warning) {
                  warning.textContent = `Warning: request completed, but transaction file was not saved: ${persistError.message}`;
                  warning.hidden = false;
              }
          }

          const elapsed = Date.now() - startedAt;
          statusOutput.textContent = `${executed.response.status} ${executed.response.statusText} (${elapsed} ms)`;
          renderPrimarySummary(row, endpoint, parsedBody);
          if (transactionPath) {
              const result = row.querySelector('[data-role="result"]');
              if (result && !result.hidden) {
                  result.innerHTML += `<div class="api-v4-transaction-path">Transaction saved to: <code>${escapeHtml(transactionPath)}</code></div>`;
              }
          }
          output.textContent = JSON.stringify(resultPayload, null, 2);
      } catch (err) {
          const elapsed = Date.now() - startedAt;
          if (err && err.name === 'AbortError') {
              statusOutput.textContent = `Request timed out after ${REQUEST_TIMEOUT_MS / 1000} seconds`;
              output.textContent = 'The request exceeded the 15 second timeout and was cancelled.';
          } else {
              statusOutput.textContent = `Request failed (${elapsed} ms)`;
              output.textContent = err && err.stack ? err.stack : String(err);
          }
          renderPrimarySummary(row, endpoint, null);
      } finally {
          button.disabled = false;
      }
  }

  function clearRowOutput(row) {
      const result = row.querySelector('[data-role="result"]');
      if (result) {
          result.hidden = true;
          result.innerHTML = '';
      }
      const batchResults = row.querySelector('[data-role="batch-results"]');
      if (batchResults) {
          batchResults.hidden = true;
          batchResults.innerHTML = '';
      }
      row.querySelector('[data-role="status"]').textContent = 'Ready';
      row.querySelector('[data-role="output"]').textContent = '';
      const warning = row.querySelector('[data-role="warning"]');
      if (warning) {
          warning.hidden = true;
          warning.textContent = '';
      }
  }

  function bindRow(root, endpoint) {
      const row = root.querySelector(`[data-endpoint-id="${endpoint.id}"]`);
      const toggle = row.querySelector('[data-role="toggle"]');
      const body = row.querySelector('[data-role="body"]');
      row.querySelector('[data-role="send"]').addEventListener('click', function () {
          if (endpoint.type === 'batch-reset') {
              sendBatchResetRequest(row, endpoint);
          } else if (endpoint.type === 'delete-atsigns') {
              sendDeleteAtSignsRequest(row, endpoint);
          } else {
              sendRequest(row, endpoint);
          }
      });
      row.querySelector('[data-role="clear"]').addEventListener('click', function () {
          clearRowOutput(row);
      });
      toggle.addEventListener('click', function () {
          const expanded = toggle.getAttribute('aria-expanded') === 'true';
          toggle.setAttribute('aria-expanded', expanded ? 'false' : 'true');
          body.hidden = expanded;
      });
  }

  function syncApiKeyInputs(root, value, visible) {
      if (!root) return;

      root.querySelectorAll('[data-role="api-key"]').forEach((input) => {
          input.value = value;
          input.type = visible ? 'text' : 'password';
      });
  }

  async function loadSavedConfig(statusEl, configFields, root, isVisible) {
      try {
          const response = await fetch('/api/config');
          const data = await response.json();
          if (!response.ok || data.error) {
              throw new Error(data.error || 'Failed to load config');
          }

          configFields.apiKey.value = data.registrarApiKey || '';
          configFields.prefix.value = data.atsignPrefix || '';
          configFields.postfix.value = data.atsignPostfix || '';
          syncApiKeyInputs(root, configFields.apiKey.value, isVisible);

          if (statusEl) {
              statusEl.textContent = 'Loaded config from ~/.atsign/at_activate_web/config.json';
          }
      } catch (err) {
          if (statusEl) {
              statusEl.textContent = `Load failed: ${err.message}`;
          }
      }
  }

  function bindConfigPanel(root) {
      const globalApiKey = document.getElementById('api-v4-global-api-key');
      const atsignPrefix = document.getElementById('api-v4-atsign-prefix');
      const atsignPostfix = document.getElementById('api-v4-atsign-postfix');
      const toggleButton = document.getElementById('api-v4-toggle-api-key');
      const copyButton = document.getElementById('api-v4-copy-api-key');
      const saveButton = document.getElementById('api-v4-save-api-key');
      const refreshButton = document.getElementById('api-v4-refresh-config');
      const openFolderButton = document.getElementById('api-v4-open-config-folder');
      const saveStatus = document.getElementById('api-v4-save-status');
      if (!globalApiKey || !atsignPrefix || !atsignPostfix) return;

      let isVisible = false;
      const configFields = {
          apiKey: globalApiKey,
          prefix: atsignPrefix,
          postfix: atsignPostfix
      };

      const sync = function () {
          syncApiKeyInputs(root, globalApiKey.value, isVisible);
      };

      globalApiKey.addEventListener('input', sync);
      if (toggleButton) {
          toggleButton.addEventListener('click', function () {
              isVisible = !isVisible;
              globalApiKey.type = isVisible ? 'text' : 'password';
              toggleButton.textContent = isVisible ? 'Hide' : 'Show';
              sync();
          });
      }
      if (copyButton) {
          copyButton.addEventListener('click', async function () {
              try {
                  await navigator.clipboard.writeText(globalApiKey.value);
                  if (saveStatus) saveStatus.textContent = 'API key copied to clipboard';
              } catch (_) {
                  if (saveStatus) saveStatus.textContent = 'Copy failed';
              }
          });
      }
      if (saveButton) {
          saveButton.addEventListener('click', async function () {
              saveButton.disabled = true;
              if (saveStatus) saveStatus.textContent = 'Saving...';
              try {
                  const response = await fetch('/api/config', {
                      method: 'POST',
                      headers: {
                          'Content-Type': 'application/json'
                      },
                      body: JSON.stringify({
                          registrarApiKey: globalApiKey.value,
                          atsignPrefix: atsignPrefix.value,
                          atsignPostfix: atsignPostfix.value
                      })
                  });
                  const data = await response.json();
                  if (!response.ok || data.error) {
                      throw new Error(data.error || 'Failed to save config');
                  }
                  if (saveStatus) saveStatus.textContent = 'Saved to ~/.atsign/at_activate_web/config.json';
              } catch (err) {
                  if (saveStatus) saveStatus.textContent = `Save failed: ${err.message}`;
              } finally {
                  saveButton.disabled = false;
              }
          });
      }

      if (refreshButton) {
          refreshButton.addEventListener('click', async function () {
              refreshButton.disabled = true;
              if (saveStatus) saveStatus.textContent = 'Refreshing...';
              try {
                  await loadSavedConfig(saveStatus, configFields, root, isVisible);
              } finally {
                  refreshButton.disabled = false;
              }
          });
      }

      if (openFolderButton) {
          openFolderButton.addEventListener('click', async function () {
              openFolderButton.disabled = true;
              if (saveStatus) saveStatus.textContent = 'Opening config folder...';
              try {
                  const response = await fetch('/api/config/open-folder', {
                      method: 'POST'
                  });
                  const data = await response.json();
                  if (!response.ok || data.error) {
                      throw new Error(data.error || 'Failed to open config folder');
                  }
                  if (saveStatus) saveStatus.textContent = `Opened config folder: ${data.path}`;
              } catch (err) {
                  if (saveStatus) saveStatus.textContent = `Open failed: ${err.message}`;
              } finally {
                  openFolderButton.disabled = false;
              }
          });
      }

      globalApiKey.type = 'password';
      sync();
      loadSavedConfig(saveStatus, configFields, root, isVisible);
  }

  // --- Register + Activate tab ---

  var registerActivateItems = [];
  var registerActivateInProgress = 0;

  function registerActivateGetElements(atSign) {
      var escaped = CSS.escape(atSign);
      return {
          statusEl: document.querySelector('[data-ra-activate-item-status="' + escaped + '"]'),
          badgeEl: document.querySelector('[data-ra-activate-badge="' + escaped + '"]'),
          logEl: document.querySelector('[data-ra-activate-log="' + escaped + '"]')
      };
  }

  function registerActivateSetBadge(badgeEl, text, badgeClass) {
      if (!badgeEl) return;
      badgeEl.textContent = text;
      badgeEl.className = 'api-v4-result-badge api-v4-result-badge-' + badgeClass;
  }

  async function initRegisterActivateTab() {
      var apiKeyInput = document.getElementById('ra-api-key');
      if (apiKeyInput && !apiKeyInput.value.trim()) {
          try {
              var response = await fetch('/api/config');
              var data = await response.json();
              if (response.ok && data.registrarApiKey) apiKeyInput.value = data.registrarApiKey;
          } catch (_) {}
      }

      if (!registerActivateItems.length) {
          try {
              var cramResponse = await fetch('/api/cramkeys');
              var cramData = await cramResponse.json();
              if (cramResponse.ok && cramData.cramKeys) {
                  var items = cramData.cramKeys
                      .filter(function (entry) { return entry.data && entry.data.atSign && entry.data.cramKey; })
                      .map(function (entry) { return { atSign: entry.data.atSign, cramkey: entry.data.cramKey }; });
                  if (items.length > 0) registerActivateShowActivationSection(items);
              }
          } catch (_) {}
      }
  }

  async function registerActivateRegister() {
      var registerButton = document.getElementById('ra-register-btn');
      var registerStatus = document.getElementById('ra-register-status');
      var registerResults = document.getElementById('ra-register-results');
      var environment = document.getElementById('ra-env').value;
      var apiKey = (document.getElementById('ra-api-key').value || '').trim();
      var startAtServer = document.getElementById('ra-start-at-server').value;
      var rawText = (document.getElementById('ra-atsigns').value || '').trim();

      var atSigns = rawText.split('\n')
          .map(function (value) { return normalizeAtSignForRequest(value.trim()); })
          .filter(Boolean);

      if (registerActivateInProgress > 0) {
          registerStatus.textContent = 'Cannot register while activation is in progress';
          return;
      }
      if (!atSigns.length) { registerStatus.textContent = 'Enter at least one Atsign'; return; }
      if (!apiKey) { registerStatus.textContent = 'Authorization API Key is required'; return; }
      if (!window.confirm('Register ' + atSigns.length + ' Atsign(s)?\n\n' + atSigns.join('\n'))) {
          registerStatus.textContent = 'Registration cancelled'; return;
      }

      registerButton.disabled = true;
      registerStatus.textContent = 'Registering...';
      registerResults.hidden = false;
      registerResults.innerHTML = atSigns.map(function (atSign) {
          var escapedAtSign = escapeHtml(atSign);
          return '<div class="api-v4-batch-item" data-ra-batch="' + escapedAtSign + '">'
              + '<div class="api-v4-batch-item-head"><span class="api-v4-batch-atsign">' + escapedAtSign + '</span>'
              + '<span class="api-v4-batch-state">Queued</span></div>'
              + '<div class="api-v4-batch-message"></div><div class="api-v4-batch-cramkey" hidden></div></div>';
      }).join('');

      var endpoint = REGISTER_ATSIGNS_ENDPOINT;
      var successfulItems = [];

      try {
          for (var index = 0; index < atSigns.length; index++) {
              registerStatus.textContent = 'Registering ' + (index + 1) + ' of ' + atSigns.length + '...';
              var result = await registerActivateRegisterOne(atSigns[index], endpoint, environment, apiKey, startAtServer, registerResults);
              if (result) successfulItems.push(result);
          }

          registerStatus.textContent = 'Registration complete: ' + successfulItems.length + '/' + atSigns.length + ' succeeded';
          if (successfulItems.length > 0) registerActivateShowActivationSection(successfulItems);
      } finally {
          registerButton.disabled = false;
      }
  }

  async function registerActivateRegisterOne(atSign, endpoint, environment, apiKey, startAtServer, registerResults) {
      var batchItem = registerResults.querySelector('[data-ra-batch="' + CSS.escape(atSign) + '"]');
      if (batchItem) batchItem.querySelector('.api-v4-batch-state').textContent = 'Sending...';

      var payload = { atSign: atSign, operation: 'register' };
      if (startAtServer) payload.startAtServer = startAtServer;

      var executed;
      try {
          executed = await executeApiRequest(endpoint, environment, apiKey, payload);
      } catch (error) {
          if (batchItem) {
              batchItem.classList.add('api-v4-batch-item-error');
              batchItem.querySelector('.api-v4-batch-state').textContent = 'Error';
              batchItem.querySelector('.api-v4-batch-message').textContent = error.message || String(error);
          }
          return null;
      }

      var responseBody = (typeof executed.parsedBody === 'object' && executed.parsedBody) || {};
      var isSuccess = responseBody.status === 'success';
      var statusMessage = responseBody.message || '';
      if (!statusMessage && typeof executed.parsedBody === 'string') {
          statusMessage = executed.parsedBody.substring(0, 200);
      }

      var transactionPath = '';
      try {
          transactionPath = await persistSuccessfulTransaction(endpoint, executed.resultPayload) || '';
      } catch (_) {}

      if (batchItem) {
          batchItem.classList.add(isSuccess ? 'api-v4-batch-item-success' : 'api-v4-batch-item-error');
          batchItem.querySelector('.api-v4-batch-state').textContent = isSuccess ? 'Success' : 'Error';
          batchItem.querySelector('.api-v4-batch-message').textContent =
              statusMessage + (transactionPath ? ' | Saved: ' + transactionPath : '');
          if (responseBody.cramkey) {
              var cramkeyEl = batchItem.querySelector('.api-v4-batch-cramkey');
              cramkeyEl.hidden = false;
              cramkeyEl.innerHTML = '<code>' + escapeHtml(responseBody.cramkey) + '</code>';
          }
      }
      if (isSuccess && responseBody.cramkey) {
          return { atSign: extractAtSignFromCramkey(responseBody.cramkey) || atSign, cramkey: responseBody.cramkey };
      }
      return null;
  }

  function registerActivateShowActivationSection(items) {
      var activateSection = document.getElementById('ra-activate-section');
      var activateList = document.getElementById('ra-activate-list');
      var activateStatusEl = document.getElementById('ra-activate-status');
      if (!activateSection || !activateList) return;

      activateSection.hidden = false;
      activateStatusEl.textContent = items.length + ' Atsign(s) ready to activate';
      registerActivateItems = items;

      activateList.innerHTML = items.map(function (item) {
          var escapedAtSign = escapeHtml(item.atSign);
          return '<details class="api-v4-transaction-card" data-ra-activate="' + escapedAtSign + '">'
              + '<summary class="api-v4-transaction-summary">'
              + '<div class="api-v4-transaction-title-wrap"><div class="api-v4-transaction-title">' + escapedAtSign + '</div></div>'
              + '<div class="api-v4-transaction-summary-right">'
              + '<button type="button" class="api-v4-secondary" data-ra-activate-btn="' + escapedAtSign + '">Activate</button>'
              + '<span class="api-v4-result-badge api-v4-result-badge-neutral" data-ra-activate-badge="' + escapedAtSign + '">pending</span>'
              + '</div></summary>'
              + '<div class="api-v4-transaction-body">'
              + '<div class="api-v4-transaction-cramkey"><code>' + escapeHtml(item.cramkey) + '</code></div>'
              + '<div class="api-v4-status" data-ra-activate-item-status="' + escapedAtSign + '">Ready</div>'
              + '<pre class="api-v4-activation-log" data-ra-activate-log="' + escapedAtSign + '" hidden></pre>'
              + '</div></details>';
      }).join('');

      activateList.querySelectorAll('[data-ra-activate-btn]').forEach(function (activateButton) {
          activateButton.addEventListener('click', function (event) {
              event.preventDefault();
              event.stopPropagation();
              registerActivateActivateCramKey(activateButton.getAttribute('data-ra-activate-btn'), activateButton);
          });
      });
  }

  async function registerActivateActivateCramKey(atSign, activateButton) {
      if (activateButton && activateButton.disabled) return false;
      var elements = registerActivateGetElements(atSign);
      if (activateButton) activateButton.disabled = true;
      if (elements.statusEl) elements.statusEl.textContent = 'Activating...';
      registerActivateSetBadge(elements.badgeEl, 'running', 'success');
      if (elements.logEl) { elements.logEl.hidden = true; elements.logEl.textContent = ''; }

      registerActivateInProgress++;
      try {
          var password = (document.getElementById('ra-activate-password') || {}).value || '';
          password = password.trim();
          if (password) {
              var pwResponse = await fetch('/api/password', {
                  method: 'POST',
                  headers: { 'Content-Type': 'application/json' },
                  body: JSON.stringify({ atsign: atSign, password: password })
              });
              var pwData = await pwResponse.json();
              if (!pwResponse.ok || pwData.error) throw new Error(pwData.error || 'Failed to set password');
          }

          var response = await fetch('/api/cramkeys/activate/start', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ atSign: atSign })
          });
          var data = await response.json();
          if (!response.ok || data.error) throw new Error(data.error || 'Activation failed');

          if (elements.logEl) { elements.logEl.hidden = false; elements.logEl.textContent = 'Activation started...\n'; }
          var job = await registerActivatePollJob(data.jobId, elements, activateButton);
          return job && job.status === 'success';
      } catch (error) {
          if (elements.statusEl) elements.statusEl.textContent = 'Activation failed: ' + error.message;
          registerActivateSetBadge(elements.badgeEl, 'error', 'error');
          if (activateButton) activateButton.disabled = false;
          return false;
      } finally {
          registerActivateInProgress--;
      }
  }

  function registerActivatePollJob(jobId, elements, activateButton) {
      return new Promise(function (resolve, reject) {
          var intervalId;

          function renderJobStatus(job) {
              if (elements.logEl) {
                  var logLines = [].concat(
                      (job.stdout || []).map(function (line) { return '[stdout] ' + line; }),
                      (job.stderr || []).map(function (line) { return '[stderr] ' + line; })
                  );
                  elements.logEl.hidden = false;
                  elements.logEl.textContent = logLines.length ? logLines.join('\n') : 'No process output yet...';
              }
              if (job.status === 'running') {
                  if (elements.statusEl) elements.statusEl.textContent = 'Running...';
                  return false;
              }
              if (job.status === 'success') {
                  if (elements.statusEl) elements.statusEl.textContent = 'Success. Archived to ' + (job.archivedPath || 'old/');
                  registerActivateSetBadge(elements.badgeEl, 'Onboarded', 'neutral');
              } else {
                  if (elements.statusEl) elements.statusEl.textContent = 'Failed. Exit code: ' + job.exitCode;
                  registerActivateSetBadge(elements.badgeEl, 'error', 'error');
              }
              if (activateButton) activateButton.disabled = false;
              return true;
          }

          async function pollJobStatus() {
              try {
                  var response = await fetch('/api/cramkeys/activate/status/' + encodeURIComponent(jobId));
                  var job = await response.json();
                  if (!response.ok || job.error) throw new Error(job.error || 'Status check failed');
                  if (renderJobStatus(job)) { clearInterval(intervalId); resolve(job); }
              } catch (error) {
                  if (elements.statusEl) elements.statusEl.textContent = 'Status check failed: ' + error.message;
                  registerActivateSetBadge(elements.badgeEl, 'error', 'error');
                  if (activateButton) activateButton.disabled = false;
                  clearInterval(intervalId);
                  reject(error);
              }
          }

          pollJobStatus();
          intervalId = setInterval(pollJobStatus, 1000);
      });
  }

  async function registerActivateActivateAll() {
      var activateAllStatusEl = document.getElementById('ra-activate-status');
      var activateAllButton = document.getElementById('ra-activate-all-btn');

      if (!registerActivateItems.length) {
          if (activateAllStatusEl) activateAllStatusEl.textContent = 'No Atsigns to activate';
          return;
      }

      var atSignNames = registerActivateItems.map(function (item) { return item.atSign; });
      if (!window.confirm('Activate ' + atSignNames.length + ' Atsign(s)?\n\n' + atSignNames.join('\n'))) {
          if (activateAllStatusEl) activateAllStatusEl.textContent = 'Batch activation cancelled';
          return;
      }

      if (activateAllButton) activateAllButton.disabled = true;
      try {
          var successCount = 0;
          for (var index = 0; index < atSignNames.length; index++) {
              if (activateAllStatusEl) activateAllStatusEl.textContent = 'Activating ' + (index + 1) + ' of ' + atSignNames.length + ': ' + atSignNames[index] + '...';
              var activateButton = document.querySelector('[data-ra-activate-btn="' + CSS.escape(atSignNames[index]) + '"]');
              if (await registerActivateActivateCramKey(atSignNames[index], activateButton)) successCount++;
          }
          if (activateAllStatusEl) activateAllStatusEl.textContent = 'Batch activation complete: ' + successCount + '/' + atSignNames.length + ' succeeded';
      } finally {
          if (activateAllButton) activateAllButton.disabled = false;
      }
  }

  async function initializeApiV4Console() {
      const root = document.getElementById('api-v4-rows');
      if (!root) return;
      root.innerHTML = PRIMARY_ENDPOINTS.map(endpointMarkup).join('') + otherSectionMarkup();
      ALL_ENDPOINTS.forEach((endpoint) => bindRow(root, endpoint));
      bindConfigPanel(root);
      window.loadHistory = loadHistory;
      window.filterHistory = filterHistory;
      window.loadMoreHistoryEvents = loadMoreHistoryEvents;
      window.openTransactionsFolder = openTransactionsFolder;
      window.registerActivateRegister = registerActivateRegister;
      window.registerActivateActivateAll = registerActivateActivateAll;
      window.initRegisterActivateTab = initRegisterActivateTab;
  }

  if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', initializeApiV4Console);
  } else {
      initializeApiV4Console();
  }
})();
''';