Skip to content

Commit 99d9af0

Browse files
committed
Polish unlock flow: sharp sheet, RPC as step 3, terminal toggles
- Unlock sheet: drop rounded corners and the boxed wrapper around Connect Wallet (sharp terminal look, no stray section dividers) - Market RPC now shows as step 3 (with Save Key / Skip for now) BEFORE the Access Granted screen, instead of after it - No more per-step ding: the unlock chime plays once on the granted screen and still honors the Unlock sound setting - Settings toggles: sharp scanlined terminal switch (shared Toggle) replacing the rounded pill - Terminal-style Helius key form (shared with settings)
1 parent 17657a2 commit 99d9af0

4 files changed

Lines changed: 64 additions & 53 deletions

File tree

surfaces/webview/src/chat/Sessions.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ function ProgressiveMenuRow({ reason, unlocked, onUnlocked, ...row }: Omit<MenuR
7373
return <LockedGate reason={reason} onUnlocked={onUnlocked}><MenuRow {...row} locked onClick={onUnlocked} /></LockedGate>;
7474
}
7575

76+
// Terminal toggle switch (sharp, scanlined) — one place for every settings switch.
77+
function Toggle({ on }: { on: boolean }) {
78+
return (
79+
<span className={`an-toggle${on ? " on" : ""}`} aria-hidden="true">
80+
<span className="an-toggle-knob" />
81+
</span>
82+
);
83+
}
84+
7685
// One row in the Storage radio picker: a filled dot marks the active backend, tap to switch.
7786
// Compact + token-styled to sit naturally in the settings drawer (no emoji / em-dash).
7887
function StorageOption({ active, title, subtitle, onClick }: { active: boolean; title: string; subtitle: string; onClick: () => void }) {
@@ -395,9 +404,7 @@ export function Sessions({
395404
<span className="an-term-mono block text-[1.12rem] font-bold uppercase leading-tight" style={{ color: "var(--an-fg)" }}>Unlock sound</span>
396405
<span className="an-term-mono mt-1 block text-[10px] uppercase leading-tight" style={{ color: "var(--an-fg-mute)" }}>{unlockSound ? "On" : "Muted"}</span>
397406
</span>
398-
<span className="relative h-[1.35rem] w-[2.4rem] shrink-0 rounded-full transition" style={{ background: unlockSound ? "var(--an-green)" : "var(--an-bg-2)" }}>
399-
<span className="absolute top-[0.15rem] h-[1.05rem] w-[1.05rem] rounded-full bg-white transition-all" style={{ left: unlockSound ? "1.2rem" : "0.15rem" }} />
400-
</span>
407+
<Toggle on={unlockSound} />
401408
</button>
402409
{/* Android shell only: keep the agent running (and notify on approvals)
403410
while the app is backgrounded — but ONLY while a task is active. Off =
@@ -425,9 +432,7 @@ export function Sessions({
425432
<span className="an-term-mono block text-[1.12rem] font-bold uppercase leading-tight" style={{ color: "var(--an-fg)" }}>Background run</span>
426433
<span className="block text-[0.72rem] leading-tight" style={{ color: "var(--an-fg-mute)" }}>{bgExec ? "Runs in the background only while a task is active" : "Agent stops when you leave the app"}</span>
427434
</span>
428-
<span className="relative h-[1.35rem] w-[2.4rem] shrink-0 rounded-full transition" style={{ background: bgExec ? "var(--an-green)" : "var(--an-bg-2)" }}>
429-
<span className="absolute top-[0.15rem] h-[1.05rem] w-[1.05rem] rounded-full bg-white transition-all" style={{ left: bgExec ? "1.2rem" : "0.15rem" }} />
430-
</span>
435+
<Toggle on={bgExec} />
431436
</button>
432437
{bgExec && (
433438
<p className="px-2.5 pb-1 text-[0.68rem] leading-snug" style={{ color: "var(--an-fg-mute)" }}>
@@ -456,9 +461,7 @@ export function Sessions({
456461
{!bgExec ? "Turn on background execution first" : screenOffExec ? "Keeps active tasks running with the screen off" : "Pauses may occur after the screen turns off"}
457462
</span>
458463
</span>
459-
<span className="relative h-[1.35rem] w-[2.4rem] shrink-0 rounded-full transition" style={{ background: screenOffExec ? "var(--an-green)" : "var(--an-bg-2)" }}>
460-
<span className="absolute top-[0.15rem] h-[1.05rem] w-[1.05rem] rounded-full bg-white transition-all" style={{ left: screenOffExec ? "1.2rem" : "0.15rem" }} />
461-
</span>
464+
<Toggle on={screenOffExec} />
462465
</button>
463466
<p id="screen-off-exec-note" className="px-2.5 pb-1 text-[0.68rem] leading-snug" style={{ color: "var(--an-fg-mute)" }}>
464467
Uses more battery during active tasks. Approval requests and completed turns vibrate on the lock screen.

surfaces/webview/src/index.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,12 @@ body {
131131
.unlock-reward { animation: unlock-reward 400ms var(--ease-emphasized-decelerate) both; }
132132
/* Blinking terminal cursor for unlock-sheet eyebrows. */
133133
.unlock-cursor { animation: unlock-blink 1.1s steps(1) infinite; }
134+
/* Terminal toggle switch — sharp, scanlined, sliding square knob (replaces the rounded pill). */
135+
.an-toggle { position: relative; flex: 0 0 auto; width: 2.6rem; height: 1.4rem; overflow: hidden; border: 1px solid var(--an-line); background: var(--an-bg-2); transition: background .18s, border-color .18s; }
136+
.an-toggle::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: repeating-linear-gradient(0deg, rgba(0,0,0,0.28) 0, rgba(0,0,0,0.28) 1px, transparent 1px, transparent 3px); }
137+
.an-toggle-knob { position: absolute; top: 3px; bottom: 3px; left: 3px; width: 1.1rem; background: var(--an-fg-mute); transition: left .18s var(--ease-emphasized-decelerate), background .18s var(--ease-emphasized-decelerate); }
138+
.an-toggle.on { border-color: var(--an-green-line); background: var(--an-green-dim); }
139+
.an-toggle.on .an-toggle-knob { left: calc(100% - 1.1rem - 3px); background: var(--an-green); }
134140
@keyframes unlock-sheet-in {
135141
from { opacity: 0; transform: translateY(24px); }
136142
to { opacity: 1; transform: translateY(0); }

surfaces/webview/src/settings/HeliusKeyForm.tsx

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useState } from "react";
22
import { useStore } from "../state/store";
33

4-
export function HeliusKeyForm({ onDone }: { onDone?: () => void }) {
4+
export function HeliusKeyForm({ onDone, skipLabel = "Use Default" }: { onDone?: () => void; skipLabel?: string }) {
55
const { state, send } = useStore();
66
const [key, setKey] = useState("");
77
const [saving, setSaving] = useState(false);
@@ -30,44 +30,45 @@ export function HeliusKeyForm({ onDone }: { onDone?: () => void }) {
3030

3131
return (
3232
<div className="flex flex-col gap-3">
33-
<p className="text-sm text-zinc-400">
34-
A Helius key powers fast NFT indexing, agent lists, and skill search. It is stored locally and never synced.
33+
<p className="text-xs leading-relaxed text-zinc-500">
34+
Stored locally, never synced. Speeds up NFT indexing, agent lists, and skill search.
3535
</p>
36-
<a
37-
href="https://www.helius.dev/docs/quickstart"
38-
target="_blank"
39-
rel="noreferrer"
40-
className="text-xs font-medium text-an-green active:text-[#00d068]"
41-
>
42-
Helius quickstart
43-
</a>
4436
{state.rpcStatus?.hasKey && (
45-
<div className="rounded-lg border border-green-800/40 bg-green-900/10 px-3 py-2 text-xs text-green-500">
37+
<div className="an-term-mono border border-[color:var(--an-green-line)] bg-[color:var(--an-green-dim)] px-3 py-2 text-[11px] text-[color:var(--an-green)]">
4638
{state.rpcStatus.network} · {state.rpcStatus.masked}
4739
</div>
4840
)}
41+
<label className="an-term-mono text-[10px] font-bold uppercase tracking-[0.14em] text-[color:var(--an-fg-dim)]">
42+
&gt;HELIUS_API_KEY<span className="unlock-cursor">_</span>
43+
</label>
4944
<input
50-
className="w-full rounded-lg bg-zinc-900 border border-zinc-800 px-3 py-2.5 text-sm text-zinc-200 placeholder-zinc-600 focus:outline-none focus:border-green-500/50 font-mono"
51-
placeholder="xxxx-xxxx-xxxx or https://…helius-rpc.com/?api-key=…"
45+
className="an-term-mono w-full border border-[color:var(--an-line)] bg-[color:var(--an-bg-1)] px-3 py-3 text-sm text-[color:var(--an-fg)] placeholder-[color:var(--an-fg-mute)] focus:border-[color:var(--an-green-line)] focus:outline-none"
46+
placeholder="paste key or rpc url"
5247
value={key}
5348
onChange={(e) => setKey(e.target.value)}
5449
type="password"
5550
/>
56-
<div className="flex gap-2">
57-
<button
58-
onClick={save}
59-
disabled={saving || !key.trim()}
60-
className="flex-1 rounded-xl bg-green-600 py-2.5 text-sm font-semibold text-white active:bg-green-500 disabled:opacity-50"
61-
>
62-
{saving ? "Saving…" : "Save Key"}
63-
</button>
64-
<button
65-
onClick={clear}
66-
className="rounded-xl border border-zinc-700 px-4 py-2.5 text-sm text-zinc-400 active:bg-zinc-800"
67-
>
68-
Use Default
69-
</button>
70-
</div>
51+
<a
52+
href="https://www.helius.dev/docs/quickstart"
53+
target="_blank"
54+
rel="noreferrer"
55+
className="an-term-mono text-[10px] font-bold uppercase tracking-[0.12em] text-[color:var(--an-green)] active:opacity-70"
56+
>
57+
&gt; Get_your_key · helius.dev
58+
</a>
59+
<button
60+
onClick={save}
61+
disabled={saving || !key.trim()}
62+
className="an-btn an-btn-green mt-1 w-full disabled:opacity-50"
63+
>
64+
{saving ? "Saving…" : "Save Key"}
65+
</button>
66+
<button
67+
onClick={clear}
68+
className="an-term-mono min-h-11 w-full text-[11px] font-semibold uppercase tracking-[0.14em] text-[color:var(--an-fg-dim)] active:opacity-70"
69+
>
70+
&gt; {skipLabel}
71+
</button>
7172
</div>
7273
);
7374
}

surfaces/webview/src/unlock/UnlockProvider.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ export function UnlockProvider({ children }: { children: ReactNode }) {
108108
if (!wasUnlocked.current && unlocked && open) {
109109
localStorage.removeItem(PROGRESS_KEY);
110110
localStorage.setItem(LEGACY_PITCH_KEY, "1");
111-
setScreen("done");
111+
// Wallet just linked → step 3 (optional Market RPC) BEFORE the granted screen,
112+
// matching the tutorial order. Save-or-skip there advances to "done".
113+
setScreen("advanced");
112114
haptics.unlock();
113-
playUnlockSound();
114-
timers.current.push(window.setTimeout(() => haptics.celebrate(), 180));
115115
}
116116
wasUnlocked.current = unlocked;
117117
}, [unlocked, open]);
@@ -148,6 +148,14 @@ export function UnlockProvider({ children }: { children: ReactNode }) {
148148
if (next === "installed") haptics.step1();
149149
else if (next === "connect") haptics.step2();
150150
else haptics.tick();
151+
}
152+
153+
// Leaving the optional RPC step (saved or skipped) → the Access Granted screen. This is the
154+
// single success moment: one celebrate buzz + the unlock chime (only if the setting is on).
155+
// Per-step presses stay silent — no ding while advancing the tutorial.
156+
function enterGranted() {
157+
setScreen("done");
158+
haptics.celebrate();
151159
playUnlockSound();
152160
}
153161

@@ -157,7 +165,7 @@ export function UnlockProvider({ children }: { children: ReactNode }) {
157165
{open && (
158166
<div className="fixed inset-0 z-[60] flex items-end justify-center" role="dialog" aria-modal="true" aria-label="Unlock AgentNet">
159167
<button type="button" className="absolute inset-0 bg-black/70" aria-label="Close unlock tutorial" onClick={unlocked ? continueAction : dismiss} />
160-
<section className="unlock-sheet relative z-10 flex max-h-[92dvh] w-full max-w-lg flex-col overflow-hidden rounded-t-2xl border border-b-0 border-[color:var(--an-line)] bg-[color:var(--an-bg-0)]">
168+
<section className="unlock-sheet relative z-10 flex max-h-[92dvh] w-full max-w-lg flex-col overflow-hidden border border-b-0 border-[color:var(--an-line)] bg-[color:var(--an-bg-0)]">
161169
<div className="border-b border-[color:var(--an-line)]">
162170
<div className="an-term-mono flex items-center justify-between gap-2 px-4 pt-3 pb-2 text-[10px] uppercase tracking-[0.14em]" style={{ color: "var(--an-fg-mute)" }}>
163171
<span>&gt;UNLOCK_SEQ {SEQ[screen]}/03</span><span>アクセス ******</span>
@@ -183,7 +191,7 @@ export function UnlockProvider({ children }: { children: ReactNode }) {
183191
)}
184192
{screen === "connect" && (
185193
<StepScreen step={2} title="Connect wallet" detail="One signature links your agent. No payment is made.">
186-
<div className="mt-6 rounded-xl bg-[color:var(--an-bg-1)] p-4"><ConnectWallet embedded /></div>
194+
<div className="mt-6"><ConnectWallet embedded /></div>
187195
<p className="mt-4 text-center text-caption leading-relaxed text-[color:var(--an-fg-dim)]">Close this at any time. This step will be waiting when you return.</p>
188196
</StepScreen>
189197
)}
@@ -201,19 +209,12 @@ export function UnlockProvider({ children }: { children: ReactNode }) {
201209
))}
202210
</div>
203211
<button type="button" onClick={continueAction} className="an-btn an-btn-green mt-7 w-full">{copy.returnLabel}</button>
204-
<button type="button" onClick={() => setScreen("advanced")} className="mt-3 min-h-11 w-full text-label font-medium text-[color:var(--an-fg-dim)]">Advanced: configure Market RPC</button>
205212
</div>
206213
)}
207214
{screen === "advanced" && (
208-
<div className="mx-auto max-w-sm">
209-
<button type="button" onClick={() => setScreen("done")} className="mb-5 flex min-h-11 items-center gap-2 text-label text-[color:var(--an-fg-dim)]">
210-
<svg viewBox="0 0 24 24" className="h-4 w-4" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"><path d="m15 18-6-6 6-6" /></svg>
211-
Back to unlocked
212-
</button>
213-
<h3 className="text-title font-semibold text-[color:var(--an-fg)]">Optional Market RPC</h3>
214-
<p className="mb-5 mt-2 text-body-dense text-[color:var(--an-fg-dim)]">The public RPC works by default. Add Helius only for faster market indexing.</p>
215-
<HeliusKeyForm onDone={() => setScreen("done")} />
216-
</div>
215+
<StepScreen step={3} title="Market RPC" detail="Optional. The public RPC works by default — add a Helius key only for faster market indexing.">
216+
<div className="mt-6"><HeliusKeyForm onDone={enterGranted} skipLabel="Skip for now" /></div>
217+
</StepScreen>
217218
)}
218219
</div>
219220
</section>

0 commit comments

Comments
 (0)