Skip to content

Commit c5567b9

Browse files
authored
Merge pull request #2 from daretodave/step-04a-share-storyboard
feat(web): rebuild the share page as a per-client connect storyboard
2 parents c53fd32 + 7a0d0b1 commit c5567b9

34 files changed

Lines changed: 1984 additions & 698 deletions

.github/assets/badge-connect.svg

Lines changed: 17 additions & 0 deletions
Loading

.github/assets/connect-page.png

88.6 KB
Loading

.github/assets/create-page.png

71.1 KB
Loading

.github/assets/logo.svg

Lines changed: 26 additions & 0 deletions
Loading

README.md

Lines changed: 191 additions & 26 deletions
Large diffs are not rendered by default.

apps/web/src/components/step-card/step-card.test.tsx

Lines changed: 0 additions & 56 deletions
This file was deleted.

apps/web/src/components/step-card/step-card.tsx

Lines changed: 0 additions & 124 deletions
This file was deleted.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { render } from "@testing-library/react";
2+
import { describe, expect, it } from "vitest";
3+
import { AuthScene } from "./auth-scene";
4+
5+
describe("AuthScene", () => {
6+
it("shows the server, its needs-auth status, and the default Connect control", () => {
7+
const { getByText } = render(<AuthScene name="acme-mcp" url="https://acme.dev/mcp" />);
8+
expect(getByText("acme-mcp")).toBeInTheDocument();
9+
expect(getByText("https://acme.dev/mcp")).toBeInTheDocument();
10+
expect(getByText("Needs authentication")).toBeInTheDocument();
11+
expect(getByText("Connect")).toBeInTheDocument();
12+
});
13+
14+
it("uses the given control label (Authenticate)", () => {
15+
const { getByText } = render(<AuthScene name="acme-mcp" button="Authenticate" />);
16+
expect(getByText("Authenticate")).toBeInTheDocument();
17+
});
18+
19+
it("omits the url row when no url is given", () => {
20+
const { queryByText } = render(<AuthScene name="acme-mcp" />);
21+
expect(queryByText(/^https:/)).toBeNull();
22+
});
23+
});
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { css } from "../../../styled-system/css";
2+
import { Icon } from "../icon/icon";
3+
4+
// The authenticate beat for clients with an explicit control — after the server is added, the client shows a
5+
// "Needs authentication" state and a glowing Connect / Authenticate button. We draw that pane: the server, the
6+
// warn cue, and the control the reader taps. (Cursor, Claude, Cline, Zed.)
7+
8+
const frame = css({
9+
display: "flex",
10+
flexDirection: "column",
11+
alignItems: "center",
12+
justifyContent: "center",
13+
gap: "[9px]",
14+
padding: "[26px 18px]",
15+
textAlign: "center",
16+
background: "bg.inset",
17+
minHeight: "[190px]",
18+
});
19+
20+
const glyph = css({
21+
display: "inline-flex",
22+
alignItems: "center",
23+
justifyContent: "center",
24+
width: "[46px]",
25+
height: "[46px]",
26+
borderRadius: "md",
27+
background: "accent.soft",
28+
borderWidth: "hairline",
29+
borderStyle: "solid",
30+
borderColor: "accent.line",
31+
color: "accent.text",
32+
fontFamily: "mono",
33+
fontSize: "h3",
34+
fontWeight: "semibold",
35+
});
36+
37+
const nameClass = css({ fontSize: "body", fontWeight: "semibold", color: "text.primary" });
38+
39+
const urlClass = css({
40+
display: "inline-flex",
41+
alignItems: "center",
42+
gap: "[7px]",
43+
fontFamily: "mono",
44+
fontSize: "xs",
45+
color: "text.muted",
46+
});
47+
48+
const statusClass = css({
49+
display: "inline-flex",
50+
alignItems: "center",
51+
gap: "[6px]",
52+
fontSize: "sm",
53+
fontWeight: "semibold",
54+
color: "state.warn.text",
55+
});
56+
57+
const buttonClass = css({
58+
display: "inline-flex",
59+
alignItems: "center",
60+
gap: "[7px]",
61+
marginTop: "[4px]",
62+
padding: "[9px 22px]",
63+
borderRadius: "md",
64+
background: "accent.base",
65+
color: "accent.fg",
66+
fontSize: "sm",
67+
fontWeight: "semibold",
68+
boxShadow: "glow",
69+
});
70+
71+
export interface AuthSceneProps {
72+
/** The connector's display name in the client's list. */
73+
name: string;
74+
/** The remote URL, shown small under the name. */
75+
url?: string;
76+
/** The client's not-yet-authed status line. */
77+
status?: string;
78+
/** The control the reader taps — "Connect" or "Authenticate". */
79+
button?: string;
80+
}
81+
82+
/** The explicit-control authenticate pane — a glowing Connect / Authenticate button on a "needs auth" row. */
83+
export function AuthScene({
84+
name,
85+
url,
86+
status = "Needs authentication",
87+
button = "Connect",
88+
}: AuthSceneProps) {
89+
return (
90+
<div className={frame}>
91+
<span className={glyph} aria-hidden="true">
92+
{"{·}"}
93+
</span>
94+
<div className={nameClass}>{name}</div>
95+
{url ? (
96+
<div className={urlClass}>
97+
{url}
98+
<Icon name="external-link" size={12} />
99+
</div>
100+
) : null}
101+
<div className={statusClass}>
102+
<Icon name="alert-triangle" size={13} />
103+
{status}
104+
</div>
105+
<span className={buttonClass}>
106+
{button}
107+
<Icon name="arrow-right" size={14} />
108+
</span>
109+
</div>
110+
);
111+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { render } from "@testing-library/react";
2+
import { describe, expect, it } from "vitest";
3+
import { BeatKind } from "./beat-kind";
4+
5+
describe("BeatKind", () => {
6+
it("names the authenticate archetype", () => {
7+
const { getByText } = render(<BeatKind kind="auth" />);
8+
expect(getByText("authenticate")).toBeInTheDocument();
9+
});
10+
11+
it("distinguishes the browser variant", () => {
12+
const { getByText } = render(<BeatKind kind="browser" />);
13+
expect(getByText("authenticate · browser")).toBeInTheDocument();
14+
});
15+
16+
it("labels the add and configure kinds", () => {
17+
expect(render(<BeatKind kind="add" />).getByText("add")).toBeInTheDocument();
18+
expect(render(<BeatKind kind="run" />).getByText("configure")).toBeInTheDocument();
19+
});
20+
});

0 commit comments

Comments
 (0)