From 3926d561c0ef1339e4e7aa9665c42954a8cf8d4b34c91f426a878031edc6d1c1 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Thu, 16 Jul 2026 19:03:04 +0400 Subject: [PATCH 1/5] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D1=82=D1=8C=20=D1=84=D0=B8=D0=BD=D0=B0=D0=BB=20=D1=80?= =?UTF-8?q?=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BC=D0=BE=D0=B1=D0=B8=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.properties | 4 +- .../js/pages/registration-payment-view.js | 89 +++++++++++++------ shine-UI/styles/components.css | 33 +++++++ 3 files changed, 97 insertions(+), 29 deletions(-) diff --git a/VERSION.properties b/VERSION.properties index 36360fb7..f0ad64a2 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.324 -server.version=1.2.296 +client.version=1.2.325 +server.version=1.2.297 diff --git a/shine-UI/js/pages/registration-payment-view.js b/shine-UI/js/pages/registration-payment-view.js index 9cd3f1d2..02ccdb0b 100644 --- a/shine-UI/js/pages/registration-payment-view.js +++ b/shine-UI/js/pages/registration-payment-view.js @@ -24,10 +24,29 @@ import { defaultServerLogin } from '../deploy-config.js'; export const pageMeta = { id: 'registration-payment-view', title: 'Оплата регистрации', showAppChrome: false }; const MIN_REQUIRED_SOL = 0.01; -const AUTO_LOGIN_INITIAL_DELAY_MS = 5000; -const AUTO_LOGIN_RETRY_MS = 1000; +const AUTO_LOGIN_INITIAL_DELAY_MS = 10000; +const AUTO_LOGIN_RETRY_MS = 2000; const EMPTY_PASSWORD_WORDS = Array.from({ length: 12 }, () => ''); +function getExplorerClusterName(endpoint) { + const source = String(endpoint || '').trim().toLowerCase(); + if (!source) return 'mainnet-beta'; + if (source.includes('devnet')) return 'devnet'; + if (source.includes('testnet')) return 'testnet'; + return 'mainnet-beta'; +} + +function makeSolanaExplorerTxUrl(signature, endpoint) { + const cleanSignature = String(signature || '').trim(); + if (!cleanSignature) return ''; + const cluster = getExplorerClusterName(endpoint); + const url = new URL(`https://explorer.solana.com/tx/${encodeURIComponent(cleanSignature)}`); + if (cluster !== 'mainnet-beta') { + url.searchParams.set('cluster', cluster); + } + return url.toString(); +} + function parseBalanceSol(value) { const parsed = Number.parseFloat(String(value || '').replace(',', '.')); return Number.isFinite(parsed) ? parsed : 0; @@ -262,14 +281,16 @@ export function render({ navigate }) { // Регистрация на Solana (смарт контракт) submitButton.textContent = 'Регистрация в Solana...'; + let registrationTxId = ''; try { - await registerUserOnSolana({ + const registrationResult = await registerUserOnSolana({ login: state.registrationDraft.login, keyBundle, solanaEndpoint: state.entrySettings.solanaServer, accessServers: [state.entrySettings.shineServerLogin || defaultServerLogin], promoCode: '', }); + registrationTxId = String(registrationResult?.signature || '').trim(); } catch (solanaError) { const solanaMsg = formatSolanaErrorDetails(solanaError); // Пользователь уже зарегистрирован в Solana — продолжаем @@ -278,7 +299,7 @@ export function render({ navigate }) { } } - renderSolanaDoneStage({ navigate, status, keyBundle }); + renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId }); } catch (error) { const message = toUserMessage(error, 'Не удалось завершить регистрацию.'); setAuthError(message); @@ -324,37 +345,47 @@ export function render({ navigate }) { return screen; } -function renderSolanaDoneStage({ navigate, status, keyBundle }) { +function renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId = '' }) { const screen = document.querySelector('section.stack'); if (!screen) return; + const headerBackButton = screen.querySelector('.page-header .header-left .icon-btn'); const card = screen.querySelector('.card.stack'); if (!card) return; + card.classList.add('registration-finish-card'); let initialDelayId = null; let retryTimerId = null; let loginInFlight = false; let loginCompleted = false; + const txExplorerUrl = makeSolanaExplorerTxUrl(registrationTxId, state.entrySettings.solanaServer); const title = document.createElement('h2'); + title.className = 'registration-finish-title'; title.textContent = 'Поздравляем, регистрация завершена'; - title.style.margin = '0'; const hint = document.createElement('p'); - hint.className = 'auth-copy'; - hint.textContent = 'Осталось подождать несколько секунд, пока обновится запись в блокчейне. После этого приложение автоматически войдёт в ваш аккаунт.'; + hint.className = 'auth-copy registration-finish-text'; + hint.textContent = 'Подождите 10 секунд, пока обновится транзакция вашей регистрации в блокчейне Solana. После этого вход в аккаунт произойдёт автоматически.'; + + const txIdLine = document.createElement('p'); + txIdLine.className = 'meta-muted registration-finish-tx'; + if (registrationTxId && txExplorerUrl) { + const txLabel = document.createElement('span'); + txLabel.textContent = 'Tx ID регистрации: '; + const txLink = document.createElement('a'); + txLink.className = 'registration-finish-tx-link'; + txLink.href = txExplorerUrl; + txLink.target = '_blank'; + txLink.rel = 'noopener noreferrer'; + txLink.textContent = registrationTxId; + txIdLine.append(txLabel, txLink); + } else { + txIdLine.textContent = 'Tx ID регистрации: ожидаем подтверждённую подпись'; + } const progress = document.createElement('p'); - progress.className = 'meta-muted'; - progress.textContent = 'Подготавливаем автоматический вход...'; - - const altHint = document.createElement('p'); - altHint.className = 'meta-muted'; - altHint.textContent = 'Или вы можете'; - - const backBtn = document.createElement('button'); - backBtn.className = 'ghost-btn'; - backBtn.type = 'button'; - backBtn.textContent = 'Вернуться в главное меню'; + progress.className = 'meta-muted registration-finish-progress'; + progress.textContent = 'Подготавливаем проверку регистрации...'; const stopAutoLogin = () => { if (initialDelayId) { @@ -370,7 +401,7 @@ function renderSolanaDoneStage({ navigate, status, keyBundle }) { const tryAutoLogin = async () => { if (loginCompleted || loginInFlight) return; loginInFlight = true; - progress.textContent = 'Пробуем автоматически войти в аккаунт...'; + progress.textContent = 'Проверяем, прошла ли регистрация...'; status.style.display = 'none'; try { await completeRegistrationLogin({ navigate, keyBundle }); @@ -378,7 +409,7 @@ function renderSolanaDoneStage({ navigate, status, keyBundle }) { stopAutoLogin(); } catch (error) { if (!loginCompleted) { - progress.textContent = 'Ждём обновления записи и скоро попробуем снова...'; + progress.textContent = 'Пока ещё не прошла, подождите ещё секундочку...'; console.warn('Auto login after registration is not ready yet', toUserMessage(error, 'auto-login-wait')); } } finally { @@ -386,14 +417,18 @@ function renderSolanaDoneStage({ navigate, status, keyBundle }) { } }; - backBtn.addEventListener('click', () => { - loginCompleted = true; - stopAutoLogin(); - navigate('start-view'); - }); + if (headerBackButton) { + const replacement = headerBackButton.cloneNode(true); + replacement.addEventListener('click', () => { + loginCompleted = true; + stopAutoLogin(); + navigate('start-view'); + }); + headerBackButton.replaceWith(replacement); + } card.innerHTML = ''; - card.append(title, hint, progress, altHint, backBtn, status); + card.append(title, hint, txIdLine, progress, status); initialDelayId = window.setTimeout(() => { if (loginCompleted) return; diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index 8caa4e9e..340f5b33 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -755,6 +755,39 @@ font-size: 13px; } +.registration-finish-card { + width: 100%; + max-width: 100%; + overflow: hidden; +} + +.registration-finish-title { + margin: 0; + font-size: clamp(1.7rem, 6vw, 2.15rem); + line-height: 1.18; + text-wrap: balance; +} + +.registration-finish-text, +.registration-finish-tx, +.registration-finish-progress { + max-width: 100%; + overflow-wrap: anywhere; + word-break: break-word; +} + +.registration-finish-tx-link { + color: #9fd8ff; + text-decoration: underline; + overflow-wrap: anywhere; + word-break: break-all; +} + +.registration-finish-tx-link:hover, +.registration-finish-tx-link:focus-visible { + color: #c9ebff; +} + .session-status { color: var(--text-muted); font-size: 13px; From e4dfb43b5e7f0d40c21c09c32b9ca652c40ca06a17f739d926d0193e3d3e47d3 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Thu, 16 Jul 2026 19:19:59 +0400 Subject: [PATCH 2/5] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20server2=20deploy-=D1=81?= =?UTF-8?q?=D0=BA=D1=80=D0=B8=D0=BF=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.properties | 4 ++-- build.gradle | 2 +- ...server_test2.sh => deploy_shine-server_server2shineupme.sh | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename deploy_shine-server_test2.sh => deploy_shine-server_server2shineupme.sh (100%) diff --git a/VERSION.properties b/VERSION.properties index f0ad64a2..9b482720 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.325 -server.version=1.2.297 +client.version=1.2.326 +server.version=1.2.298 diff --git a/build.gradle b/build.gradle index 7d9532eb..4dcf6640 100644 --- a/build.gradle +++ b/build.gradle @@ -216,7 +216,7 @@ tasks.register('deployServer', Exec) { dependsOn shadowJar workingDir = rootDir environment 'LOCAL_JAR', file('SHiNE-server/build/libs/shine-server.jar').absolutePath - commandLine 'bash', file('deploy_shine-server_test2.sh').absolutePath + commandLine 'bash', file('deploy_shine-server_server2shineupme.sh').absolutePath } tasks.register('deployUI', Exec) { diff --git a/deploy_shine-server_test2.sh b/deploy_shine-server_server2shineupme.sh similarity index 100% rename from deploy_shine-server_test2.sh rename to deploy_shine-server_server2shineupme.sh From 2890cdd45718c25fc8cf003794dfa03bde62d01cde47ac7f3affae155348f88f Mon Sep 17 00:00:00 2001 From: AidarKC Date: Thu, 16 Jul 2026 20:33:48 +0400 Subject: [PATCH 3/5] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20public=20Solana=20RPC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.properties | 4 +- ...16_1935_проверка_public_solana_rpc_в_ui.md | 23 ++ shine-UI/js/app.js | 2 + shine-UI/js/pages/developer-settings-view.js | 3 + shine-UI/js/pages/solana-rpc-check-view.js | 258 ++++++++++++++++++ shine-UI/styles/components.css | 53 ++++ 6 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 docs/Pending_Features/2026-07-16_1935_проверка_public_solana_rpc_в_ui.md create mode 100644 shine-UI/js/pages/solana-rpc-check-view.js diff --git a/VERSION.properties b/VERSION.properties index 9b482720..0271d5bc 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.326 -server.version=1.2.298 +client.version=1.2.327 +server.version=1.2.299 diff --git a/docs/Pending_Features/2026-07-16_1935_проверка_public_solana_rpc_в_ui.md b/docs/Pending_Features/2026-07-16_1935_проверка_public_solana_rpc_в_ui.md new file mode 100644 index 00000000..6008e2a6 --- /dev/null +++ b/docs/Pending_Features/2026-07-16_1935_проверка_public_solana_rpc_в_ui.md @@ -0,0 +1,23 @@ +# Экран проверки public Solana RPC в UI + +- статус: `pending` + +## Кратко + +Добавлен отдельный экран разработчика для проверки публичных mainnet Solana RPC прямо из браузера. +Экран отправляет реальный JSON-RPC `getVersion` запрос к списку public endpoint-ов и показывает, +какие ноды реально подходят для browser use. + +## Что проверять + +- в `Настройки разработчика` появилась кнопка `Solana: проверить public RPC`; +- экран открывается без ошибок; +- по каждой mainnet ноде показывается итоговый статус; +- для доступных нод видно HTTP-статус, время ответа и версию `solana-core`; +- длинные URL и ошибки не распирают экран по ширине на телефоне. + +## Ожидаемый результат + +- можно визуально увидеть, какие public Solana RPC доступны именно из браузера; +- проверка работает без промежуточного сервера; +- экран остаётся mobile-first и не требует горизонтального скролла. diff --git a/shine-UI/js/app.js b/shine-UI/js/app.js index 52e0b37d..f8e8a193 100644 --- a/shine-UI/js/app.js +++ b/shine-UI/js/app.js @@ -72,6 +72,7 @@ import * as languageView from './pages/language-view.js'; import * as appLogView from './pages/app-log-view.js'; import * as pwaDiagnosticsView from './pages/pwa-diagnostics-view.js'; import * as solanaUsersInitView from './pages/solana-users-init-view.js'; +import * as solanaRpcCheckView from './pages/solana-rpc-check-view.js'; import * as messagesList from './pages/messages-list.js'; import * as contactSearchView from './pages/contact-search-view.js'; import * as chatView from './pages/chat-view.js'; @@ -132,6 +133,7 @@ const routes = { 'app-log-view': appLogView, 'pwa-diagnostics-view': pwaDiagnosticsView, 'solana-users-init-view': solanaUsersInitView, + 'solana-rpc-check-view': solanaRpcCheckView, 'messages-list': messagesList, 'contact-search-view': contactSearchView, 'chat-view': chatView, diff --git a/shine-UI/js/pages/developer-settings-view.js b/shine-UI/js/pages/developer-settings-view.js index f8f90e6e..b570a909 100644 --- a/shine-UI/js/pages/developer-settings-view.js +++ b/shine-UI/js/pages/developer-settings-view.js @@ -264,6 +264,7 @@ export function render({ navigate }) { + @@ -278,9 +279,11 @@ export function render({ navigate }) { const forceUpdateHelpBtn = card.querySelector('#settings-force-update-help'); const uiErrorReportingBtn = card.querySelector('#settings-ui-error-reporting'); const solanaUsersInitBtn = card.querySelector('#settings-solana-users-init'); + const solanaRpcCheckBtn = card.querySelector('#settings-solana-rpc-check'); appLogBtn?.addEventListener('click', () => navigate('app-log-view')); solanaUsersInitBtn?.addEventListener('click', () => navigate('solana-users-init-view')); + solanaRpcCheckBtn?.addEventListener('click', () => navigate('solana-rpc-check-view')); diagnosticsBtn?.addEventListener('click', () => navigate('pwa-diagnostics-view')); uploadAvatarBtn?.addEventListener('click', () => { openDeveloperAvatarUploadModal({ diff --git a/shine-UI/js/pages/solana-rpc-check-view.js b/shine-UI/js/pages/solana-rpc-check-view.js new file mode 100644 index 00000000..1974acc9 --- /dev/null +++ b/shine-UI/js/pages/solana-rpc-check-view.js @@ -0,0 +1,258 @@ +import { renderHeader } from '../components/header.js'; + +export const pageMeta = { id: 'solana-rpc-check-view', title: 'Проверка Solana RPC' }; + +const MAINNET_RPC_ENDPOINTS = [ + { name: 'GetBlock', url: 'https://go.getblock.us/86aac42ad4484f3c813079afc201451c' }, + { name: 'PublicNode', url: 'https://solana-rpc.publicnode.com' }, + { name: 'BlockEden', url: 'https://api.blockeden.xyz/solana/KeCh6p22EX5AeRHxMSmc' }, + { name: 'dRPC', url: 'https://solana.drpc.org/' }, + { name: 'Grove', url: 'https://solana.rpc.grove.city/v1/01fdb492' }, + { name: 'LeoRPC', url: 'https://solana.leorpc.com/?api_key=FREE' }, + { name: 'OnFinality', url: 'https://solana.api.onfinality.io/public' }, + { name: 'Solana Foundation', url: 'https://api.mainnet-beta.solana.com' }, + { name: 'Solana Vibe Station', url: 'https://public.rpc.solanavibestation.com/' }, + { name: 'TheRPC', url: 'https://solana.therpc.io' }, +]; + +const RPC_PAYLOAD = { + jsonrpc: '2.0', + id: 1, + method: 'getVersion', + params: [], +}; + +function classifyResult(result) { + if (!result) return { label: 'Нет данных', tone: 'neutral' }; + if (result.ok) return { label: 'Работает в браузере', tone: 'ok' }; + if (result.httpStatus === 402) return { label: 'Требует платный план', tone: 'bad' }; + if (result.httpStatus === 403) return { label: 'Доступ запрещён', tone: 'bad' }; + if (result.httpStatus === 429) return { label: 'Упёрлась в rate limit', tone: 'warn' }; + if (result.httpStatus >= 500) return { label: 'Ошибка на стороне RPC', tone: 'bad' }; + if (result.errorCode === 35) return { label: 'Free tier не даёт mainnet', tone: 'bad' }; + if (result.networkError) return { label: 'Не проходит из браузера', tone: 'bad' }; + return { label: 'Отвечает, но не подходит', tone: 'warn' }; +} + +function shortenText(value, max = 140) { + const text = String(value || '').trim(); + if (!text) return '—'; + return text.length > max ? `${text.slice(0, max)}...` : text; +} + +async function testEndpoint(url, timeoutMs = 12000) { + const startedAt = performance.now(); + const controller = new AbortController(); + const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs); + try { + const response = await fetch(url, { + method: 'POST', + mode: 'cors', + cache: 'no-store', + credentials: 'omit', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(RPC_PAYLOAD), + signal: controller.signal, + }); + const elapsedMs = Math.round(performance.now() - startedAt); + let data = null; + let raw = ''; + try { + raw = await response.text(); + data = raw ? JSON.parse(raw) : null; + } catch { + data = null; + } + return { + url, + ok: response.ok && !!data?.result, + httpStatus: response.status, + elapsedMs, + result: data?.result || null, + errorCode: data?.error?.code ?? null, + errorMessage: data?.error?.message || '', + raw: shortenText(raw, 180), + networkError: '', + }; + } catch (error) { + const elapsedMs = Math.round(performance.now() - startedAt); + return { + url, + ok: false, + httpStatus: 0, + elapsedMs, + result: null, + errorCode: null, + errorMessage: '', + raw: '', + networkError: error?.name === 'AbortError' ? 'Timeout' : (error?.message || 'Network error'), + }; + } finally { + window.clearTimeout(timeoutId); + } +} + +function makeResultCard(endpoint) { + const card = document.createElement('article'); + card.className = 'card stack solana-rpc-card'; + + const head = document.createElement('div'); + head.className = 'solana-rpc-card-head'; + + const title = document.createElement('strong'); + title.textContent = endpoint.name; + + const badge = document.createElement('span'); + badge.className = 'badge alt solana-rpc-badge'; + badge.textContent = 'Ожидает проверки'; + + head.append(title, badge); + + const endpointLine = document.createElement('a'); + endpointLine.className = 'solana-rpc-url'; + endpointLine.href = endpoint.url; + endpointLine.target = '_blank'; + endpointLine.rel = 'noopener noreferrer'; + endpointLine.textContent = endpoint.url; + + const meta = document.createElement('div'); + meta.className = 'solana-rpc-meta'; + + const statusLine = document.createElement('div'); + statusLine.className = 'meta-muted'; + statusLine.textContent = 'Ещё не проверяли.'; + + const details = document.createElement('div'); + details.className = 'meta-muted solana-rpc-details'; + details.textContent = '—'; + + meta.append(statusLine, details); + card.append(head, endpointLine, meta); + + return { card, badge, statusLine, details }; +} + +export function render({ navigate }) { + const screen = document.createElement('section'); + screen.className = 'stack'; + + const intro = document.createElement('div'); + intro.className = 'card stack'; + intro.innerHTML = ` + Проверка mainnet RPC из браузера +
+ `; + + const controls = document.createElement('div'); + controls.className = 'auth-footer-actions'; + + const runBtn = document.createElement('button'); + runBtn.className = 'primary-btn'; + runBtn.type = 'button'; + runBtn.textContent = 'Проверить все ноды'; + + const resetBtn = document.createElement('button'); + resetBtn.className = 'ghost-btn'; + resetBtn.type = 'button'; + resetBtn.textContent = 'Сбросить'; + + controls.append(runBtn, resetBtn); + intro.append(controls); + + const summary = document.createElement('p'); + summary.className = 'status-line'; + summary.textContent = 'Пока проверки не запускались.'; + + const grid = document.createElement('div'); + grid.className = 'stack'; + + const cards = MAINNET_RPC_ENDPOINTS.map((endpoint) => { + const ui = makeResultCard(endpoint); + grid.append(ui.card); + return { endpoint, ui }; + }); + + let runToken = 0; + + const resetState = () => { + runToken += 1; + summary.className = 'status-line'; + summary.textContent = 'Пока проверки не запускались.'; + cards.forEach(({ ui }) => { + ui.badge.className = 'badge alt solana-rpc-badge'; + ui.badge.textContent = 'Ожидает проверки'; + ui.statusLine.textContent = 'Ещё не проверяли.'; + ui.details.textContent = '—'; + }); + }; + + const runChecks = async () => { + const token = ++runToken; + runBtn.disabled = true; + resetBtn.disabled = true; + summary.className = 'status-line'; + summary.textContent = 'Проверяем mainnet RPC из браузера...'; + + let okCount = 0; + let warnCount = 0; + let badCount = 0; + + const tasks = cards.map(async ({ endpoint, ui }) => { + ui.badge.className = 'badge alt solana-rpc-badge'; + ui.badge.textContent = 'Проверяем...'; + ui.statusLine.textContent = 'Отправляем JSON-RPC запрос getVersion...'; + ui.details.textContent = 'Ждём ответ.'; + + const result = await testEndpoint(endpoint.url); + if (token !== runToken) return; + + const verdict = classifyResult(result); + if (verdict.tone === 'ok') okCount += 1; + else if (verdict.tone === 'warn') warnCount += 1; + else if (verdict.tone === 'bad') badCount += 1; + + ui.badge.className = `badge solana-rpc-badge is-${verdict.tone}`; + ui.badge.textContent = verdict.label; + ui.statusLine.textContent = `HTTP ${result.httpStatus || 'ERR'} • ${result.elapsedMs} мс`; + if (result.ok) { + ui.details.textContent = `solana-core: ${result.result?.['solana-core'] || 'unknown'} • feature-set: ${result.result?.['feature-set'] || 'unknown'}`; + } else if (result.networkError) { + ui.details.textContent = `Browser error: ${result.networkError}`; + } else if (result.errorMessage) { + ui.details.textContent = shortenText(result.errorMessage, 180); + } else { + ui.details.textContent = result.raw || 'Нода ответила без пригодного JSON-RPC result.'; + } + }); + + await Promise.all(tasks); + if (token !== runToken) return; + + summary.className = 'status-line is-available'; + summary.textContent = `Готово. Для браузера подходят: ${okCount}. С оговорками: ${warnCount}. Не подходят: ${badCount}.`; + runBtn.disabled = false; + resetBtn.disabled = false; + }; + + runBtn.addEventListener('click', () => { + void runChecks(); + }); + resetBtn.addEventListener('click', resetState); + + screen.append( + renderHeader({ + title: 'Проверка Solana RPC', + leftAction: { label: '←', onClick: () => navigate('developer-settings-view') }, + }), + intro, + summary, + grid, + ); + + void runChecks(); + + return screen; +} diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index 340f5b33..b318ef7f 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -1300,6 +1300,59 @@ margin-top: 4px; } +.solana-rpc-card { + width: min(100%, 420px); + justify-self: center; +} + +.solana-rpc-card-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; +} + +.solana-rpc-badge { + flex: 0 0 auto; + text-align: center; +} + +.solana-rpc-badge.is-ok { + border-color: rgba(132, 244, 161, 0.42); + background: rgba(132, 244, 161, 0.12); + color: #d7ffe3; +} + +.solana-rpc-badge.is-warn { + border-color: rgba(242, 210, 129, 0.42); + background: rgba(242, 210, 129, 0.12); + color: #fff0c7; +} + +.solana-rpc-badge.is-bad { + border-color: rgba(255, 113, 143, 0.42); + background: rgba(255, 113, 143, 0.12); + color: #ffd7df; +} + +.solana-rpc-url { + color: #9fd8ff; + text-decoration: underline; + overflow-wrap: anywhere; + word-break: break-all; + font-size: 13px; +} + +.solana-rpc-meta { + display: grid; + gap: 6px; +} + +.solana-rpc-details { + overflow-wrap: anywhere; + word-break: break-word; +} + .settings-developer-panel { margin-top: 2px; padding-top: 8px; From daa516babeee4faed411c13979f1bf3ba6a8fd9d5f91885d6e6c1020883b4f23 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Fri, 17 Jul 2026 18:31:05 +0400 Subject: [PATCH 4/5] =?UTF-8?q?=D0=A0=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F:=20=D0=BE=D0=B6=D0=B8=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=82=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D1=8F=20Solana?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.properties | 4 +- build.gradle | 2 +- ...ие_подтверждения_solana_при_регистрации.md | 20 ++ shine-UI/js/pages/registration-keys-view.js | 3 + .../js/pages/registration-payment-view.js | 321 ++++++++++++------ shine-UI/styles/components.css | 6 +- 6 files changed, 251 insertions(+), 105 deletions(-) create mode 100644 docs/Pending_Features/2026-07-16_2035_ожидание_подтверждения_solana_при_регистрации.md diff --git a/VERSION.properties b/VERSION.properties index 0271d5bc..7d99cbe7 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.327 -server.version=1.2.299 +client.version=1.2.329 +server.version=1.2.301 diff --git a/build.gradle b/build.gradle index 4dcf6640..3c786e10 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'application' - id 'com.github.johnrengelman.shadow' version '8.1.1' +проверь ещё id 'com.github.johnrengelman.shadow' version '8.1.1' } def appVersionProps = new Properties() diff --git a/docs/Pending_Features/2026-07-16_2035_ожидание_подтверждения_solana_при_регистрации.md b/docs/Pending_Features/2026-07-16_2035_ожидание_подтверждения_solana_при_регистрации.md new file mode 100644 index 00000000..d546b671 --- /dev/null +++ b/docs/Pending_Features/2026-07-16_2035_ожидание_подтверждения_solana_при_регистрации.md @@ -0,0 +1,20 @@ +## Кратко +- Переделан финальный flow регистрации: сначала экран ожидания подтверждения Solana с прогрессом и повторными проверками, потом отдельный экран успешной регистрации с кнопкой входа в стандартный экран сохранения ключей. + +## Что проверять +- После отправки регистрации открывается экран ожидания с текстом про подтверждение Solana и кликабельным `Tx ID`. +- Прогресс-бар сначала идёт быстрее, потом заметно замедляется. +- Первая проверка регистрации начинается примерно через 4 секунды, далее повторяется раз в 2 секунды. +- Пока подтверждения нет, снизу показываются понятные статусы ожидания. +- После подтверждения открывается экран «Поздравляем с регистрацией» с одной кнопкой `Войти в аккаунт`. +- Кнопка `Войти в аккаунт` открывает штатный экран сохранения ключей с тремя галочками. +- Стрелка назад в левом верхнем углу на обоих экранах возвращает в главное меню. +- После успешного сохранения ключей регистрационный черновик и адрес кошелька очищаются. + +## Ожидаемый результат +- Пользователь не видит преждевременное поздравление до подтверждения регистрации в Solana. +- Финальный успех показывается только после появления подтверждения регистрации. +- Вход после регистрации идёт через тот же стандартный сценарий сохранения ключей, что и в обычном login-flow. + +## Статус +- pending diff --git a/shine-UI/js/pages/registration-keys-view.js b/shine-UI/js/pages/registration-keys-view.js index f66f6973..7189a89f 100644 --- a/shine-UI/js/pages/registration-keys-view.js +++ b/shine-UI/js/pages/registration-keys-view.js @@ -162,6 +162,9 @@ export function render({ navigate }) { state.registrationDraft.sessionId = ''; state.registrationDraft.pendingKeyBundle = null; state.registrationDraft.pendingSessionMaterial = null; + state.registrationDraft.preGeneratedKeyBundle = null; + state.registrationPayment.walletAddress = ''; + state.registrationPayment.balanceSOL = '0.0000'; await refreshSessions(); setAuthInfo(isLoginFlow diff --git a/shine-UI/js/pages/registration-payment-view.js b/shine-UI/js/pages/registration-payment-view.js index 02ccdb0b..31b6fd8c 100644 --- a/shine-UI/js/pages/registration-payment-view.js +++ b/shine-UI/js/pages/registration-payment-view.js @@ -1,8 +1,6 @@ import { renderHeader } from '../components/header.js'; import { authService, - authorizeSession, - refreshSessions, setAuthError, setAuthInfo, state, @@ -16,6 +14,7 @@ import { } from '../services/solana-wallet-service.js'; import { loadSolanaWeb3 } from '../vendor/solana-web3-loader.js'; import { + checkLoginExistsOnSolana, formatSolanaErrorDetails, isUserAlreadyExistsSolanaError, registerUserOnSolana, @@ -24,9 +23,11 @@ import { defaultServerLogin } from '../deploy-config.js'; export const pageMeta = { id: 'registration-payment-view', title: 'Оплата регистрации', showAppChrome: false }; const MIN_REQUIRED_SOL = 0.01; -const AUTO_LOGIN_INITIAL_DELAY_MS = 10000; -const AUTO_LOGIN_RETRY_MS = 2000; -const EMPTY_PASSWORD_WORDS = Array.from({ length: 12 }, () => ''); +const REGISTRATION_PROGRESS_DURATION_MS = 12000; +const REGISTRATION_POLL_START_DELAY_MS = 4000; +const REGISTRATION_POLL_INTERVAL_MS = 2000; +const REGISTRATION_CONFIRM_TIMEOUT_MS = 25000; +const REGISTRATION_SUCCESS_SETTLE_DELAY_MS = 2000; function getExplorerClusterName(endpoint) { const source = String(endpoint || '').trim().toLowerCase(); @@ -60,70 +61,23 @@ function getCryptoRuntimeState() { return { hasCrypto, hasGetRandomValues, hasSubtle, secureContext }; } -async function completeRegistrationLogin({ navigate, keyBundle }) { +function clamp(value, min, max) { + return Math.max(min, Math.min(max, value)); +} + +async function prepareRegistrationLoginForKeyStorage({ keyBundle }) { await authService.reconnect(state.entrySettings.shineServer); const result = await authService.createSessionForExistingUser( state.registrationDraft.login, state.registrationDraft.password, ); - - state.keyStorage.saveRoot = Boolean(state.keyStorage.saveRoot); - state.keyStorage.saveBlockchain = Boolean(state.keyStorage.saveBlockchain); - - await authService.persistSelectedKeys( - result.login, - result.storagePwd, - keyBundle, - { - saveRoot: state.keyStorage.saveRoot, - saveBlockchain: state.keyStorage.saveBlockchain, - }, - ); - await authService.persistSessionMaterial(result.login, result.sessionMaterial); - await clearStoredMessages().catch(() => {}); - - const resumed = await authService.resumeSession(result.login, result.sessionId); - const resumedLogin = resumed.login || result.login; - const resumedSessionId = resumed.sessionId || result.sessionId; - const resumedStoragePwd = resumed.storagePwd || result.storagePwd; - - authorizeSession({ - login: resumedLogin, - sessionId: resumedSessionId, - storagePwd: resumedStoragePwd, - }); - - state.loginDraft.login = resumedLogin; - state.loginDraft.password = ''; - state.loginDraft.passwordMode = 'single'; - state.loginDraft.passwordWords = EMPTY_PASSWORD_WORDS.slice(); - - state.registrationDraft.flowType = ''; - state.registrationDraft.password = ''; - state.registrationDraft.passwordMode = 'single'; - state.registrationDraft.passwordWords = EMPTY_PASSWORD_WORDS.slice(); - state.registrationDraft.passwordWordsLinked = false; - state.registrationDraft.usePromoCode = false; - state.registrationDraft.promoCode = ''; - state.registrationDraft.storagePwd = ''; - state.registrationDraft.sessionId = ''; - state.registrationDraft.pendingKeyBundle = null; - state.registrationDraft.pendingSessionMaterial = null; - state.registrationDraft.preGeneratedKeyBundle = null; - - state.registrationPayment.walletAddress = ''; - state.registrationPayment.balanceSOL = '0.0000'; - - await refreshSessions(); - setAuthInfo(`Регистрация завершена. Вы автоматически вошли как @${resumedLogin}.`); - - const nextHash = String(state.authReturnHash || '').trim(); - state.authReturnHash = ''; - if (nextHash.startsWith('/')) { - navigate(nextHash.slice(1)); - } else { - navigate('profile-view'); - } + state.registrationDraft.flowType = 'register'; + state.registrationDraft.login = result.login; + state.registrationDraft.sessionId = result.sessionId; + state.registrationDraft.storagePwd = result.storagePwd; + state.registrationDraft.pendingKeyBundle = keyBundle || result.keyBundle; + state.registrationDraft.pendingSessionMaterial = result.sessionMaterial; + return result; } export function render({ navigate }) { @@ -299,7 +253,7 @@ export function render({ navigate }) { } } - renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId }); + renderSolanaRegistrationStage({ navigate, status, keyBundle, registrationTxId }); } catch (error) { const message = toUserMessage(error, 'Не удалось завершить регистрацию.'); setAuthError(message); @@ -345,7 +299,7 @@ export function render({ navigate }) { return screen; } -function renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId = '' }) { +function renderSolanaRegistrationStage({ navigate, status, keyBundle, registrationTxId = '' }) { const screen = document.querySelector('section.stack'); if (!screen) return; const headerBackButton = screen.querySelector('.page-header .header-left .icon-btn'); @@ -353,19 +307,24 @@ function renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId = if (!card) return; card.classList.add('registration-finish-card'); - let initialDelayId = null; - let retryTimerId = null; - let loginInFlight = false; - let loginCompleted = false; + let progressTimerId = null; + let pollStartTimerId = null; + let pollTimerId = null; + let successTimerId = null; + let confirmationInFlight = false; + let stageClosed = false; + let successPending = false; + let timeoutShown = false; + const stageStartedAt = Date.now(); const txExplorerUrl = makeSolanaExplorerTxUrl(registrationTxId, state.entrySettings.solanaServer); const title = document.createElement('h2'); title.className = 'registration-finish-title'; - title.textContent = 'Поздравляем, регистрация завершена'; + title.textContent = 'Идёт регистрация в блокчейне...'; const hint = document.createElement('p'); hint.className = 'auth-copy registration-finish-text'; - hint.textContent = 'Подождите 10 секунд, пока обновится транзакция вашей регистрации в блокчейне Solana. После этого вход в аккаунт произойдёт автоматически.'; + hint.textContent = 'Ждём, пока транзакция будет полностью одобрена сетью Solana.'; const txIdLine = document.createElement('p'); txIdLine.className = 'meta-muted registration-finish-tx'; @@ -383,58 +342,218 @@ function renderSolanaDoneStage({ navigate, status, keyBundle, registrationTxId = txIdLine.textContent = 'Tx ID регистрации: ожидаем подтверждённую подпись'; } + const progressWrap = document.createElement('div'); + progressWrap.className = 'registration-progress'; + + const progressBar = document.createElement('div'); + progressBar.className = 'registration-progress-bar'; + progressWrap.append(progressBar); + const progress = document.createElement('p'); progress.className = 'meta-muted registration-finish-progress'; - progress.textContent = 'Подготавливаем проверку регистрации...'; + progress.textContent = 'Подготавливаем проверку подтверждения...'; - const stopAutoLogin = () => { - if (initialDelayId) { - window.clearTimeout(initialDelayId); - initialDelayId = null; + const pollStatus = document.createElement('p'); + pollStatus.className = 'meta-muted registration-finish-progress'; + pollStatus.textContent = 'Через несколько секунд начнём проверять подтверждение регистрации.'; + + const stopTimers = () => { + if (progressTimerId) { + window.clearInterval(progressTimerId); + progressTimerId = null; } - if (retryTimerId) { - window.clearInterval(retryTimerId); - retryTimerId = null; + if (pollStartTimerId) { + window.clearTimeout(pollStartTimerId); + pollStartTimerId = null; + } + if (pollTimerId) { + window.clearInterval(pollTimerId); + pollTimerId = null; + } + if (successTimerId) { + window.clearTimeout(successTimerId); + successTimerId = null; } }; - const tryAutoLogin = async () => { - if (loginCompleted || loginInFlight) return; - loginInFlight = true; - progress.textContent = 'Проверяем, прошла ли регистрация...'; + const updateVisualProgress = () => { + const elapsed = Date.now() - stageStartedAt; + let widthPercent = 0; + if (elapsed <= REGISTRATION_PROGRESS_DURATION_MS) { + const phaseRatio = clamp(elapsed / REGISTRATION_PROGRESS_DURATION_MS, 0, 1); + widthPercent = 100 * (1 - ((1 - phaseRatio) ** 1.85)); + } else { + const extraRatio = 1 - Math.exp(-(elapsed - REGISTRATION_PROGRESS_DURATION_MS) / 9000); + widthPercent = 88 + (10 * clamp(extraRatio, 0, 1)); + } + progressBar.style.width = `${clamp(widthPercent, 0, 98)}%`; + + if (elapsed < REGISTRATION_POLL_START_DELAY_MS) { + progress.textContent = 'Отправили регистрацию в сеть. Даём транзакции несколько секунд на обработку.'; + return; + } + if (elapsed < REGISTRATION_CONFIRM_TIMEOUT_MS) { + progress.textContent = 'Идёт ожидание подтверждения Solana. Индикатор может замедлиться, это нормально.'; + return; + } + progress.textContent = 'Подтверждение затянулось. Продолжаем автоматическую проверку.'; + }; + + const finalizeSuccess = () => { + if (stageClosed || successPending) return; + successPending = true; + stopTimers(); + progress.textContent = 'Подтверждение получено. Завершаем регистрацию...'; + pollStatus.textContent = 'Ещё секунду: запись уже найдена в Solana, синхронизируем финальный экран.'; status.style.display = 'none'; + progressBar.style.transition = `width ${REGISTRATION_SUCCESS_SETTLE_DELAY_MS}ms ease-out`; + progressBar.style.width = '100%'; + successTimerId = window.setTimeout(() => { + if (stageClosed) return; + stageClosed = true; + renderRegistrationSuccessStage({ navigate, status, keyBundle, registrationTxId }); + }, REGISTRATION_SUCCESS_SETTLE_DELAY_MS); + }; + + const showTimeoutState = () => { + if (timeoutShown || stageClosed) return; + timeoutShown = true; + status.className = 'status-line is-unavailable'; + status.textContent = 'Подтверждение не пришло вовремя. Возможно, регистрация уже прошла, а возможно ещё нет. Проверьте Tx ID ниже: мы продолжим автоматическую проверку.'; + status.style.display = ''; + pollStatus.textContent = 'Пока подтверждения нет. Подождите ещё секундочку, мы продолжаем проверять регистрацию.'; + }; + + const tryCheckRegistration = async () => { + if (stageClosed || confirmationInFlight) return; + confirmationInFlight = true; + progress.textContent = 'Проверяем подтверждение регистрации в Solana...'; + status.style.display = timeoutShown ? '' : 'none'; try { - await completeRegistrationLogin({ navigate, keyBundle }); - loginCompleted = true; - stopAutoLogin(); + const result = await checkLoginExistsOnSolana({ + login: state.registrationDraft.login, + solanaEndpoint: state.entrySettings.solanaServer, + }); + if (result?.exists) { + finalizeSuccess(); + return; + } + pollStatus.textContent = 'Пока ещё не прошла, подождите ещё секундочку...'; + if ((Date.now() - stageStartedAt) >= REGISTRATION_CONFIRM_TIMEOUT_MS) { + showTimeoutState(); + } } catch (error) { - if (!loginCompleted) { - progress.textContent = 'Пока ещё не прошла, подождите ещё секундочку...'; - console.warn('Auto login after registration is not ready yet', toUserMessage(error, 'auto-login-wait')); + console.warn('Registration confirmation check failed', toUserMessage(error, 'registration-confirmation')); + pollStatus.textContent = 'Проверяем регистрацию повторно...'; + if ((Date.now() - stageStartedAt) >= REGISTRATION_CONFIRM_TIMEOUT_MS) { + showTimeoutState(); } } finally { - loginInFlight = false; + confirmationInFlight = false; } }; if (headerBackButton) { const replacement = headerBackButton.cloneNode(true); replacement.addEventListener('click', () => { - loginCompleted = true; - stopAutoLogin(); + stageClosed = true; + stopTimers(); navigate('start-view'); }); headerBackButton.replaceWith(replacement); } card.innerHTML = ''; - card.append(title, hint, txIdLine, progress, status); + status.style.display = 'none'; + card.append(title, hint, txIdLine, progressWrap, progress, pollStatus, status); - initialDelayId = window.setTimeout(() => { - if (loginCompleted) return; - void tryAutoLogin(); - retryTimerId = window.setInterval(() => { - void tryAutoLogin(); - }, AUTO_LOGIN_RETRY_MS); - }, AUTO_LOGIN_INITIAL_DELAY_MS); + updateVisualProgress(); + progressTimerId = window.setInterval(() => { + if (stageClosed) return; + updateVisualProgress(); + if ((Date.now() - stageStartedAt) >= REGISTRATION_CONFIRM_TIMEOUT_MS) { + showTimeoutState(); + } + }, 160); + + pollStartTimerId = window.setTimeout(() => { + if (stageClosed) return; + void tryCheckRegistration(); + pollTimerId = window.setInterval(() => { + void tryCheckRegistration(); + }, REGISTRATION_POLL_INTERVAL_MS); + }, REGISTRATION_POLL_START_DELAY_MS); +} + +function renderRegistrationSuccessStage({ navigate, status, keyBundle, registrationTxId = '' }) { + const screen = document.querySelector('section.stack'); + if (!screen) return; + const headerBackButton = screen.querySelector('.page-header .header-left .icon-btn'); + const card = screen.querySelector('.card.stack'); + if (!card) return; + + const txExplorerUrl = makeSolanaExplorerTxUrl(registrationTxId, state.entrySettings.solanaServer); + let loginInFlight = false; + + const title = document.createElement('h2'); + title.className = 'registration-finish-title'; + title.textContent = 'Поздравляем с регистрацией'; + + const hint = document.createElement('p'); + hint.className = 'auth-copy registration-finish-text'; + hint.textContent = 'Регистрация подтверждена в блокчейне Solana. Теперь можно войти в аккаунт и выбрать, какие ключи сохранить на устройстве.'; + + const txIdLine = document.createElement('p'); + txIdLine.className = 'meta-muted registration-finish-tx'; + if (registrationTxId && txExplorerUrl) { + const txLabel = document.createElement('span'); + txLabel.textContent = 'Tx ID регистрации: '; + const txLink = document.createElement('a'); + txLink.className = 'registration-finish-tx-link'; + txLink.href = txExplorerUrl; + txLink.target = '_blank'; + txLink.rel = 'noopener noreferrer'; + txLink.textContent = registrationTxId; + txIdLine.append(txLabel, txLink); + } else { + txIdLine.textContent = 'Регистрация подтверждена. Tx ID недоступен.'; + } + + const enterButton = document.createElement('button'); + enterButton.className = 'primary-btn'; + enterButton.type = 'button'; + enterButton.textContent = 'Войти в аккаунт'; + enterButton.addEventListener('click', async () => { + if (loginInFlight) return; + loginInFlight = true; + status.className = 'status-line'; + status.textContent = 'Готовим вход и экран сохранения ключей...'; + status.style.display = ''; + enterButton.disabled = true; + try { + await prepareRegistrationLoginForKeyStorage({ keyBundle }); + await clearStoredMessages().catch(() => {}); + setAuthInfo(`Регистрация подтверждена для @${state.registrationDraft.login}. Выберите, какие ключи сохранить на устройстве.`); + navigate('registration-keys-view'); + } catch (error) { + const message = toUserMessage(error, 'Не удалось подготовить вход после регистрации.'); + setAuthError(message); + status.className = 'status-line is-unavailable'; + status.textContent = message; + status.style.display = ''; + } finally { + loginInFlight = false; + enterButton.disabled = false; + } + }); + + if (headerBackButton) { + const replacement = headerBackButton.cloneNode(true); + replacement.addEventListener('click', () => navigate('start-view')); + headerBackButton.replaceWith(replacement); + } + + card.innerHTML = ''; + status.style.display = 'none'; + card.append(title, hint, txIdLine, enterButton, status); } diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index b318ef7f..c6a9c88c 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -763,7 +763,7 @@ .registration-finish-title { margin: 0; - font-size: clamp(1.7rem, 6vw, 2.15rem); + font-size: clamp(1.9rem, 7vw, 2.45rem); line-height: 1.18; text-wrap: balance; } @@ -776,6 +776,10 @@ word-break: break-word; } +.registration-finish-card .primary-btn { + width: 100%; +} + .registration-finish-tx-link { color: #9fd8ff; text-decoration: underline; From 8208bb0b9d9813120fccaafb34bc95f8a266c34cb9b60c7ff3a69d0e38dc929e Mon Sep 17 00:00:00 2001 From: AidarKC Date: Mon, 20 Jul 2026 11:29:03 +0400 Subject: [PATCH 5/5] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=81=D1=82?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8E=20=D0=B8=20TODO=20=D0=BA=20production-=D1=81?= =?UTF-8?q?=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 38 +- ...-05-30_agent_instructions_approval_task.md | 2 +- SHiNE-agent-bot-coder/AGENT.md | 2 +- SHiNE-server/AGENTS.md | 4 +- .../blockchain/Net_AddBlock_Handler.java | 2 +- TASKS/01-07.04.26-каналы-api/01-ТЗ-каналы.md | 220 --- .../01-кратко-для-внешних.md | 25 - .../01-ТЗ-web-push-direct-message.md | 153 -- ...06-26_1800_корректное_завершение_за_30с.md | 2 +- ...-06-26_1810_подключение_устройств_по_qr.md | 2 +- TODO/README.md | 20 +- ...er_technical_commands_and_file_transfer.md | 6 +- ...6-05-24_1140_репосты_в_каналах_и_тредах.md | 10 +- .../2026-05-25_1106_shine_balance_wallet.md | 8 +- .../2026-05-26_0029_esp32s3_file_storage.md | 8 +- .../2026-06-02_сессионные_homeserver_в_pda.md | 10 +- ...3_подключение_других_устройств_через_qr.md | 2 +- ...-05-25_1106_wallet_topup_solana_arweave.md | 4 +- ...ановить_полную_логику_shine_login_guard.md | 4 +- ...брать_временную_очистку_signed_messages.md | 6 +- ...26-06-26_1805_межсерверный_ws_и_dm_sync.md | 12 +- TODO/Децентрализация/README.md | 16 + .../realtime_pda_solana_sync.md | 30 + {docs => TODO/Децентрализация}/ИТХ/README.md | 0 .../ИТХ/Спецификация_ИТХ_v1.md | 0 .../запись_блокчейнов_в_arweave.md | 29 + .../межсерверная_передача_сообщений.md | 30 + TODO/Децентрализация/межсерверные_звонки.md | 29 + .../односерверный_production_режим.md | 23 + .../01_Презентация_для_людей.md | 275 +++ .../02_ТЗ_новых_типов_блоков.md | 670 ++++++++ VERSION.properties | 4 +- docs/API/00_Common_API_Format.md | 2 +- docs/API/02_Authentication_API.md | 4 +- docs/API/12_Direct_Messages_Push_Calls_API.md | 4 +- .../00_Blockchain_Formats_and_Block_Types.md | 2 +- docs/Blockchain/CHANGELOG.md | 14 +- docs/Blockchain/sync-between-servers.md | 2 +- docs/Figma/TRANSFER_UI_SCREENS.md | 2 +- docs/Keys/DERIVATION.md | 6 +- docs/Keys/README.md | 18 +- docs/Personal_Messages/README.md | 8 +- docs/Personal_Messages/Протокол_DM_v1.md | 8 +- .../Спецификация_DM_v0.5_устаревшая.md | 4 +- docs/Personal_Messages/Формат_DM_v1.md | 2 +- docs/Solana/user_pda/README.md | 2 +- docs/Solana_Architecture/README.md | 4 +- .../ESP_Pairing_и_режимы_подключения.md | 2 +- .../codex_profile_toggle_20260324_194129.log | 1509 ----------------- .../features/interactive-network-graph.md | 183 -- shine-UI/docs/design/messages-list-v2.md | 74 - shine-UI/js/pages/channel-thread-view.js | 2 +- shine-UI/js/pages/channel-view.js | 2 +- .../formats/shine-user-pda-format-v.1.0.md | 2 +- .../shine/doc/shine_payments_e2e_test_plan.md | 2 +- 55 files changed, 1229 insertions(+), 2275 deletions(-) delete mode 100644 TASKS/01-07.04.26-каналы-api/01-ТЗ-каналы.md delete mode 100644 TASKS/01-07.04.26-каналы-api/01-кратко-для-внешних.md delete mode 100644 TASKS/02-12.04.26-web-push-direct-message/01-ТЗ-web-push-direct-message.md rename TODO/{ => Децентрализация}/2026-06-26_1805_межсерверный_ws_и_dm_sync.md (70%) create mode 100644 TODO/Децентрализация/README.md create mode 100644 TODO/Децентрализация/realtime_pda_solana_sync.md rename {docs => TODO/Децентрализация}/ИТХ/README.md (100%) rename {docs => TODO/Децентрализация}/ИТХ/Спецификация_ИТХ_v1.md (100%) create mode 100644 TODO/Децентрализация/запись_блокчейнов_в_arweave.md create mode 100644 TODO/Децентрализация/межсерверная_передача_сообщений.md create mode 100644 TODO/Децентрализация/межсерверные_звонки.md create mode 100644 TODO/Децентрализация/односерверный_production_режим.md create mode 100644 TODO/Новые фишки которые надо доделать/Новая_контентная_модель_блокчейна/01_Презентация_для_людей.md create mode 100644 TODO/Новые фишки которые надо доделать/Новая_контентная_модель_блокчейна/02_ТЗ_новых_типов_блоков.md delete mode 100644 shine-UI/.elaira_logs/codex_profile_toggle_20260324_194129.log delete mode 100644 shine-UI/Dev_Docs/features/interactive-network-graph.md delete mode 100644 shine-UI/docs/design/messages-list-v2.md diff --git a/AGENTS.md b/AGENTS.md index 2019fbc4..350f9226 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,43 +38,43 @@ - В git добавлять исходники, lock-файлы, настройки проекта и документацию Solana-модуля, но не добавлять локальные ключи, `.git`, `.idea`, `.gradle`, `target`, `node_modules`, `test-ledger`, логи, временные run-отчёты и `.env`-конфиги. - Для Solana deploy/push использовать правила из локального `shine-solana/shine/AGENTS.md`; не смешивать deploy Solana-модуля с `deployServer`/`deployUI` основного проекта. - Для регистрации пользователей в Solana (программа `shine_users`) единая актуальная инструкция по деплою/инициализации, адресам программ, и куда их прописывать в UI/сервере находится в: - - `Dev_Docs/Инициализация_Solana_регистрации/README.md` + - `docs/Инициализация_Solana_регистрации/README.md` - Этот файл считать основной справкой (single source of truth) по деплою и первичной инициализации Solana-регистрации в текущем проекте. - Актуальная архитектурная справка по устройству Solana-программ, PDA-счетам, ролям DAO и движению средств находится в: - - `Dev_Docs/Solana_Architecture/README.md` + - `docs/Solana_Architecture/README.md` - Документ формата пользовательской PDA-записи `shine_users` находится в: - `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md` ## Документация блокчейна -- Актуальная документация по форматам блокчейна находится в `Dev_Docs/Blockchain/README.md`. +- Актуальная документация по форматам блокчейна находится в `docs/Blockchain/README.md`. - Это точка входа (оглавление), рядом расположены детальные файлы по форматам, типам каналов и командным сообщениям. -- При любом изменении кода, связанного с блокчейном (формат блока, типы каналов, правила чтения/записи, команды), обязательно обновлять соответствующие документы в `Dev_Docs/Blockchain/`. -- Дополнительно обязательно вести `Dev_Docs/Blockchain/CHANGELOG.md`: дописывать изменения построчно с указанием даты/времени и хэша коммита, после которого внесено изменение. +- При любом изменении кода, связанного с блокчейном (формат блока, типы каналов, правила чтения/записи, команды), обязательно обновлять соответствующие документы в `docs/Blockchain/`. +- Дополнительно обязательно вести `docs/Blockchain/CHANGELOG.md`: дописывать изменения построчно с указанием даты/времени и хэша коммита, после которого внесено изменение. - Перед любым изменением формата блокчейна обязательно заранее предупреждать пользователя, что формат будет изменён. - Изменять формат блокчейна можно только после явного подтверждения пользователя (без подтверждения формат не менять). - Добавление любых данных в блокчейн выполнять только через операцию `AddBlock`. - Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока. ## Документация личных сообщений (DM) -- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`. -- Точный байтовый формат DM находится в `Dev_Docs/Personal_Messages/Формат_DM_v1.md`. +- Актуальная документация по логике личных сообщений находится в `docs/Personal_Messages/Протокол_DM_v1.md`. +- Точный байтовый формат DM находится в `docs/Personal_Messages/Формат_DM_v1.md`. - При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять оба документа: - - `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` - - `Dev_Docs/Personal_Messages/Формат_DM_v1.md` + - `docs/Personal_Messages/Протокол_DM_v1.md` + - `docs/Personal_Messages/Формат_DM_v1.md` - Логика личных сообщений в коде должна всегда соответствовать этим документам. - Документ по личным сообщениям обязан поддерживаться в актуальном состоянии. ## Документация API сервера -- Актуальная документация по публичному JSON/WebSocket API сервера находится в `Dev_Docs/API/`. -- При любом изменении серверного API/эндпоинтов/операций `op` обязательно обновлять соответствующие документы в `Dev_Docs/API/`. +- Актуальная документация по публичному JSON/WebSocket API сервера находится в `docs/API/`. +- При любом изменении серверного API/эндпоинтов/операций `op` обязательно обновлять соответствующие документы в `docs/API/`. - Перед изменением самого серверного API обязательно явно предупредить пользователя, какие операции, поля запросов/ответов или коды ошибок будут изменены, и запросить отдельное подтверждение. - Без явного подтверждения пользователя формат серверного API не менять; допускается только приведение документации в соответствие уже существующему коду. -- Если добавляется новая операция `op`, нужно обновить общий список операций в `Dev_Docs/API/09_Operations_Index.md` или создать его, если файла ещё нет. +- Если добавляется новая операция `op`, нужно обновить общий список операций в `docs/API/09_Operations_Index.md` или создать его, если файла ещё нет. ## Документация Figma -- Актуальная документация по переносу экранов SHiNE в Figma и обратному переносу из Figma в код находится в `Dev_Docs/Figma/`. -- Точка входа: `Dev_Docs/Figma/README.md`. -- Подробный рабочий регламент: `Dev_Docs/Figma/TRANSFER_UI_SCREENS.md`. +- Актуальная документация по переносу экранов SHiNE в Figma и обратному переносу из Figma в код находится в `docs/Figma/`. +- Точка входа: `docs/Figma/README.md`. +- Подробный рабочий регламент: `docs/Figma/TRANSFER_UI_SCREENS.md`. - Для экранов регистрации, входа и других чувствительных UI-flow по умолчанию переносить экраны в Figma по одному, а не пачкой, если пользователь отдельно не подтвердил иной способ. ## Версионирование @@ -124,8 +124,8 @@ - В этих записях искать поля `reason`, `failureStage`, `pcConnectionState`, `pcIceConnectionState`, `routeLabel`, `configuredTurnHosts*`, `reachableTurnHosts*`. ## Недопроверенные фичи (обязательно) -- Папка для учёта недопроверенных фич: `Dev_Docs/Pending_Features/`. -- По каждой новой доработке, которая требует ручной проверки, добавлять отдельный markdown-файл в `Dev_Docs/Pending_Features/`. +- Папка для учёта недопроверенных фич: `docs/Pending_Features/`. +- По каждой новой доработке, которая требует ручной проверки, добавлять отдельный markdown-файл в `docs/Pending_Features/`. - Рекомендуемый формат имени файла: `YYYY-MM-DD_HHMM_