feat(launcher): promote control surface and isolate advanced integration panel
This commit is contained in:
parent
06e48d7aa4
commit
9b989bd735
28
README.md
28
README.md
@ -21,17 +21,27 @@ Launcher never contains private kernel internals. It verifies and installs signe
|
||||
|
||||
## Local Harness (Current)
|
||||
|
||||
`app/index.html` is a local launcher shell harness for end-to-end API validation:
|
||||
`app/index.html` now exposes a product-first control surface with advanced harness tooling preserved.
|
||||
|
||||
1. Wallet intent + verify
|
||||
2. Membership quote + confirm
|
||||
3. Member channel register/poll/ack/support
|
||||
4. Governance install token/confirm/status
|
||||
5. Lease heartbeat + offline renew
|
||||
6. Injected wallet automation for intent signing and membership tx send
|
||||
7. One-click `Run membership flow` path (intent -> verify -> quote -> tx -> confirm)
|
||||
Top-level control surface:
|
||||
|
||||
Wallet automation shortcuts in the shell:
|
||||
1. `Connect wallet`
|
||||
2. `Activate membership`
|
||||
3. `Refresh status + feed`
|
||||
4. `Governance status`
|
||||
5. Wallet/membership/designation/last-sync overview cards
|
||||
6. Pull-first updates feed + support ticket action
|
||||
|
||||
Advanced integration controls (collapsible):
|
||||
|
||||
1. API/chain connection settings
|
||||
2. Wallet intent + verify primitives
|
||||
3. Membership quote + confirm primitives
|
||||
4. Member channel register/poll primitives
|
||||
5. Governance install + lease primitives
|
||||
6. Raw response log for deterministic troubleshooting
|
||||
|
||||
Wallet automation helpers remain available in advanced controls:
|
||||
|
||||
1. `Connect wallet` fills address from `window.ethereum`.
|
||||
2. `Sign intent (EIP-712)` signs the current intent payload and fills `walletSignature`.
|
||||
|
||||
87
app/app.js
87
app/app.js
@ -6,6 +6,7 @@ const state = {
|
||||
eventMap: new Map(),
|
||||
lastIntent: null,
|
||||
lastQuote: null,
|
||||
channelReady: false,
|
||||
};
|
||||
|
||||
function nowISO() {
|
||||
@ -52,6 +53,25 @@ function logLine(label, payload) {
|
||||
log.textContent = line + log.textContent;
|
||||
}
|
||||
|
||||
function setSummary(id, value) {
|
||||
const el = $(id);
|
||||
if (!el) return;
|
||||
el.textContent = value;
|
||||
}
|
||||
|
||||
function refreshOverview(statusPayload) {
|
||||
const currentWallet = wallet();
|
||||
setSummary("summaryWallet", currentWallet || "not connected");
|
||||
if (statusPayload && typeof statusPayload === "object") {
|
||||
setSummary("summaryMembership", statusPayload.status || "unknown");
|
||||
setSummary("summaryDesignation", statusPayload.designation_code || "-");
|
||||
setSummary("summaryLastSync", nowISO());
|
||||
return;
|
||||
}
|
||||
const designation = $("designationCode")?.value?.trim() || "-";
|
||||
setSummary("summaryDesignation", designation);
|
||||
}
|
||||
|
||||
function setFlowStatus(message) {
|
||||
const el = $("flowStatus");
|
||||
if (el) {
|
||||
@ -211,6 +231,7 @@ async function onConnectWallet() {
|
||||
provider_chain_id: providerChainID,
|
||||
});
|
||||
}
|
||||
refreshOverview();
|
||||
}
|
||||
|
||||
async function onSignIntent() {
|
||||
@ -317,6 +338,7 @@ async function onIntent() {
|
||||
$("designationCode").value = out.designation_code || "";
|
||||
$("displayToken").value = out.display_token || "";
|
||||
logLine("wallet intent", out);
|
||||
refreshOverview();
|
||||
}
|
||||
|
||||
async function onVerify() {
|
||||
@ -330,6 +352,7 @@ async function onVerify() {
|
||||
$("designationCode").value = out.designation_code;
|
||||
}
|
||||
logLine("wallet verify", out);
|
||||
refreshOverview();
|
||||
}
|
||||
|
||||
async function onStatus() {
|
||||
@ -341,6 +364,7 @@ async function onStatus() {
|
||||
$("designationCode").value = out.designation_code;
|
||||
}
|
||||
logLine("membership status", out);
|
||||
refreshOverview(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -469,7 +493,9 @@ async function onRegisterChannel() {
|
||||
app_version: $("appVersion").value.trim(),
|
||||
push_provider: "none",
|
||||
});
|
||||
state.channelReady = true;
|
||||
logLine("channel register", out);
|
||||
return out;
|
||||
}
|
||||
|
||||
async function onUnregisterChannel() {
|
||||
@ -477,7 +503,9 @@ async function onUnregisterChannel() {
|
||||
wallet: requireWallet(),
|
||||
device_id: deviceID(),
|
||||
});
|
||||
state.channelReady = false;
|
||||
logLine("channel unregister", out);
|
||||
return out;
|
||||
}
|
||||
|
||||
async function onPollEvents() {
|
||||
@ -497,6 +525,7 @@ async function onPollEvents() {
|
||||
}
|
||||
renderEvents(out.events || []);
|
||||
logLine("channel poll", out);
|
||||
return out;
|
||||
}
|
||||
|
||||
async function onSupportTicket() {
|
||||
@ -552,6 +581,53 @@ async function onInstallStatus() {
|
||||
});
|
||||
const out = await request("GET", `/governance/install/status?${query.toString()}`);
|
||||
logLine("install status", out);
|
||||
return out;
|
||||
}
|
||||
|
||||
async function ensureChannelBinding() {
|
||||
if (state.channelReady) {
|
||||
return;
|
||||
}
|
||||
await onRegisterChannel();
|
||||
}
|
||||
|
||||
async function onQuickConnect() {
|
||||
await onConnectWallet();
|
||||
try {
|
||||
await onStatus();
|
||||
} catch (err) {
|
||||
logLine("quick connect status warning", { error: String(err) });
|
||||
}
|
||||
}
|
||||
|
||||
async function onQuickActivate() {
|
||||
await onRunMembershipFlow();
|
||||
try {
|
||||
await ensureChannelBinding();
|
||||
await onPollEvents();
|
||||
} catch (err) {
|
||||
logLine("quick activate feed warning", { error: String(err) });
|
||||
}
|
||||
}
|
||||
|
||||
async function onQuickRefresh() {
|
||||
const status = await onStatus();
|
||||
if (String(status.status || "").toLowerCase() !== "active") {
|
||||
setFlowStatus("membership inactive");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await ensureChannelBinding();
|
||||
await onPollEvents();
|
||||
setFlowStatus("status synced");
|
||||
} catch (err) {
|
||||
setFlowStatus("feed sync warning");
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function onQuickInstallStatus() {
|
||||
await onInstallStatus();
|
||||
}
|
||||
|
||||
async function onLeaseHeartbeat() {
|
||||
@ -578,7 +654,11 @@ async function onOfflineRenew() {
|
||||
}
|
||||
|
||||
function bind(id, handler) {
|
||||
$(id).addEventListener("click", async () => {
|
||||
const el = $(id);
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
el.addEventListener("click", async () => {
|
||||
try {
|
||||
await handler();
|
||||
} catch (err) {
|
||||
@ -587,6 +667,10 @@ function bind(id, handler) {
|
||||
});
|
||||
}
|
||||
|
||||
bind("btnQuickConnect", onQuickConnect);
|
||||
bind("btnQuickActivate", onQuickActivate);
|
||||
bind("btnQuickRefresh", onQuickRefresh);
|
||||
bind("btnQuickInstallStatus", onQuickInstallStatus);
|
||||
bind("btnConnectWallet", onConnectWallet);
|
||||
bind("btnRunMembershipFlow", onRunMembershipFlow);
|
||||
bind("btnIntent", onIntent);
|
||||
@ -611,3 +695,4 @@ logLine("launcher shell ready", {
|
||||
api_base: baseURL(),
|
||||
chain_id: chainID(),
|
||||
});
|
||||
refreshOverview();
|
||||
|
||||
@ -3,17 +3,71 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>EDUT Launcher Shell</title>
|
||||
<title>EDUT Launcher</title>
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<header class="hero">
|
||||
<h1>EDUT Launcher</h1>
|
||||
<p>Wallet-first onboarding shell (local integration harness)</p>
|
||||
<p>Deterministic infrastructure control surface.</p>
|
||||
</header>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Control Surface</h2>
|
||||
<div class="actions">
|
||||
<button id="btnQuickConnect">Connect wallet</button>
|
||||
<button id="btnQuickActivate">Activate membership</button>
|
||||
<button id="btnQuickRefresh">Refresh status + feed</button>
|
||||
<button id="btnQuickInstallStatus">Governance status</button>
|
||||
</div>
|
||||
<p id="flowStatus" class="flow-status">ready</p>
|
||||
<div class="grid two">
|
||||
<article class="stat">
|
||||
<h3>Wallet</h3>
|
||||
<p id="summaryWallet">not connected</p>
|
||||
</article>
|
||||
<article class="stat">
|
||||
<h3>Membership</h3>
|
||||
<p id="summaryMembership">unknown</p>
|
||||
</article>
|
||||
<article class="stat">
|
||||
<h3>Designation</h3>
|
||||
<p id="summaryDesignation">-</p>
|
||||
</article>
|
||||
<article class="stat">
|
||||
<h3>Last Sync</h3>
|
||||
<p id="summaryLastSync">never</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Updates</h2>
|
||||
<p class="note">Communication is pull-first. No broadcast notifications.</p>
|
||||
<div id="eventList" class="event-list"></div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Support</h2>
|
||||
<label>
|
||||
Support summary (owner only)
|
||||
<input id="supportSummary" value="Need diagnostics snapshot." />
|
||||
</label>
|
||||
<div class="actions">
|
||||
<button id="btnSupportTicket">Open support ticket</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Response Log</h2>
|
||||
<pre id="log"></pre>
|
||||
</section>
|
||||
|
||||
<details class="panel advanced">
|
||||
<summary>Advanced Integration Controls</summary>
|
||||
<div class="advanced-body">
|
||||
<section class="subpanel">
|
||||
<h2>Connection</h2>
|
||||
<div class="grid two">
|
||||
<label>
|
||||
@ -27,8 +81,8 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Wallet Intent</h2>
|
||||
<section class="subpanel">
|
||||
<h2>Membership Flow Controls</h2>
|
||||
<div class="actions">
|
||||
<button id="btnRunMembershipFlow">Run membership flow</button>
|
||||
<button id="btnConnectWallet">Connect wallet</button>
|
||||
@ -37,7 +91,6 @@
|
||||
<button id="btnVerify">Verify signature</button>
|
||||
<button id="btnStatus">Membership status</button>
|
||||
</div>
|
||||
<p id="flowStatus" class="flow-status">flow idle</p>
|
||||
<div class="grid three">
|
||||
<label>
|
||||
Wallet
|
||||
@ -72,8 +125,8 @@
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Membership Quote + Confirm</h2>
|
||||
<section class="subpanel">
|
||||
<h2>Quote + Confirm</h2>
|
||||
<div class="actions">
|
||||
<button id="btnQuote">Get quote</button>
|
||||
<button id="btnSignPayerProof">Sign payer proof</button>
|
||||
@ -114,7 +167,7 @@
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<section class="subpanel">
|
||||
<h2>Member Channel</h2>
|
||||
<div class="grid three">
|
||||
<label>
|
||||
@ -151,10 +204,6 @@
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button id="btnRegisterChannel">Register channel</button>
|
||||
<button id="btnUnregisterChannel">Unregister channel</button>
|
||||
</div>
|
||||
<div class="grid two">
|
||||
<label>
|
||||
Cursor
|
||||
@ -166,19 +215,13 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button id="btnRegisterChannel">Register channel</button>
|
||||
<button id="btnUnregisterChannel">Unregister channel</button>
|
||||
<button id="btnPollEvents">Poll events</button>
|
||||
</div>
|
||||
<div id="eventList" class="event-list"></div>
|
||||
<label>
|
||||
Support summary (owner only)
|
||||
<input id="supportSummary" value="Need diagnostics snapshot." />
|
||||
</label>
|
||||
<div class="actions">
|
||||
<button id="btnSupportTicket">Open support ticket</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<section class="subpanel">
|
||||
<h2>Governance Install + Lease</h2>
|
||||
<div class="actions">
|
||||
<button id="btnInstallToken">Issue install token</button>
|
||||
@ -208,11 +251,8 @@
|
||||
<button id="btnOfflineRenew">Offline renew</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<h2>Response Log</h2>
|
||||
<pre id="log"></pre>
|
||||
</section>
|
||||
</div>
|
||||
</details>
|
||||
</main>
|
||||
<script src="./app.js"></script>
|
||||
</body>
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
--muted: #8fa2c2;
|
||||
--accent: #39c36b;
|
||||
--warn: #ffcd57;
|
||||
--accent-soft: #8bdba8;
|
||||
}
|
||||
|
||||
* {
|
||||
@ -52,6 +53,12 @@ body {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.note {
|
||||
margin: 0 0 10px;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
@ -107,6 +114,28 @@ textarea {
|
||||
color: var(--warn);
|
||||
}
|
||||
|
||||
.stat {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
background: #0d1625;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.stat h3 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.stat p {
|
||||
margin: 0;
|
||||
color: var(--accent-soft);
|
||||
font-size: 12px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 1px solid #2d7f4a;
|
||||
background: #173325;
|
||||
@ -166,6 +195,44 @@ button:active {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.advanced {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.advanced > summary {
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.advanced > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.advanced-body {
|
||||
padding: 12px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.subpanel {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
background: #0b1523;
|
||||
}
|
||||
|
||||
.subpanel h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.shell {
|
||||
margin-top: 12px;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user