From 290ed83a856b58463981bfec27d27cea8bea83cc Mon Sep 17 00:00:00 2001 From: Jay Zeng Date: Mon, 17 Aug 2026 00:05:16 -0700 Subject: [PATCH] feat: redesign plugin activation flow Signed-off-by: Jay Zeng --- CHANGELOG.md | 2 + src/plugin-service.ts | 97 +++++++++++++++++++++++++++++++++++++++++-- test/unit.test.ts | 10 ++++- 3 files changed, 104 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 832ce48..90d9690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- Redesigned the loopback activation and completion pages with a focused responsive card layout, clearer activation copy, extension-safe email input spacing, accessible focus/error states, and progressive privacy disclosure. + ## [0.4.15] - 2026-08-17 ### Added diff --git a/src/plugin-service.ts b/src/plugin-service.ts index 0572364..96da357 100644 --- a/src/plugin-service.ts +++ b/src/plugin-service.ts @@ -94,13 +94,104 @@ function securityHeaders(contentType: string): Record { }; } +const ACTIVATION_PAGE_STYLES = ` +:root{color-scheme:light;font-family:Inter,ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#172033;background:#f4f7fb;font-synthesis:none} +*{box-sizing:border-box} +body{min-height:100vh;margin:0;padding:2rem 1.25rem;display:grid;place-items:center;background:radial-gradient(circle at 12% 0%,#e8f0ff 0,transparent 38rem),radial-gradient(circle at 100% 100%,#eef2ff 0,transparent 32rem),#f7f9fc} +main{width:min(100%,34rem)} +.skip-link{position:fixed;z-index:1;top:1rem;left:1rem;padding:.65rem .8rem;border-radius:.55rem;background:#172033;color:#fff;font-weight:700;transform:translateY(-200%)} +.skip-link:focus{transform:translateY(0)} +.brand{display:flex;align-items:center;gap:.75rem;margin:0 0 1.25rem .25rem;color:#33415c;font-size:.875rem;font-weight:700;letter-spacing:.01em} +.mark{width:2.25rem;height:2.25rem;display:grid;place-items:center;border-radius:.7rem;background:linear-gradient(145deg,#172033,#3159b8);color:#fff;box-shadow:0 .5rem 1.25rem rgba(35,65,130,.2);font-size:1.05rem;letter-spacing:-.08em} +.card{padding:clamp(1.5rem,5vw,2.5rem);border:1px solid rgba(205,216,231,.9);border-radius:1.5rem;background:rgba(255,255,255,.94);box-shadow:0 1.5rem 4rem rgba(32,51,84,.12),0 .125rem .375rem rgba(32,51,84,.06);backdrop-filter:blur(1rem)} +.eyebrow{display:inline-flex;align-items:center;gap:.45rem;margin:0 0 1rem;padding:.42rem .7rem;border:1px solid #d9e4f7;border-radius:999px;background:#f3f7ff;color:#315798;font-size:.78rem;font-weight:750;letter-spacing:.03em;text-transform:uppercase} +.eyebrow::before{content:"";width:.45rem;height:.45rem;border-radius:50%;background:#2b67d1;box-shadow:0 0 0 .22rem #dbe8ff} +h1{margin:0;color:#141d2c;font-size:clamp(2rem,7vw,2.75rem);line-height:1.06;letter-spacing:-.045em;text-wrap:balance} +.intro{margin:.9rem 0 1.75rem;color:#526176;font-size:1.05rem;line-height:1.55} +form{display:grid;gap:.75rem} +label{color:#29364a;font-size:.9rem;font-weight:700} +input{width:100%;min-height:3.25rem;padding:.8rem 3.5rem .8rem 1rem;border:1px solid #bcc8d8;border-radius:.8rem;background:#fff;color:#172033;font:inherit;box-shadow:inset 0 1px 2px rgba(24,39,65,.04)} +input::placeholder{color:#8a96a7} +input:hover{border-color:#92a2b8} +input:focus-visible{outline:0;border-color:#2b67d1;box-shadow:0 0 0 .25rem rgba(43,103,209,.15)} +button{min-height:3.35rem;margin-top:.25rem;padding:.85rem 1rem;border:1px solid #172033;border-radius:.8rem;background:linear-gradient(180deg,#24334a,#172033);color:#fff;font:inherit;font-weight:750;cursor:pointer;box-shadow:0 .55rem 1.2rem rgba(23,32,51,.18);touch-action:manipulation;transition:transform .15s} +button:hover{background:linear-gradient(180deg,#2d405c,#1d2a40);box-shadow:0 .7rem 1.4rem rgba(23,32,51,.23);transform:translateY(-1px)} +button:active{transform:translateY(0)} +button:focus-visible,summary:focus-visible{outline:.2rem solid rgba(43,103,209,.32);outline-offset:.18rem} +.terminal-note{margin:.8rem 0 0;color:#6a7789;font-size:.85rem;text-align:center} +.error{margin:0 0 1rem;padding:.8rem 1rem;border:1px solid #efb8bd;border-radius:.75rem;background:#fff3f4;color:#982631;font-size:.9rem;line-height:1.45} +details{margin-top:1.5rem;padding-top:1.25rem;border-top:1px solid #e2e8f0;color:#5b687a;font-size:.85rem;line-height:1.55} +summary{color:#46566d;font-weight:700;cursor:pointer;list-style-position:outside;touch-action:manipulation} +details p{margin:.8rem 0 0} +.never-sent{padding:.75rem .85rem;border-radius:.65rem;background:#f5f7fa;color:#536176} +.success{display:grid;place-items:center;width:3.5rem;height:3.5rem;margin-bottom:1.4rem;border-radius:1rem;background:#eaf7ef;color:#197542;font-size:1.65rem;font-weight:800;box-shadow:inset 0 0 0 1px #c9ead6} +.completion .intro{margin-bottom:0} +@media (max-width:30rem){body{padding:1rem}.brand{margin-left:.1rem}.card{border-radius:1.15rem}h1{font-size:2rem}} +@media (prefers-reduced-motion:reduce){input,button{transition:none}} +`; + function activationPage(action: string, error?: string): string { - const errorHtml = error ? `

${error}

` : ""; - return `Activate AgentMemory

Activate AgentMemory

Enter an email address to enable the free daily agent-session allowance on this device.

${errorHtml}

The AgentMemory CLI sends your email plus core, bundle, platform, architecture, and release-channel metadata to the private activation service. D1 stores a daily count of opaque SessionStart operations for your normalized email. The request never includes memory, session content, queries, repository paths, raw agent session identifiers, IP addresses, or user-agent strings. Activation records expire after 365 days without use.

`; + const errorHtml = error ? `` : ""; + const describedBy = error ? "activation-error terminal-note" : "terminal-note"; + const autofocus = error ? " autofocus" : ""; + return ` + + + + + + Activate AgentMemory + + + + +
+
AgentMemory
+
+

Free daily allowance

+

Activate This Device

+

Use your email to enable AgentMemory Pro for your local agent sessions.

+
+ + ${errorHtml} + + +
+

Your terminal will finish setup after activation.

+
+ What’s shared during activation +

Your email identifies your free daily allowance. The CLI also sends core and bundle versions, platform, architecture, and release channel. The service stores a daily count of opaque session-start operations. Activation records expire after 365 days without use.

+

Never sent: The request never includes memory, session content, queries, repository paths, raw agent session identifiers, IP addresses, or user-agent strings.

+
+
+
+ +`; } function completionPage(): string { - return 'AgentMemory activated

Activation complete

You can close this tab and return to the terminal.

'; + return ` + + + + + + AgentMemory activated + + + + +
+
AgentMemory
+
+ +

Device activated

+

You’re All Set

+

Return to your terminal to finish installing AgentMemory Pro. You can close this tab.

+
+
+ +`; } function send(response: ServerResponse, status: number, body: string, contentType = "text/html; charset=utf-8"): void { diff --git a/test/unit.test.ts b/test/unit.test.ts index bb87e66..cde211c 100644 --- a/test/unit.test.ts +++ b/test/unit.test.ts @@ -2311,10 +2311,16 @@ describe("temporary plugin activation and runtime", () => { return true; }); expect(page).toContain("Activate AgentMemory"); + expect(page).toContain("Activate This Device"); + expect(page).toContain("What’s shared during activation"); expect(page).toContain( - "sends your email plus core, bundle, platform, architecture, and release-channel metadata", + "The CLI also sends core and bundle versions, platform, architecture, and release channel", ); - expect(page).toContain("D1 stores a daily count of opaque SessionStart operations"); + expect(page).toContain("Never sent:"); + expect(page).toContain('autocomplete="email"'); + expect(page).toContain('spellcheck="false"'); + expect(page).toContain('href="#activation"'); + expect(page).toContain("padding:.8rem 3.5rem .8rem 1rem"); expect(page).toContain( "never includes memory, session content, queries, repository paths, raw agent session identifiers", );