From ca6b91d883d68ae45f8491964428bf829bee7c91 Mon Sep 17 00:00:00 2001
From: anandpant <109482096+anandpant@users.noreply.github.com>
Date: Sat, 25 Jul 2026 17:21:26 +0000
Subject: [PATCH] fix(web): cut the copy, drop the Product tab, put npm in the
header (#294)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Subtractive pass over `apps/web`, per the S5 brief. Three jobs plus a full read of the rest of the site.
## 1. Nav — Product removed
`{ href: "/#product", label: "Product" }` is gone from the header. The `FeatureGrid` section keeps `id="product"`; nothing else in the repo linked to that anchor (grepped), so no dangling target.
## 2. npm beside GitHub
`https://www.npmjs.com/package/sketchi` now sits in the header actions next to GitHub, in the same 40px `.site-header__mark` button with the same hover and focus treatment, and again in the mobile sheet where the desktop actions are hidden.
npm now uses its **wordmark**, masked in `currentColor` exactly like `.gh-mark`, at its natural 46×18 rather than forced into a 20px square. It inherits ink on the light header and paper on the dark footer, and appears in the header bar, the mobile sheet, and the footer's More column.
Two things about the asset, since neither was obvious:
- **The square tile cannot be masked.** `/brand/npm.svg` is a knockout — a solid red 100×100 with the `n` cut out — so an alpha mask renders the complement: a bare frame with a bar in it. Shipping it in colour instead (the first push) read as a red blob, not as npm, and broke the header's monochrome treatment.
- **npm's wordmark geometry cannot be masked verbatim either.** Its letters bleed to the left, top, and right edges of the red box, so the holes break the field's outline and the mask renders as disconnected slivers. `/brand/npm-wordmark.svg` therefore uses npm's own 780×250 letter grid **unchanged**, inset by a uniform 50 units. That inset is the only deviation and it is load-bearing.
The footer's "npm package" entry takes the same mark, which drops two words — the wordmark is the word.
## 3. Every string deleted
**CLI band**
- "`sketchi` is a real command-line tool, not a wrapper around this website. It keeps your diagrams in `~/.sketchi/diagrams`, does its authoring, export, and recovery work offline, and speaks `--output json` on every command so an agent can drive it."
- "Generate without signing in"
- "One prompt, one HTTPS request, a validated diagram on disk. No API key, token, or account."
- "Seven commands work offline"
- "Create, patch, show, edit, list, export, and restore never touch the network or a model."
- "Export from the terminal"
- "Render PNG or Excalidraw locally, deterministically, without starting a browser."
- "Installs the package and sets up completions for zsh, bash, or fish."
- "Straight from npm, if you would rather configure completions yourself."
- "Then ask for a diagram:"
- "Read the CLI docs →"
Replaced by one line: "`sketchi` draws diagrams from your shell. No account, no API key, and `--output json` on every command so an agent can drive it."
**Feature grid** — the three sentences added in an earlier round:
- "Every shape, connector, and label stays editable after generation."
- "Find the tools in your stack without drawing their marks by hand."
- "Open the scene in Excalidraw, then export it when you are ready."
Nothing replaces them. The band was thin without them, so the cards got a surface, padding, and a tighter grid — layout, not words.
**Closing CTA**
- "or install the CLI →"
**Footer**
- "Prompts become real, editable diagrams, logos included."
- "Made for people who'd rather describe a diagram than draw one."
- "npm package" (the wordmark says it)
Both restated the hero. The hero survives.
**Docs**
- "You describe it." (from the Three paths lead)
- ", not a best-guess sketch"
- ", so nothing you make is trapped in a browser tab"
- "Makes a single credential-free HTTPS request — " → "No key, token, account, or login."
- "PNG rendering is deterministic and local; it never starts a browser or uses the network." → "Local and deterministic; never uses the network."
- "Read the code, file an issue, or run your own copy."
- "Take it to your docs, slides, or pull request." → "Yes, as PNG or Excalidraw."
- both install captions (shared with the CLI band above)
**Agents (hub + all four pages)**
- ", not a wall of ASCII"
- "Add this URL wherever your agent keeps its MCP config." (endpoint caption, hub)
- "Public endpoint used by the setup below." (endpoint caption, detail pages)
- "Installs as a Codex plugin, so the skill is always a command away."
- "Setup is just the skill plus the public MCP server." (×2)
- "The plugin bundles the skill, so you can ask in plain language."
- "; provider and model behavior remain OpenCode concerns"
- "; model and harness behavior remain Agy concerns"
Kept: "No account, API key, or local browser needed." on each page, and Antigravity's merge-don't-overwrite note, which is real setup information the heading does not carry.
**Dead code removed with it:** `cliCapabilities`, `CliCapability`, `CliInstallOption.detail`, `CtaBandProps.cliHref`, `CliBandProps.docsHref`, `SiteFooterProps.colophon`, `EndpointCard`'s `meta` prop, and the CSS for `.cli-band__points`, `.cli-band__point`, `.cli-band__note`, `.cli-band__then`, `.cli-band__step`, `.cli-band__docs-link`, `.feature-card__description`, `.site-footer__tagline`, `.agent-endpoint__meta`.
No caption, tooltip, helper line, or "learn more" was added anywhere. Install commands are byte-identical — `cli-package.test.ts` still pins them.
## Tests
Rewritten to assert the absence rather than deleted: the CLI band now proves it makes *no* offline claim and captions nothing; the feature grid proves no `
` under a title; the CTA proves two actions; the footer proves the hero thesis is not restated; the header proves both marks reach the mobile sheet.
## Proof
- `pnpm nx run-many -t typecheck,test,build` — green (52 tasks)
- `pnpm test:tools` — 64 passed
- Real Chrome at 1440×900 and 390×844: home, `/docs`, `/agents`, `/agents/codex`, `/agents/antigravity`. npm and GitHub both link correctly in the bar and the sheet, no Product tab, no empty container or dangling anchor from anything cut.
- Both marks checked at 1x on the light header, the dark footer, and in the mobile sheet.
- `codex review --base main` — clean on both pushes.
---
apps/web/public/brand/npm-wordmark.svg | 12 ++
.../agent-setup-view/agent-setup-data.ts | 30 +---
.../agent-setup-view.test.tsx | 8 +-
.../agent-setup-view/agent-setup-view.tsx | 9 +-
.../src/components/cli-band/cli-band.test.tsx | 39 +++--
apps/web/src/components/cli-band/cli-band.tsx | 44 ++---
.../src/components/cta-band/cta-band.test.tsx | 16 +-
apps/web/src/components/cta-band/cta-band.tsx | 10 +-
.../src/components/docs-view/docs-view.tsx | 37 ++--
.../feature-grid/feature-grid.test.tsx | 25 ++-
.../components/feature-grid/feature-grid.tsx | 11 +-
.../site-footer/site-footer.test.tsx | 32 ++--
.../components/site-footer/site-footer.tsx | 16 +-
.../site-header/site-header.test.tsx | 27 ++-
.../components/site-header/site-header.tsx | 24 ++-
apps/web/src/lib/cli-package.ts | 34 ----
apps/web/src/routes/-agents.test.tsx | 3 -
apps/web/src/styles/app.css | 163 ++++++++----------
18 files changed, 245 insertions(+), 295 deletions(-)
create mode 100644 apps/web/public/brand/npm-wordmark.svg
diff --git a/apps/web/public/brand/npm-wordmark.svg b/apps/web/public/brand/npm-wordmark.svg
new file mode 100644
index 00000000..79102c27
--- /dev/null
+++ b/apps/web/public/brand/npm-wordmark.svg
@@ -0,0 +1,12 @@
+
+
diff --git a/apps/web/src/components/agent-setup-view/agent-setup-data.ts b/apps/web/src/components/agent-setup-view/agent-setup-data.ts
index fe452142..0f62cd67 100644
--- a/apps/web/src/components/agent-setup-view/agent-setup-data.ts
+++ b/apps/web/src/components/agent-setup-view/agent-setup-data.ts
@@ -53,13 +53,9 @@ export const agentSetupEntries: readonly AgentSetupEntry[] = [
icon: "/agents/codex.svg",
id: "codex",
name: "Codex",
- notes: [
- "Installs as a Codex plugin, so the skill is always a command away.",
- "No account, API key, or local browser needed.",
- ],
+ notes: ["No account, API key, or local browser needed."],
status: "Plugin",
- summary:
- "Install the plugin, then ask Codex to diagram anything.",
+ summary: "Install the plugin, then ask Codex to diagram anything.",
tagline: "OpenAI's coding agent",
},
{
@@ -84,13 +80,9 @@ curl -fsSL ${portableSkillUrl} -o "$SKETCHI_SKILL_DIR/SKILL.md"`,
icon: "/agents/opencode.svg",
id: "opencode",
name: "OpenCode",
- notes: [
- "Setup is just the skill plus the public MCP server.",
- "No account, API key, or local browser needed; provider and model behavior remain OpenCode concerns.",
- ],
+ notes: ["No account, API key, or local browser needed."],
status: "MCP",
- summary:
- "Point OpenCode at Sketchi and diagram from the terminal.",
+ summary: "Point OpenCode at Sketchi and diagram from the terminal.",
tagline: "Open-source terminal agent",
},
{
@@ -120,13 +112,9 @@ curl -fsSL ${portableSkillUrl} -o "$SKETCHI_SKILL_DIR/SKILL.md"`,
icon: "/agents/claude-code.svg",
id: "claude-code",
name: "Claude Code",
- notes: [
- "The plugin bundles the skill, so you can ask in plain language.",
- "No account, API key, or local browser needed.",
- ],
+ notes: ["No account, API key, or local browser needed."],
status: "Plugin",
- summary:
- "Add the plugin, then turn any explanation into a diagram.",
+ summary: "Add the plugin, then turn any explanation into a diagram.",
tagline: "Anthropic's coding agent",
},
{
@@ -152,12 +140,10 @@ curl -fsSL ${portableSkillUrl} -o .agents/skills/sketchi-code-mode/SKILL.md`,
name: "Antigravity",
notes: [
"Save as .agents/mcp_config.json, merging the sketchi-code-mode server into any existing mcpServers object instead of overwriting it.",
- "Setup is just the skill plus the public MCP server.",
- "No account, API key, or local browser needed; model and harness behavior remain Agy concerns.",
+ "No account, API key, or local browser needed.",
],
status: "MCP",
- summary:
- "Add the skill and point Agy at the Code Mode server.",
+ summary: "Add the skill and point Agy at the Code Mode server.",
tagline: "Google's agent IDE",
},
] satisfies readonly AgentSetupEntry[];
diff --git a/apps/web/src/components/agent-setup-view/agent-setup-view.test.tsx b/apps/web/src/components/agent-setup-view/agent-setup-view.test.tsx
index 59b2d62c..286b09a0 100644
--- a/apps/web/src/components/agent-setup-view/agent-setup-view.test.tsx
+++ b/apps/web/src/components/agent-setup-view/agent-setup-view.test.tsx
@@ -20,9 +20,7 @@ describe("AgentSetupView", () => {
"href",
"http://localhost:3000/agents/opencode",
);
- expect(
- screen.getByText("https://playground.sketchi.app/mcp"),
- ).toBeTruthy();
+ expect(screen.getByText("https://playground.sketchi.app/mcp")).toBeTruthy();
expect(screen.queryByRole("link", { name: /Eval harness/ })).toBeNull();
expect(screen.queryByRole("link", { name: /Excalidraw app/ })).toBeNull();
});
@@ -41,7 +39,9 @@ describe("AgentSetupView", () => {
expect(
screen.getByText(/opencode\/skills\/sketchi-code-mode/),
).toBeTruthy();
- expect(screen.getByText(/provider and model behavior/)).toBeTruthy();
+ expect(
+ screen.getByText("No account, API key, or local browser needed."),
+ ).toBeTruthy();
});
it("renders the Codex plugin marketplace commands", () => {
diff --git a/apps/web/src/components/agent-setup-view/agent-setup-view.tsx b/apps/web/src/components/agent-setup-view/agent-setup-view.tsx
index 85f79dbf..d2d85331 100644
--- a/apps/web/src/components/agent-setup-view/agent-setup-view.tsx
+++ b/apps/web/src/components/agent-setup-view/agent-setup-view.tsx
@@ -22,12 +22,11 @@ export function AgentSetupView({ agentId }: AgentSetupViewProps) {
return ;
}
-function EndpointCard({ meta }: { meta: string }) {
+function EndpointCard() {
return (
Server URL{codeModeMcpEndpoint}
- {meta}
);
}
@@ -41,10 +40,10 @@ function AgentSetupHub() {
Sketch diagrams without leaving your agent.
Connect it once, then ask in plain language. Your agent hands back
- a real, editable diagram, not a wall of ASCII.
+ a real, editable diagram.
-
+
diff --git a/apps/web/src/components/cli-band/cli-band.test.tsx b/apps/web/src/components/cli-band/cli-band.test.tsx
index 8f1e3ddf..1d19a243 100644
--- a/apps/web/src/components/cli-band/cli-band.test.tsx
+++ b/apps/web/src/components/cli-band/cli-band.test.tsx
@@ -4,6 +4,7 @@ import { describe, expect, it, vi } from "vitest";
import { CliBand } from "./cli-band";
import {
CLI_INSTALL_COMMAND,
+ CLI_NODE_REQUIREMENT,
CLI_NPM_INSTALL_COMMAND,
CLI_NPM_URL,
} from "../../lib/cli-package";
@@ -60,20 +61,29 @@ describe("CliBand", () => {
});
/**
- * `generate`, `share`, and `pull` all make HTTPS requests. Only the other
- * seven commands are offline, so a blanket "offline except generation" claim
- * is false — and a wrong privacy claim is worse than a vague one.
+ * Generation needs the network, so counting the commands that do not is a
+ * claim nobody installs a CLI for. The band makes no offline claim at all
+ * now, and must not grow one back.
*/
- it("does not overstate what works offline", () => {
+ it("makes no offline claim", () => {
+ const { container } = render();
+
+ expect(container.textContent).not.toMatch(/offline/i);
+ });
+
+ /**
+ * Every install command carried a sentence explaining it. The commands are
+ * self-evident, so the terminal card is commands and the Node floor, nothing
+ * else.
+ */
+ it("captions none of the commands", () => {
const { container } = render();
- const text = container.textContent ?? "";
- expect(text).not.toMatch(/offline for everything but generation/);
expect(
- screen.getByText(
- "Create, patch, show, edit, list, export, and restore never touch the network or a model.",
+ [...container.querySelectorAll(".cli-band__body p")].map(
+ (element) => element.textContent,
),
- ).toBeTruthy();
+ ).toEqual([`Requires ${CLI_NODE_REQUIREMENT}.`]);
});
it("links to the real npm package page with the npm mark", () => {
@@ -88,14 +98,13 @@ describe("CliBand", () => {
);
});
- it("links to the CLI docs section", () => {
- render();
+ it("offers npm as the section's only trailing link", () => {
+ const { container } = render();
+ expect(container.querySelectorAll(".cli-band__links a")).toHaveLength(1);
expect(
- screen
- .getByRole("link", { name: /Read the CLI docs/ })
- .getAttribute("href"),
- ).toBe("/docs#cli");
+ screen.queryByRole("link", { name: /Read the CLI docs/ }),
+ ).toBeNull();
});
it("is anchored so navigation and footer entries can reach it", () => {
diff --git a/apps/web/src/components/cli-band/cli-band.tsx b/apps/web/src/components/cli-band/cli-band.tsx
index c667cfb5..cd85b96e 100644
--- a/apps/web/src/components/cli-band/cli-band.tsx
+++ b/apps/web/src/components/cli-band/cli-band.tsx
@@ -5,15 +5,12 @@ import {
CLI_NODE_REQUIREMENT,
CLI_NPM_URL,
CLI_PACKAGE_NAME,
- cliCapabilities,
cliInstallOptions,
} from "../../lib/cli-package";
export interface CliBandProps {
/** Link to the published npm package. */
npmUrl?: string;
- /** Where the fuller CLI writeup lives. */
- docsHref?: string;
}
/**
@@ -21,10 +18,7 @@ export interface CliBandProps {
* on this page; the terminal is a peer of both, so it gets one too — the
* install command is the section, copy-pasteable, not a link to go find it.
*/
-export function CliBand({
- docsHref = "/docs#cli",
- npmUrl = CLI_NPM_URL,
-}: CliBandProps) {
+export function CliBand({ npmUrl = CLI_NPM_URL }: CliBandProps) {
return (
@@ -33,30 +27,16 @@ export function CliBand({
Or never leave the terminal.
- {CLI_PACKAGE_NAME} is a real command-line tool, not a
- wrapper around this website. It keeps your diagrams in{" "}
- ~/.sketchi/diagrams, does its authoring, export, and
- recovery work offline, and speaks --output json on
- every command so an agent can drive it.
+ {CLI_PACKAGE_NAME} draws diagrams from your shell. No
+ account, no API key, and --output json on every command
+ so an agent can drive it.
diff --git a/apps/web/src/components/cta-band/cta-band.test.tsx b/apps/web/src/components/cta-band/cta-band.test.tsx
index 9f828865..9697cfb7 100644
--- a/apps/web/src/components/cta-band/cta-band.test.tsx
+++ b/apps/web/src/components/cta-band/cta-band.test.tsx
@@ -20,13 +20,15 @@ describe("CtaBand", () => {
).toHaveProperty("href", "http://localhost:3000/agents");
});
- it("offers the CLI as the third closing route", () => {
- render();
+ /**
+ * Three trailing routes out of one closing sheet is clutter. The playground
+ * is the primary, the agent hub is the one alternative, and the CLI keeps
+ * its own full section further up the page.
+ */
+ it("closes on one primary action and one alternative", () => {
+ const { container } = render();
- expect(
- screen
- .getByRole("link", { name: /install the CLI/ })
- .getAttribute("href"),
- ).toBe("/#cli");
+ expect(container.querySelectorAll(".cta-band__actions a")).toHaveLength(2);
+ expect(screen.queryByRole("link", { name: /install the CLI/ })).toBeNull();
});
});
diff --git a/apps/web/src/components/cta-band/cta-band.tsx b/apps/web/src/components/cta-band/cta-band.tsx
index 158ff20c..22fdf5cb 100644
--- a/apps/web/src/components/cta-band/cta-band.tsx
+++ b/apps/web/src/components/cta-band/cta-band.tsx
@@ -5,19 +5,16 @@ export interface CtaBandProps {
playgroundHref?: string;
/** Link to the agent setup hub. */
agentsHref?: string;
- /** Link to the CLI section. */
- cliHref?: string;
}
/**
* Closing invitation — a warm, blank sketch sheet echoing the hero board.
- * One clear next step, with the agent and terminal paths as quiet secondary
- * links for readers the browser is not the answer for.
+ * One clear next step, and one quiet alternative for readers the browser is
+ * not the answer for. The CLI has its own section above.
*/
export function CtaBand({
playgroundHref = DEFAULT_WEB_SURFACE_URLS.playground,
agentsHref = "/agents",
- cliHref = "/#cli",
}: CtaBandProps) {
return (
@@ -33,9 +30,6 @@ export function CtaBand({
or add it to your coding agent →
-
- or install the CLI →
-
diff --git a/apps/web/src/components/docs-view/docs-view.tsx b/apps/web/src/components/docs-view/docs-view.tsx
index 26042944..2f2492e6 100644
--- a/apps/web/src/components/docs-view/docs-view.tsx
+++ b/apps/web/src/components/docs-view/docs-view.tsx
@@ -62,10 +62,9 @@ export function DocsView({
Three paths
- You describe it. Sketchi structures and validates the request before
- drawing, so the result stays consistent, not a best-guess sketch.
- What differs between the three paths is where that happens and who
- keeps the file.
+ Sketchi structures and validates every request before drawing. What
+ differs between the three paths is where that happens and who keeps
+ the file.
@@ -129,20 +128,16 @@ export function DocsView({
{CLI_PACKAGE_NAME} is published on npm and authors
diagrams from your shell. Records live under{" "}
~/.sketchi/diagrams with the canonical document, the
- Excalidraw artifact, and every prior revision, so nothing you make
- is trapped in a browser tab.
+ Excalidraw artifact, and every prior revision.
Install
{cliInstallOptions.map((option) => (
-
-
-
- {option.command}
-
-
-
-
{option.detail}
+
+
+ {option.command}
+
+
))}
Requires {CLI_NODE_REQUIREMENT}.
@@ -154,9 +149,8 @@ export function DocsView({
generate
- Turn one prompt into a validated diagram. Makes a single
- credential-free HTTPS request — no key, token, account, or
- login.
+ Turn one prompt into a validated diagram. No key, token,
+ account, or login.
@@ -182,8 +176,8 @@ export function DocsView({
export
- Write PNG or Excalidraw. PNG rendering is deterministic and
- local; it never starts a browser or uses the network.
+ Write PNG or Excalidraw. Local and deterministic; never uses the
+ network.
@@ -227,8 +221,7 @@ export function DocsView({
Open source
Sketchi is built in the open and MIT-licensed: the diagram engine,
- the icon set, the agent runtime, even these pages. Read the code,
- file an issue, or run your own copy.
+ the icon set, the agent runtime, even these pages.
Source
@@ -278,7 +271,7 @@ export function DocsView({
Can I export?
-
Yes. Take it to your docs, slides, or pull request.
+
Yes, as PNG or Excalidraw.
Is there a CLI?
diff --git a/apps/web/src/components/feature-grid/feature-grid.test.tsx b/apps/web/src/components/feature-grid/feature-grid.test.tsx
index 381b0925..498ce3a7 100644
--- a/apps/web/src/components/feature-grid/feature-grid.test.tsx
+++ b/apps/web/src/components/feature-grid/feature-grid.test.tsx
@@ -16,20 +16,15 @@ describe("FeatureGrid", () => {
expect(
screen.getByRole("heading", { name: "Yours to edit and export" }),
).toBeTruthy();
- expect(
- screen.getByText(
- "Every shape, connector, and label stays editable after generation.",
- ),
- ).toBeTruthy();
- expect(
- screen.getByText(
- "Find the tools in your stack without drawing their marks by hand.",
- ),
- ).toBeTruthy();
- expect(
- screen.getByText(
- "Open the scene in Excalidraw, then export it when you are ready.",
- ),
- ).toBeTruthy();
+ });
+
+ /**
+ * The titles say the whole thing. A supporting sentence per card was padding
+ * dressed as polish, so the band carries its weight through layout instead.
+ */
+ it("adds no restating sentence under a title", () => {
+ const { container } = render();
+
+ expect(container.querySelector(".feature-card p")).toBeNull();
});
});
diff --git a/apps/web/src/components/feature-grid/feature-grid.tsx b/apps/web/src/components/feature-grid/feature-grid.tsx
index db62c0cb..100250c4 100644
--- a/apps/web/src/components/feature-grid/feature-grid.tsx
+++ b/apps/web/src/components/feature-grid/feature-grid.tsx
@@ -3,7 +3,6 @@ import type { ReactNode } from "react";
import { BrandIcon } from "../brand-icon/index.js";
interface Feature {
- description: string;
glyph: ReactNode;
title: string;
}
@@ -17,14 +16,10 @@ const clusterIcons = [
const features: readonly Feature[] = [
{
- description:
- "Every shape, connector, and label stays editable after generation.",
glyph: ,
title: "Real objects, not screenshots",
},
{
- description:
- "Find the tools in your stack without drawing their marks by hand.",
glyph: (
{clusterIcons.map((icon) => (
@@ -41,15 +36,14 @@ const features: readonly Feature[] = [
title: "1,400+ logos, already drawn",
},
{
- description:
- "Open the scene in Excalidraw, then export it when you are ready.",
glyph: ,
title: "Yours to edit and export",
},
];
/**
- * Three icon-led benefits with one supporting sentence each.
+ * Three icon-led benefits. The titles say the whole thing; the cards carry the
+ * band's weight through their own surface rather than a sentence each.
*/
export function FeatureGrid() {
return (
@@ -63,7 +57,6 @@ export function FeatureGrid() {
{feature.glyph}
{feature.title}
-
{feature.description}
))}
diff --git a/apps/web/src/components/site-footer/site-footer.test.tsx b/apps/web/src/components/site-footer/site-footer.test.tsx
index b3f3db89..fafabd49 100644
--- a/apps/web/src/components/site-footer/site-footer.test.tsx
+++ b/apps/web/src/components/site-footer/site-footer.test.tsx
@@ -4,17 +4,12 @@ import { describe, expect, it } from "vitest";
import { SiteFooter } from "./site-footer";
describe("SiteFooter", () => {
- it("renders the footer columns and colophon", () => {
+ it("renders the footer columns", () => {
render();
expect(screen.getByRole("heading", { name: "Product" })).toBeTruthy();
expect(screen.getByRole("heading", { name: "Agents" })).toBeTruthy();
expect(screen.getByRole("heading", { name: "More" })).toBeTruthy();
- expect(
- screen.getByText(
- "Made for people who'd rather describe a diagram than draw one.",
- ),
- ).toBeTruthy();
expect(
screen
.getByRole("link", { name: "Sketchi on GitHub" })
@@ -22,6 +17,18 @@ describe("SiteFooter", () => {
).toBe("https://github.com/shpitdev/sketchi");
});
+ /**
+ * The hero already states the thesis. The footer restated it twice more, in
+ * the brand blurb and the colophon; both are gone and neither comes back.
+ */
+ it("does not restate the hero thesis", () => {
+ const { container } = render();
+ const text = container.textContent ?? "";
+
+ expect(text).not.toMatch(/rather describe a diagram than draw one/);
+ expect(text).not.toMatch(/Prompts become real, editable diagrams/);
+ });
+
it("links to llms.txt under its own name", () => {
render();
@@ -39,20 +46,23 @@ describe("SiteFooter", () => {
).toBe("https://sketchi.app/llms.txt");
});
- it("lists the CLI as a product and links the npm package with its mark", () => {
+ /**
+ * The npm wordmark is the word, so the link carries the mark and an
+ * accessible name rather than the mark plus "npm package" beside it.
+ */
+ it("lists the CLI as a product and links npm by its wordmark", () => {
render();
expect(screen.getByRole("link", { name: "CLI" }).getAttribute("href")).toBe(
"/#cli",
);
- const npmLink = screen.getByRole("link", { name: /npm package/ });
+ const npmLink = screen.getByRole("link", { name: "sketchi on npm" });
expect(npmLink.getAttribute("href")).toBe(
"https://www.npmjs.com/package/sketchi",
);
- expect(npmLink.querySelector("img")?.getAttribute("src")).toBe(
- "/brand/npm.svg",
- );
+ expect(npmLink.querySelector(".npm-mark")).toBeTruthy();
+ expect(screen.queryByText("npm package")).toBeNull();
});
it("uses configured surface links", () => {
diff --git a/apps/web/src/components/site-footer/site-footer.tsx b/apps/web/src/components/site-footer/site-footer.tsx
index af252808..fa95b646 100644
--- a/apps/web/src/components/site-footer/site-footer.tsx
+++ b/apps/web/src/components/site-footer/site-footer.tsx
@@ -1,4 +1,3 @@
-import { BrandIcon } from "../brand-icon/index.js";
import { CLI_NPM_URL } from "../../lib/cli-package";
import {
DEFAULT_WEB_SURFACE_URLS,
@@ -6,7 +5,6 @@ import {
} from "../../lib/surface-urls";
export interface SiteFooterProps {
- colophon?: string;
// Every surface that renders this footer must actually serve the file it
// points at; pass an absolute URL when the surface has no llms.txt of its own.
llmsTxtUrl?: string;
@@ -19,7 +17,6 @@ const DEFAULT_REPO_URL = "https://github.com/shpitdev/sketchi";
const DEFAULT_LLMS_TXT_URL = "/llms.txt";
export function SiteFooter({
- colophon = "Made for people who'd rather describe a diagram than draw one.",
llmsTxtUrl = DEFAULT_LLMS_TXT_URL,
npmUrl = CLI_NPM_URL,
repoUrl = DEFAULT_REPO_URL,
@@ -39,9 +36,6 @@ export function SiteFooter({
/>
Sketchi
-
- Prompts become real, editable diagrams, logos included.
-