From 9b989bd73544c38282836037ddad3d0806b0075b Mon Sep 17 00:00:00 2001 From: Joshua Date: Wed, 18 Feb 2026 10:06:26 -0800 Subject: [PATCH] feat(launcher): promote control surface and isolate advanced integration panel --- README.md | 28 ++-- app/app.js | 87 ++++++++++- app/index.html | 404 +++++++++++++++++++++++++++---------------------- app/style.css | 67 ++++++++ 4 files changed, 394 insertions(+), 192 deletions(-) diff --git a/README.md b/README.md index 21b4be7..b80ecfb 100644 --- a/README.md +++ b/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`. diff --git a/app/app.js b/app/app.js index e45f47b..7e34f05 100644 --- a/app/app.js +++ b/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(); diff --git a/app/index.html b/app/index.html index da189bb..891cb01 100644 --- a/app/index.html +++ b/app/index.html @@ -3,172 +3,53 @@ - EDUT Launcher Shell + EDUT Launcher

EDUT Launcher

-

Wallet-first onboarding shell (local integration harness)

+

Deterministic infrastructure control surface.

-

Connection

+

Control Surface

+
+ + + + +
+

ready

- - +
+

Wallet

+

not connected

+
+
+

Membership

+

unknown

+
+
+

Designation

+

-

+
+
+

Last Sync

+

never

+
-

Wallet Intent

-
- - - - - - -
-

flow idle

-
- - - -
-
- - - -
- -
- -
-

Membership Quote + Confirm

-
- - - - -
-
- - - -
-
- - - -
- -
- -
-

Member Channel

-
- - - -
-
- - - -
-
- - -
-
- - -
-
- -
+

Updates

+

Communication is pull-first. No broadcast notifications.

+
+ +
+

Support

-
-

Governance Install + Lease

-
- - -
-
- - - -
- -
- - - -
-
-

Response Log


       
+ +
+ Advanced Integration Controls +
+
+

Connection

+
+ + +
+
+ +
+

Membership Flow Controls

+
+ + + + + + +
+
+ + + +
+
+ + + +
+ +
+ +
+

Quote + Confirm

+
+ + + + +
+
+ + + +
+
+ + + +
+ +
+ +
+

Member Channel

+
+ + + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ +
+

Governance Install + Lease

+
+ + +
+
+ + + +
+ +
+ + + +
+
+
+
diff --git a/app/style.css b/app/style.css index 23a09cf..e48ff13 100644 --- a/app/style.css +++ b/app/style.css @@ -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;