Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions STYLE.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
# Public writing style

<!-- synced from hraness/.github STYLE.md sha256:3e0d4984501e1d7bfbaa2812fa0b71ba6846cc537d9e79c358e771e567aa58a5 -->

This guide covers everything written for readers outside a repository: product pages, documentation, READMEs, interface text, metadata, and text a model writes for publication. Apply the voice rules in [`WRITING.md`](WRITING.md) first. The [documentation guidelines](https://github.com/hraness/.github/blob/main/DOCUMENTATION_GUIDELINES.md) choose a document's purpose and shape, and the [README guidelines](https://github.com/hraness/.github/blob/main/README_GUIDELINES.md) cover the repository front door.

Public prose must be precise, useful, and free of hype. Use a direct, natural voice that reads well aloud.

This copy is synced from [hraness/.github](https://github.com/hraness/.github/blob/main/STYLE.md). Change shared rules there; add rules for this repository under “Repository additions” below.
This is a synced copy of the [Hraness public writing style](https://github.com/hraness/.github/blob/main/STYLE.md), kept here so agents can read it offline. Repository-specific rules appear under “Repository additions” below.

## Leave the reader with a clearer model

Expand Down Expand Up @@ -109,6 +107,18 @@ Most Hraness copy is drafted by agents working inside repository guides full of
- Remove unverifiable superlatives such as “the first” and “the only” unless a cited source supports them.
- Keep repository instructions and tests from demanding reader-hostile copy. When a guide or test requires a status phrase on every page, change the requirement to the fact that must stay true and let the page say it plainly.

## Give marketing pages a readable path

- Start with what the product helps someone do, who it is for, and a useful next action. Introduce implementation details only when they help that reader choose or use the product.
- Make each section answer the next question a visitor is likely to have. Remove a section when it repeats the introduction or explains internal work without helping that decision.
- Use a preview to show a recognizable task and a useful result. A CLI help dump, test log, checksum, or release-verification link does not show a product's value unless that is the product's actual task. Omit a preview that adds no useful example.
- Keep release inspection, protocol contracts, configuration details, and maintainer evidence in the install guide or reference. Label links by what readers can do there, such as “Get started” or “See an example”.
- Keep examples truthful. Label illustrations as examples, and never present invented output, timings, customer data, or completion claims as a recorded run.
- Render code and executable commands with the shared syntax highlighter and the correct language. Do not bypass it with a bare code block or manually colored text. Keep natural-language prompts and non-code output readable as text.
- Use the shared terminal frame for shell commands and terminal interactions. Use a code block for source files and structured data; do not dress ordinary prose in terminal chrome. Apply the same treatment to equivalent examples across sites.
- Copy controls copy executable input without shell prompts or displayed output. Preserve complete commands, keyboard access, readable colors in both themes, and horizontal scrolling for long lines on narrow screens.
- Review the page as a new visitor at desktop and phone widths. Confirm that the headline, example, and next action make sense before reading the documentation, and that essential limits appear beside the claims they qualify.

## State each limit once

Readers trust a page that states its limits plainly. They skim a page that repeats them.
Expand Down Expand Up @@ -198,6 +208,7 @@ Readers trust a page that states its limits plainly. They skim a page that repea
- Name the consequence in a confirmation. Repeat the exact verb and object for a destructive action.
- Use nouns for labels. Use placeholders for a format or example, not a repeated label.
- State the completed result in past tense in a toast notification.
- Follow [`CLI_MENU_STYLE.md`](https://github.com/hraness/.github/blob/main/CLI_MENU_STYLE.md) for command-line output, menu bar menus, and macOS permission notices.

## Vary a generated series

Expand Down
15 changes: 15 additions & 0 deletions website/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
readClaimsRegisterSource,
} from "./claims-register";
import { product, type PortfolioProductId } from "@hraness/design-kit/portfolio";
import { highlightCode, type SyntaxLanguage } from "@hraness/design-kit/syntax-highlighting";
import { renderStatusPageHtml, type StatusPageLink } from "@hraness/design-kit";
import {
EDITORIAL_ARTICLE_IMAGE_SIZES,
Expand Down Expand Up @@ -1012,6 +1013,20 @@ function renderTemplate(
rendered = rendered.replaceAll(placeholder, escapeHtml(value));
}
}
const codeExamples = new Map<string, readonly [string, SyntaxLanguage]>([
["{{GHOSTGET_READ_CODE}}", ["ghostget read https://example.com", "shell"]],
["{{GHOSTGET_FIRST_READ_CODE}}", [`${installCommand}\nghostget read https://example.com`, "shell"]],
["{{GHOSTGET_SKILL_CODE}}", [skillInstallCommands.npx, "shell"]],
["{{GHOSTGET_CAPABILITIES_CODE}}", ["ghostget capabilities --json", "shell"]],
["{{GHOSTGET_SDK_CODE}}", ['import { isProviderPluginId } from "@hraness/ghostget"', "typescript"]],
]);
for (const [placeholder, [source, language]] of codeExamples) {
if (!rendered.includes(placeholder)) continue;
const code = highlightCode(source, language, { styles: "classes" });
const tag = placeholder === "{{GHOSTGET_READ_CODE}}" ? "span" : "code";
const markup = `<${tag} class="${code.className}" data-language="${code.language}">${code.html}</${tag}>`;
rendered = rendered.replaceAll(placeholder, () => markup);
}
if (page !== undefined && rendered.includes("{{WEBMCP_")) {
const webmcpValues = options.webmcpValues(page.canonicalPath);
if (webmcpValues === undefined) {
Expand Down
22 changes: 9 additions & 13 deletions website/site.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ describe("ghostget.com static site", () => {
`<a href="${SKILLS_URL}">View the Ghostget Agent Skill on skills.sh.</a>`,
);
expect(html).toContain(
`<a href="${npmPackageUrl}"><code>@hraness/ghostget</code> canonical release archive</a>`,
`<a href="${npmPackageUrl}"><code>@hraness/ghostget</code> release archive</a>`,
);
expect(html).not.toContain(`value="${skillInstallCommands.npx}"`);
expect(html).not.toContain(`href="${GITHUB_RELEASES_URL}"`);
Expand Down Expand Up @@ -572,6 +572,9 @@ describe("ghostget.com static site", () => {
expect(page.html).not.toContain("data-hraness-hero-item");
}
expect(html).toContain('class="proof-transcript"');
expect(html).toContain('class="syntax-code language-shell"');
expect(html).toContain('class="syntax-code language-typescript"');
expect(html).toContain('class="hraness-marketing-proof-frame__chrome"');
expect(html).toContain("ghostget menubar");
expect(html).toContain("Review connected accounts, permissions, pending approvals, and recent activity in your menu bar or terminal");
expect(html).toContain("ghostget tui");
Expand Down Expand Up @@ -661,8 +664,7 @@ describe("ghostget.com static site", () => {
expect(html).not.toContain('class="hraness-marketing-hero__eyebrow"');
expect(html).toContain('data-align="start"');
expect(html).not.toContain('class="hraness-marketing-hero__example"');
expect(html).toContain('import { isProviderPluginId } from "@hraness/ghostget"');
expect(html).toMatch(/Reviewed actions across \d+ services\./u);
expect(html).toContain('id="providers-title"');
expect(html).toContain('aria-label="Ghostget home" class="hraness-marketing-header__brand" data-foil="" href="/"><span aria-hidden="true" class="brand-mark hraness-foil-mark" data-foil=""><img alt="" class="hraness-foil-mark__image" decoding="async" height="20" src="/marks/wrench.svg" width="20" /><span aria-hidden="true" class="hraness-foil-mark__paint"></span></span> Ghostget</a>');
expect(html).not.toMatch(/hero-field|hero-orbit|hero-glyph/u);
expect(html).not.toMatch(/observed provider operations|capture-required|unavailable reservations/iu);
Expand Down Expand Up @@ -1228,6 +1230,7 @@ describe("ghostget.com static site", () => {
"utf8",
);
expect(homepageMarkdown).not.toContain("![](");
expect(homepageMarkdown).toContain('import { isProviderPluginId } from "@hraness/ghostget"');
for (const image of INDEXABLE_EDITORIAL_IMAGES) {
expect(homepageMarkdown).toContain(image.cardTitle);
expect(homepageMarkdown).not.toContain(editorialImageUrl(image));
Expand Down Expand Up @@ -1378,11 +1381,9 @@ describe("ghostget.com static site", () => {
expect(html).toContain('<a href="https://composio.dev">Composio</a>, <a href="https://www.arcade.dev">Arcade</a>, and <a href="https://pipedream.com/docs/connect">Pipedream Connect</a></th>');
expect(html).not.toContain("Hosted integration breadth and managed end-user authentication");
expect(html).toContain('href="https://docs.apify.com/integrations/mcp">Apify MCP</a>');
expect(html).toContain("Discovering and running eligible Apify Store Actors");
expect(html).toContain('href="/compare/browserbase/">Browserbase + Stagehand</a>');
expect(html).toContain("Managed cloud browser sessions at scale, with proxies, stealth, and session replay");
expect(html).toContain(
"Encrypted local copies of reads, a preview and a record for every write, and actions that stop when a service changes",
"A write with an unknown result is not sent again.",
);

const gettingStarted = pages.find((page) => page.definition.canonicalPath === "/docs/tutorials/getting-started/");
Expand Down Expand Up @@ -1484,11 +1485,8 @@ describe("ghostget.com static site", () => {
expect(providerCapabilities?.html).toContain(
`${String(BEEPER_PRESENTATION_TRANSPORT_COUNTS.cliBackedOperationCount)} actions run through a pinned version of Beeper's official CLI, and ${String(BEEPER_PRESENTATION_TRANSPORT_COUNTS.desktopLoopbackOperationCount)} are fixed reads from Beeper Desktop. Writes need a preview first, and Ghostget never resends a write whose outcome is unknown.`,
);
expect(html).toContain(
`<h2 id="providers-title">Reviewed actions across ${String(providerDirectory.providerCount)} services.</h2>`,
);
expect(html).toContain("Each entry shows how Ghostget connects to that service and links to");
expect(html).toContain("its supported actions in this release.");
const providerHeading = html.match(/<h2 id="providers-title">([^<]+)<\/h2>/u)?.[1];
expect(Number(providerHeading?.match(/\d+/u)?.[0])).toBe(providerDirectory.providerCount);
expect(html).not.toContain("Each card names the actions");
for (const entry of providerDirectory.entries) {
expect(html).toContain(`data-provider-icon="${entry.icon}"`);
Expand Down Expand Up @@ -2129,8 +2127,6 @@ describe("ghostget.com static site", () => {
expect(html).toContain('id="measured"');
expect(html).toContain("145,617 bytes");
expect(html).toContain("9.7×");
expect(html).toContain('id="boundary"');
expect(html).toContain("Your agent never steers a browser.");
expect(html).toContain("ghostget vault import-x");
expect(html).toContain('href="/compare/"');

Expand Down
Loading
Loading