copy: remove second-person pronouns, use imperative tone

Replace "you/your" with impersonal alternatives across all
user-facing strings in templates, CLI output, error messages,
and documentation. Add copy style rule to CLAUDE.md.
This commit is contained in:
Jon Seager 2026-02-08 09:50:49 +00:00
parent 3f672d7c06
commit ba319e3ab7
No known key found for this signature in database
18 changed files with 54 additions and 46 deletions

View file

@ -693,6 +693,14 @@ Server-side via `q` query parameter. `ListQuery` extracts it, repos apply `LIKE`
- Prefer `if`/`else` and `switch` over ternaries; never nest ternaries - Prefer `if`/`else` and `switch` over ternaries; never nest ternaries
- **Inline `onclick` + global arrow functions** for pages with imperative JS — do not use `DOMContentLoaded` + `addEventListener` - **Inline `onclick` + global arrow functions** for pages with imperative JS — do not use `DOMContentLoaded` + `addEventListener`
### Copy & UI Text
- **No second-person pronouns** — never use "you", "your", "you're" in user-facing strings (templates, CLI output, error messages, docs). Use imperative tone or impersonal phrasing instead.
- "Browse all your coffee data" → "Browse all coffee data"
- "Lets you sign in" → "Enables sign-in"
- "Your browser does not support" → "This browser does not support"
- "you won't see it again" → "it will not be shown again"
### Naming & Conventions ### Naming & Conventions
- **Method naming**: `order_clause()` for sort query builders (not `sort_clause`) - **Method naming**: `order_clause()` for sort query builders (not `sort_clause`)

View file

@ -46,8 +46,8 @@ No users found. Register the first user at:
This link expires in 1 hour. This link expires in 1 hour.
``` ```
Open that URL, choose a display name, and register a passkey. This creates your account and Open that URL, choose a display name, and register a passkey. This creates an account and
signs you in. signs in automatically.
### CLI Authentication ### CLI Authentication
@ -55,12 +55,12 @@ To use the CLI or API for write operations, create a token via browser handoff:
```bash ```bash
brewlog token create --name "my-cli-token" brewlog token create --name "my-cli-token"
# Browser opens → authenticate with your passkey → token printed once # Browser opens → authenticate with a passkey → token printed once
export BREWLOG_URL="http://localhost:3000" export BREWLOG_URL="http://localhost:3000"
export BREWLOG_TOKEN="<token from above>" export BREWLOG_TOKEN="<token from above>"
# Now you can create data from the CLI # Create data from the CLI
brewlog roaster add --name "Radical Roasters" --country "United Kingdom" brewlog roaster add --name "Radical Roasters" --country "United Kingdom"
``` ```
@ -75,7 +75,7 @@ directory is loaded automatically via [dotenvy](https://crates.io/crates/dotenvy
| Variable | Purpose | Default | | Variable | Purpose | Default |
| ------------------------ | ------------------------------------------------------ | --------------------- | | ------------------------ | ------------------------------------------------------ | --------------------- |
| `BREWLOG_RP_ID` | WebAuthn Relying Party ID (your domain) | **required** | | `BREWLOG_RP_ID` | WebAuthn Relying Party ID (server domain) | **required** |
| `BREWLOG_RP_ORIGIN` | WebAuthn Relying Party origin (full URL) | **required** | | `BREWLOG_RP_ORIGIN` | WebAuthn Relying Party origin (full URL) | **required** |
| `BREWLOG_DATABASE_URL` | Database connection string | `sqlite://brewlog.db` | | `BREWLOG_DATABASE_URL` | Database connection string | `sqlite://brewlog.db` |
| `BREWLOG_BIND_ADDRESS` | Server bind address | `127.0.0.1:3000` | | `BREWLOG_BIND_ADDRESS` | Server bind address | `127.0.0.1:3000` |

View file

@ -87,14 +87,14 @@ async fn run_server(command: ServeCommand) -> Result<()> {
let openrouter_api_key = command.openrouter_api_key.ok_or_else(|| { let openrouter_api_key = command.openrouter_api_key.ok_or_else(|| {
anyhow::anyhow!( anyhow::anyhow!(
"BREWLOG_OPENROUTER_API_KEY is required. Set this environment variable \ "BREWLOG_OPENROUTER_API_KEY is required. Set this environment variable \
to your OpenRouter API key for AI-powered extraction features." to an OpenRouter API key for AI-powered extraction features."
) )
})?; })?;
let foursquare_api_key = command.foursquare_api_key.ok_or_else(|| { let foursquare_api_key = command.foursquare_api_key.ok_or_else(|| {
anyhow::anyhow!( anyhow::anyhow!(
"BREWLOG_FOURSQUARE_API_KEY is required. Set this environment variable \ "BREWLOG_FOURSQUARE_API_KEY is required. Set this environment variable \
to your Foursquare API key for nearby cafe search." to a Foursquare API key for nearby cafe search."
) )
})?; })?;

View file

@ -102,7 +102,7 @@ pub async fn create_token(client: &BrewlogClient, cmd: CreateTokenCommand) -> Re
println!("Token Name: {}", cmd.name); println!("Token Name: {}", cmd.name);
println!("\nSave this token securely - it will not be shown again:"); println!("\nSave this token securely - it will not be shown again:");
println!("\n{token}"); println!("\n{token}");
println!("\nExport it in your environment:"); println!("\nExport it as an environment variable:");
println!(" export BREWLOG_TOKEN={token}"); println!(" export BREWLOG_TOKEN={token}");
Ok(()) Ok(())
@ -159,7 +159,7 @@ async fn run_callback_server(
let _ = sender.send(token); let _ = sender.send(token);
} }
Html("<html><body><h1>Authenticated</h1><p>You can close this window and return to the terminal.</p></body></html>".to_string()) Html("<html><body><h1>Authenticated</h1><p>This window can be closed. Return to the terminal.</p></body></html>".to_string())
} }
}), }),
); );

View file

@ -266,8 +266,8 @@
</div> </div>
{% else if grinder_options.is_empty() || brewer_options.is_empty() %} {% else if grinder_options.is_empty() || brewer_options.is_empty() %}
<div class="text-sm text-text-secondary"> <div class="text-sm text-text-secondary">
<h3 class="text-lg font-semibold text-text">Add your gear first</h3> <h3 class="text-lg font-semibold text-text">Add gear first</h3>
<p class="mt-2">Brews need a grinder and brewer. Add your gear to enable this form.</p> <p class="mt-2">Brews need a grinder and brewer. Add gear to enable this form.</p>
</div> </div>
{% else %} {% else %}
<div> <div>
@ -449,7 +449,7 @@
<div data-show="$_addType === 'gear'" style="display:none" class="rounded-lg border bg-surface p-5"> <div data-show="$_addType === 'gear'" style="display:none" class="rounded-lg border bg-surface p-5">
<div> <div>
<h3 class="text-lg font-semibold text-text">New Gear</h3> <h3 class="text-lg font-semibold text-text">New Gear</h3>
<p class="mt-1 text-sm text-text-secondary">Add brewing equipment to your collection.</p> <p class="mt-1 text-sm text-text-secondary">Add brewing equipment.</p>
</div> </div>
<form method="post" action="/api/v1/gear" class="mt-4 flex flex-col gap-4"> <form method="post" action="/api/v1/gear" class="mt-4 flex flex-col gap-4">
<div class="grid gap-4 sm:grid-cols-3"> <div class="grid gap-4 sm:grid-cols-3">
@ -550,7 +550,7 @@
{% else %} {% else %}
<div> <div>
<h3 class="text-lg font-semibold text-text">New Cup</h3> <h3 class="text-lg font-semibold text-text">New Cup</h3>
<p class="mt-1 text-sm text-text-secondary">Record a coffee you had at a cafe.</p> <p class="mt-1 text-sm text-text-secondary">Record a coffee from a cafe visit.</p>
</div> </div>
<form method="post" action="/api/v1/cups" class="mt-4 flex flex-col gap-4"> <form method="post" action="/api/v1/cups" class="mt-4 flex flex-col gap-4">
<div class="grid gap-4 sm:grid-cols-2"> <div class="grid gap-4 sm:grid-cols-2">

View file

@ -6,7 +6,7 @@
{% block content %} {% block content %}
<header class="flex flex-col gap-2"> <header class="flex flex-col gap-2">
<h1 class="text-3xl font-semibold">Admin</h1> <h1 class="text-3xl font-semibold">Admin</h1>
<p class="max-w-2xl text-sm text-text-secondary">Manage your passkeys, API tokens, and data.</p> <p class="max-w-2xl text-sm text-text-secondary">Manage passkeys, API tokens, and data.</p>
</header> </header>
<!-- Passkeys --> <!-- Passkeys -->
@ -14,7 +14,7 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div> <div>
<h2 class="text-lg font-semibold text-text">Passkeys</h2> <h2 class="text-lg font-semibold text-text">Passkeys</h2>
<p class="mt-1 text-sm text-text-secondary">Passkeys let you sign in securely without a password.</p> <p class="mt-1 text-sm text-text-secondary">Passkeys enable secure sign-in without a password.</p>
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
@ -45,7 +45,7 @@
</div> </div>
{% if passkeys.len() <= 1 %} {% if passkeys.len() <= 1 %}
<p class="text-xs text-text-muted">Add another passkey before removing your only one.</p> <p class="text-xs text-text-muted">Add another passkey before removing the only one.</p>
{% endif %} {% endif %}
<!-- Add passkey form --> <!-- Add passkey form -->
@ -81,7 +81,7 @@
</div> </div>
<div id="add-passkey-loading" class="mt-3 hidden flex items-center gap-3 text-sm text-accent"> <div id="add-passkey-loading" class="mt-3 hidden flex items-center gap-3 text-sm text-accent">
{{ icons::spinner("h-5 w-5") }} {{ icons::spinner("h-5 w-5") }}
Follow the prompts from your browser or device... Follow the prompts from the browser or device...
</div> </div>
</div> </div>
@ -195,7 +195,7 @@
> >
{{ icons::x_mark("h-4 w-4") }} {{ icons::x_mark("h-4 w-4") }}
</button> </button>
<p class="pr-6 text-sm font-medium text-green-800 dark:text-green-300">Token created! Copy it now — you won't see it again.</p> <p class="pr-6 text-sm font-medium text-green-800 dark:text-green-300">Token created! Copy it now — it will not be shown again.</p>
<div class="mt-3 flex items-center gap-2"> <div class="mt-3 flex items-center gap-2">
<code id="token-value" data-text="$_tokenValue" <code id="token-value" data-text="$_tokenValue"
class="flex-1 rounded bg-surface px-3 py-2 text-sm font-mono text-text border border-green-200 dark:border-green-800 break-all select-all"></code> class="flex-1 rounded bg-surface px-3 py-2 text-sm font-mono text-text border border-green-200 dark:border-green-800 break-all select-all"></code>
@ -402,7 +402,7 @@
return; return;
} }
if (!confirm("Are you sure? All coffee data will be permanently deleted.")) { if (!confirm("Confirm? All coffee data will be permanently deleted.")) {
return; return;
} }
@ -434,7 +434,7 @@
if (response.ok) { if (response.ok) {
window.location.reload(); window.location.reload();
} else if (response.status === 409) { } else if (response.status === 409) {
alert("Cannot delete your only passkey. Add another passkey first."); alert("Cannot delete the only passkey. Add another passkey first.");
} else { } else {
alert("Failed to delete passkey."); alert("Failed to delete passkey.");
} }

View file

@ -230,11 +230,11 @@ block title %}Brewlog · Check In{% endblock %} {% block head %}
<!-- Step 2: Roast identification --> <!-- Step 2: Roast identification -->
<div class="mt-4" data-show="$_step === 2" style="display: none"> <div class="mt-4" data-show="$_step === 2" style="display: none">
<div class="rounded-lg border bg-surface p-5"> <div class="rounded-lg border bg-surface p-5">
<h3 class="text-base font-semibold text-text mb-3">What are you drinking?</h3> <h3 class="text-base font-semibold text-text mb-3">Select a coffee</h3>
<div data-show="!$_scanSuccess" class="mb-4"> <div data-show="!$_scanSuccess" class="mb-4">
<p class="text-sm text-text-secondary mb-3" data-show="!$_extracting"> <p class="text-sm text-text-secondary mb-3" data-show="!$_extracting">
Scan a bag to identify the coffee, or select from your existing roasts below. Scan a bag to identify the coffee, or select from existing roasts below.
</p> </p>
<form <form

View file

@ -2,7 +2,7 @@
{% block content %} {% block content %}
<header class="flex flex-col gap-2"> <header class="flex flex-col gap-2">
<h1 class="text-3xl font-semibold">Data</h1> <h1 class="text-3xl font-semibold">Data</h1>
<p class="max-w-2xl text-sm text-text-secondary">Browse all your coffee data.</p> <p class="max-w-2xl text-sm text-text-secondary">Browse all coffee data.</p>
</header> </header>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">

View file

@ -8,13 +8,13 @@
<div class="rounded-lg border bg-surface p-6"> <div class="rounded-lg border bg-surface p-6">
<h1 class="text-2xl font-semibold text-accent">Login</h1> <h1 class="text-2xl font-semibold text-accent">Login</h1>
<p class="mt-2 text-sm text-text-secondary"> <p class="mt-2 text-sm text-text-secondary">
Sign in with your passkey to manage your coffee log. Sign in with a passkey to access the coffee log.
</p> </p>
<div id="login-error" class="mt-4 hidden rounded-md bg-red-100 border border-red-300 p-3 text-sm text-red-800"></div> <div id="login-error" class="mt-4 hidden rounded-md bg-red-100 border border-red-300 p-3 text-sm text-red-800"></div>
<div id="login-unsupported" class="mt-4 hidden rounded-md bg-yellow-100 border border-yellow-300 p-3 text-sm text-yellow-800"> <div id="login-unsupported" class="mt-4 hidden rounded-md bg-yellow-100 border border-yellow-300 p-3 text-sm text-yellow-800">
Your browser does not support passkeys. Please use a modern browser (Chrome, Firefox, Safari, or Edge). This browser does not support passkeys. Please use a modern browser (Chrome, Firefox, Safari, or Edge).
</div> </div>
<div class="mt-6"> <div class="mt-6">

View file

@ -8,13 +8,13 @@
<div class="rounded-lg border bg-surface p-6"> <div class="rounded-lg border bg-surface p-6">
<h1 class="text-2xl font-semibold text-accent">Register</h1> <h1 class="text-2xl font-semibold text-accent">Register</h1>
<p class="mt-2 text-sm text-text-secondary"> <p class="mt-2 text-sm text-text-secondary">
Create your account by registering a passkey. This will be used to sign in going forward. Create an account by registering a passkey. This will be used to sign in going forward.
</p> </p>
<div id="register-error" class="mt-4 hidden rounded-md bg-red-100 border border-red-300 p-3 text-sm text-red-800"></div> <div id="register-error" class="mt-4 hidden rounded-md bg-red-100 border border-red-300 p-3 text-sm text-red-800"></div>
<div id="register-unsupported" class="mt-4 hidden rounded-md bg-yellow-100 border border-yellow-300 p-3 text-sm text-yellow-800"> <div id="register-unsupported" class="mt-4 hidden rounded-md bg-yellow-100 border border-yellow-300 p-3 text-sm text-yellow-800">
Your browser does not support passkeys. Please use a modern browser (Chrome, Firefox, Safari, or Edge). This browser does not support passkeys. Please use a modern browser (Chrome, Firefox, Safari, or Edge).
</div> </div>
<div id="register-form" class="mt-6 flex flex-col gap-4"> <div id="register-form" class="mt-6 flex flex-col gap-4">
@ -26,7 +26,7 @@
required required
autofocus autofocus
class="input-field" class="input-field"
placeholder="Your name" placeholder="Display name"
/> />
</label> </label>
@ -49,14 +49,14 @@
</button> </button>
<div id="register-loading" class="mt-2 hidden text-center text-sm text-text-muted"> <div id="register-loading" class="mt-2 hidden text-center text-sm text-text-muted">
<p>Follow the prompts from your browser or device...</p> <p>Follow the prompts from the browser or device...</p>
</div> </div>
</div> </div>
<div id="register-success" class="mt-6 hidden"> <div id="register-success" class="mt-6 hidden">
<div class="rounded-md bg-green-100 border border-green-300 p-4 text-sm text-green-800"> <div class="rounded-md bg-green-100 border border-green-300 p-4 text-sm text-green-800">
<p class="font-semibold">Registration successful!</p> <p class="font-semibold">Registration successful!</p>
<p class="mt-1">Your passkey has been registered and you are now signed in.</p> <p class="mt-1">Passkey registered. Now signed in.</p>
</div> </div>
<a <a
href="/" href="/"

View file

@ -8,7 +8,7 @@
> >
<p class="text-center"> <p class="text-center">
{% if is_authenticated %} {% if is_authenticated %}
No bags added yet. Click the Add button to create your first bag. No bags added yet. Click Add to create the first bag.
{% else %} {% else %}
No bags added yet. No bags added yet.
{% endif %} {% endif %}
@ -135,7 +135,7 @@
</table> </table>
</div> </div>
{% if bags.items.is_empty() %} {% if bags.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No bags match your search.</div> <div class="p-8 text-center text-text-muted">No bags match the search.</div>
{% endif %} {% endif %}
{{ table::pagination_header(bags, navigator, "#bag-list") }} {{ table::pagination_header(bags, navigator, "#bag-list") }}
{% if bags.has_next() %} {% if bags.has_next() %}

View file

@ -22,7 +22,7 @@
> >
<p class="text-center"> <p class="text-center">
{% if is_authenticated %} {% if is_authenticated %}
No brews logged yet. Click the Add button to log your first brew. No brews logged yet. Click Add to log the first brew.
{% else %} {% else %}
No brews logged yet. No brews logged yet.
{% endif %} {% endif %}
@ -124,7 +124,7 @@
{% if brews.items.is_empty() %} {% if brews.items.is_empty() %}
<div class="p-8 text-center text-text-muted"> <div class="p-8 text-center text-text-muted">
No brews match your search. No brews match the search.
</div> </div>
{% endif %} {% endif %}

View file

@ -8,7 +8,7 @@
> >
<p class="text-center"> <p class="text-center">
{% if is_authenticated %} {% if is_authenticated %}
No cafes added yet. Click the Add button to add your first cafe. No cafes added yet. Click Add to add the first cafe.
{% else %} {% else %}
No cafes added yet. No cafes added yet.
{% endif %} {% endif %}
@ -110,7 +110,7 @@
</table> </table>
</div> </div>
{% if cafes.items.is_empty() %} {% if cafes.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No cafes match your search.</div> <div class="p-8 text-center text-text-muted">No cafes match the search.</div>
{% endif %} {% endif %}
{{ table::pagination_header(cafes, navigator, "#cafe-list") }} {{ table::pagination_header(cafes, navigator, "#cafe-list") }}
{% if cafes.has_next() %} {% if cafes.has_next() %}

View file

@ -8,7 +8,7 @@
> >
<p class="text-center"> <p class="text-center">
{% if is_authenticated %} {% if is_authenticated %}
No cups added yet. Click the Add button to add your first cup. No cups added yet. Click Add to add the first cup.
{% else %} {% else %}
No cups added yet. No cups added yet.
{% endif %} {% endif %}
@ -93,7 +93,7 @@
</table> </table>
</div> </div>
{% if cups.items.is_empty() %} {% if cups.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No cups match your search.</div> <div class="p-8 text-center text-text-muted">No cups match the search.</div>
{% endif %} {% endif %}
{{ table::pagination_header(cups, navigator, "#cup-list") }} {{ table::pagination_header(cups, navigator, "#cup-list") }}
{% if cups.has_next() %} {% if cups.has_next() %}

View file

@ -8,7 +8,7 @@
> >
<p class="text-center"> <p class="text-center">
{% if is_authenticated %} {% if is_authenticated %}
No gear added yet. Click the Add button to add your first piece of equipment. No gear added yet. Click Add to add the first piece of equipment.
{% else %} {% else %}
No gear added yet. No gear added yet.
{% endif %} {% endif %}
@ -90,7 +90,7 @@
</table> </table>
</div> </div>
{% if gear.items.is_empty() %} {% if gear.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No gear matches your search.</div> <div class="p-8 text-center text-text-muted">No gear matches the search.</div>
{% endif %} {% endif %}
{{ table::pagination_header(gear, navigator, "#gear-list") }} {{ table::pagination_header(gear, navigator, "#gear-list") }}
{% if gear.has_next() %} {% if gear.has_next() %}

View file

@ -8,7 +8,7 @@
> >
<p class="text-center"> <p class="text-center">
{% if is_authenticated %} {% if is_authenticated %}
No roasts added yet. Click the Add button to create your first roast. No roasts added yet. Click Add to create the first roast.
{% else %} {% else %}
No roasts added yet. No roasts added yet.
{% endif %} {% endif %}
@ -111,7 +111,7 @@
</table> </table>
</div> </div>
{% if roasts.items.is_empty() %} {% if roasts.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No roasts match your search.</div> <div class="p-8 text-center text-text-muted">No roasts match the search.</div>
{% endif %} {% endif %}
{{ table::pagination_header(roasts, navigator, "#roast-list") }} {{ table::pagination_header(roasts, navigator, "#roast-list") }}
{% if roasts.has_next() %} {% if roasts.has_next() %}

View file

@ -8,7 +8,7 @@
> >
<p class="text-center"> <p class="text-center">
{% if is_authenticated %} {% if is_authenticated %}
No roasters added yet. Click the Add button to create your first roaster. No roasters added yet. Click Add to create the first roaster.
{% else %} {% else %}
No roasters added yet. No roasters added yet.
{% endif %} {% endif %}
@ -110,7 +110,7 @@
</table> </table>
</div> </div>
{% if roasters.items.is_empty() %} {% if roasters.items.is_empty() %}
<div class="p-8 text-center text-text-muted">No roasters match your search.</div> <div class="p-8 text-center text-text-muted">No roasters match the search.</div>
{% endif %} {% endif %}
{{ table::pagination_header(roasters, navigator, "#roaster-list") }} {{ table::pagination_header(roasters, navigator, "#roaster-list") }}
{% if roasters.has_next() %} {% if roasters.has_next() %}

View file

@ -7,7 +7,7 @@ const locateUser = (triggerEl) => {
root.dispatchEvent(new CustomEvent(name, { detail, bubbles: true })); root.dispatchEvent(new CustomEvent(name, { detail, bubbles: true }));
if (!navigator.geolocation) { if (!navigator.geolocation) {
emit('location-error', { message: 'Geolocation not supported by your browser.' }); emit('location-error', { message: 'Geolocation not supported by this browser.' });
return; return;
} }
@ -48,7 +48,7 @@ const locateUser = (triggerEl) => {
</button> </button>
<input type="text" data-bind:_city-name <input type="text" data-bind:_city-name
class="input-field w-full" class="input-field w-full"
data-attr:placeholder="$_locationFound ? 'Using your location (' + $_userLat.toFixed(2) + ', ' + $_userLng.toFixed(2) + ')' : 'Name of city or area'" data-attr:placeholder="$_locationFound ? 'Using current location (' + $_userLat.toFixed(2) + ', ' + $_userLng.toFixed(2) + ')' : 'Name of city or area'"
data-attr:disabled="$_locationFound" data-attr:disabled="$_locationFound"
data-on:input__debounce.350ms="$_cityName.length >= 2 && $_cafeSearch.length >= 2 && @get('/api/v1/nearby-cafes?near=' + encodeURIComponent($_cityName) + '&q=' + encodeURIComponent($_cafeSearch), {responseOverrides: {selector: '#nearby-results', mode: 'replace'}})" data-on:input__debounce.350ms="$_cityName.length >= 2 && $_cafeSearch.length >= 2 && @get('/api/v1/nearby-cafes?near=' + encodeURIComponent($_cityName) + '&q=' + encodeURIComponent($_cafeSearch), {responseOverrides: {selector: '#nearby-results', mode: 'replace'}})"
/> />