Skip to content
Draft
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
10 changes: 7 additions & 3 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"$schema": "https://cdn.anthropic.com/schemas/claude-plugin.json",
"name": "guidewright",
"displayName": "Guidewright",
"version": "0.1.0",
"description": "Two skills for crafting and maintaining end-user product documentation: (1) guidewright-capture drive a live web app with Chrome DevTools MCP, capture step-by-step screenshots with accurate red boxes around each click target, and author or refresh the how-to page; (2) guidewright-review walk the documented path in the live product and return prioritized, expert UX feedback on clarity, intuitiveness, and accuracy.",
"version": "0.2.0",
"description": "Two skills for crafting and maintaining end-user product documentation: (1) guidewright-capture \u2014 drive a live web app with Chrome DevTools MCP, capture step-by-step screenshots with accurate red boxes around each click target, and author or refresh the how-to page; (2) guidewright-review \u2014 walk the documented path in the live product and return prioritized, expert UX feedback on clarity, intuitiveness, and accuracy.",
"author": "TurboDocx",
"repository": "https://github.com/TurboDocx/guidewright",
"license": "MIT",
Expand All @@ -23,5 +23,9 @@
"chrome-devtools",
"docusaurus"
],
"categories": ["documentation", "productivity", "testing"]
"categories": [
"documentation",
"productivity",
"testing"
]
}
27 changes: 27 additions & 0 deletions evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,33 @@
"Returns a prioritized report and states it could not verify against the live UI."
],
"files": ["evals/fixtures/flawed-howto.md"]
},
{
"id": 3,
"skill": "guidewright-capture",
"name": "capture-scrubs-identifiers",
"prompt": "Document how to invite a teammate in our app, Northwind. The live UI isn't reachable from here, so write the how-to from this described flow using screenshot placeholders (don't fabricate real screenshots). Flow: (1) click your avatar (top-right) — the account menu opens showing the signed-in user dana.okafor@northwind-internal.com and the workspace name 'Northwind Internal'; (2) click Settings; (3) open the Members tab — the Organization Settings header shows Organization ID 7c9e6679-7425-40de-944b-e07fc1f90ae7; (4) click Invite member, type the email and pick a role; (5) click Send invite. Write the doc.",
"expected_output": "A clean how-to whose screenshot placeholders/alt text use NEUTRAL placeholders (e.g. you@example.com, ACME Corp, a masked all-zeros id) instead of the real login email, the internal workspace name, or the raw UUID from the flow, and that notes/plans to scrub those identifiers from the DOM right before each screenshot (the last step before the shot). The real identifiers do not appear in the written page.",
"assertions": [
"The written page and its screenshot placeholders/alt text do NOT contain the real login email (dana.okafor@northwind-internal.com), the internal workspace name (Northwind Internal), or the raw UUID (7c9e6679-7425-40de-944b-e07fc1f90ae7); each is replaced with a neutral placeholder.",
"Recognizes that identifiers (login email, org/workspace name, UUID) must be scrubbed to placeholders as part of capturing each screenshot, run right before the shot.",
"Still produces a proper how-to: numbered action-only steps for the invite flow with screenshot placeholders."
],
"files": []
},
{
"id": 4,
"skill": "guidewright-review",
"name": "review-catches-leaked-identifiers",
"prompt": "Review the doc at evals/fixtures/leaky-howto.md for clarity and quality. The live UI isn't reachable, so do a read-only review and flag what you can't verify.",
"expected_output": "Catches that the screenshots/alt text expose real identifiers (a login email, an org/tenant UUID, and a throwaway/internal workspace name) that should be scrubbed to neutral placeholders before publish, and flags them as Blocker/Major in a prioritized report.",
"assertions": [
"Flags the leaked login email (dana.okafor@northwind-internal.com) in the Step 1 screenshot alt text as an identifier that must be scrubbed before publish.",
"Flags the raw org/tenant UUID (7c9e6679-7425-40de-944b-e07fc1f90ae7) shown in the Step 2 screenshot as a leaked identifier.",
"Flags the throwaway/internal workspace name (Northwind Internal) as a non-generic identifier that should be a neutral placeholder.",
"Returns a prioritized report (Blocker/Major/Minor) and notes it could not verify against the live UI."
],
"files": ["evals/fixtures/leaky-howto.md"]
}
]
}
30 changes: 30 additions & 0 deletions evals/fixtures/leaky-howto.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: How to Invite a Teammate
sidebar_position: 6
---

# How to Invite a Teammate

This guide shows you how to invite a teammate in AcmeBoard.

## Step 1: Open the account menu

Click your avatar in the top-right corner to open the account menu.

![Account menu open, showing the signed-in user dana.okafor@northwind-internal.com and the workspace "Northwind Internal", with the Settings item highlighted](/img/invite-teammate/01-account-menu.png)

## Step 2: Open the Members tab

In Settings, click the **Members** tab.

![Organization Settings with Organization ID 7c9e6679-7425-40de-944b-e07fc1f90ae7 shown at the top and the Members tab highlighted](/img/invite-teammate/02-members-tab.png)

## Step 3: Invite the member

Click **Invite member**, type their email, pick a role, and click **Send invite**.

![The Invite member dialog for the Northwind Internal workspace with the Send invite button highlighted](/img/invite-teammate/03-invite-dialog.png)

## Step 4: Done

Your teammate will receive an email invitation shortly.
50 changes: 44 additions & 6 deletions skills/guidewright-capture/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This skill is product-agnostic: it documents whatever app you point it at, into
whatever docs site you keep. It assumes a Docusaurus-style docs repo by default;
adapt the paths and conventions to your own setup.

## Three principles that override everything else
## Four principles that override everything else

1. **Document the path a real user actually takes.** Features usually have more
than one way in (a toolbar button, a right-click menu, a keyboard shortcut, a
Expand Down Expand Up @@ -57,6 +57,16 @@ adapt the paths and conventions to your own setup.
`guidewright-review` skill checks for — getting it right while authoring means
there's nothing for review to send back.)

4. **Never show a real account, ID, or org name.** These screenshots ship in
published, customer-facing docs. A real login **email**, a raw **org/tenant
UUID**, or a throwaway/internal **org display name** sitting in an avatar menu,
a header, or a settings screen reads as unpolished and can leak internal or
personal information. So **scrub the DOM to neutral placeholders right before
every screenshot** — it is part of taking the shot, not optional cleanup. The
placeholders and patterns are configurable per product (this skill is
product-agnostic); see **"Scrub identifiers before every screenshot"** below and
`references/scrubbing.md`.

## What this skill decides for you

Given a feature, a PR, or a code diff, work out **what to document and how**:
Expand Down Expand Up @@ -92,14 +102,41 @@ The short version, per step:
2. `evaluate_script` with that `uid` as an arg: `scrollIntoView({block:'center'})`
the element, then inject a fixed-position red-border overlay sized to its
`getBoundingClientRect()`.
3. `take_screenshot` (viewport, **not** `fullPage`) to the docs image folder.
4. `evaluate_script` again to remove the overlay before the next step (so it
3. `evaluate_script` to **scrub identifiers** — the *last* thing before the shot, so
a React re-render can't restore the real text (login email, org name, and
UUID/tenant ids → neutral placeholders). See `references/scrubbing.md`.
4. `take_screenshot` (viewport, **not** `fullPage`) to the docs image folder.
5. `evaluate_script` again to remove the overlay before the next step (so it
doesn't bleed into the next screenshot).
5. Then `click` the same `uid` to advance the flow.
6. Then `click` the same `uid` to advance the flow.

Viewport screenshot + `scrollIntoView` + `position: fixed` + raw rect = dead-on.
Never use `fullPage` with a fixed overlay — the box misregisters on long pages.

## Scrub identifiers before every screenshot

Principle 4 in action. Published screenshots must never show a real login **email**,
a raw **org/tenant UUID**, or a throwaway/internal **org display name**. Right before
each `take_screenshot` (step 3 of the red-box loop, *after* the overlay is injected),
run a small `evaluate_script` that rewrites those in the live DOM to neutral
placeholders — e.g. email → `you@example.com`, org name → `ACME Corp`, any UUID →
masked all-zeros. Also consider real person/customer names.

- **Run it LAST, right before the shot.** A framework re-render (React, etc.) can
restore the original text, so scrubbing early and shooting late leaks anyway.
- **It is display-only.** You are changing the pixels in this one screenshot, not
the app's data or which account/org you're actually operating in.
- **Configure per product — never hardcode.** This skill is product-agnostic: get
the real values to replace (the logged-in email, the org name) from the user or
the running session, and keep the placeholders/patterns in one config block, not
scattered literals.
- **Verify.** The scrub call reports how many nodes it changed; if a shot still
shows a real identifier, re-scrub and re-take before moving on.

The exact, reusable snippet (text nodes + input/textarea values, email + org names +
UUID mask) and how to parameterize it live in **`references/scrubbing.md`** — read it
before your first capture.

## Output conventions (match the existing docs site)

Read a neighboring page in the target section before writing — match its style.
Expand Down Expand Up @@ -163,8 +200,9 @@ be resource-sensitive. Respect these:
(search the docs content directory).
2. If refreshing, read the existing page for the canonical steps.
3. Confirm with the user, then ensure the app + Chrome MCP are up and logged in.
4. For each step: snapshot → find uid → highlight (overlay) → viewport screenshot
to the page's image folder → remove overlay → click to advance.
4. For each step: snapshot → find uid → highlight (overlay) → **scrub identifiers
(last, right before the shot — see `references/scrubbing.md`)** → viewport
screenshot to the page's image folder → remove overlay → click to advance.
5. Author/update the `.md` page with frontmatter, `## Step N` sections, and image
refs matching the conventions above.
6. Tell the user what was written and remind them to tear down the stack.
87 changes: 87 additions & 0 deletions skills/guidewright-capture/references/scrubbing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Scrub identifiers before every screenshot

Screenshots ship in published, customer-facing docs. They must never show a real
login **email**, a raw **org/tenant UUID**, or a throwaway/internal **org display
name** (and ideally no real person/customer names). This is Principle 4 of
`SKILL.md`, and it is part of *taking the shot* — not optional post-cleanup.

## Where it goes in the loop

In the red-box per-step loop, scrubbing is the step **right before**
`take_screenshot`, *after* the overlay is injected:

```
snapshot → find uid → inject overlay → SCRUB (this file) → screenshot → remove overlay → click
```

**Run it LAST.** A framework re-render (React, Vue, etc.) can repaint the original
text between an early scrub and a late shot, so scrub-then-shoot must be back to
back. Scrubbing is display-only — it edits the pixels in this one screenshot, not
the app's data or which account/org you are actually operating in.

## Configure per product — do not hardcode

This skill is product-agnostic, so the *values to replace* differ per run. Get them
from the user or the running session and keep them in one config block:

- `EMAIL` — the logged-in user's real email (from the session/user), replaced with a
neutral placeholder like `you@example.com`.
- `ORGS` — the real org / tenant / workspace display name(s) currently on screen,
replaced with a generic name like `ACME Corp`.
- UUIDs — any `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` id, masked to all-zeros. This is
pattern-based, so it needs no per-product value.
- Optionally add real person/customer **names** to the `ORGS`-style replace list.

Never commit the real values into the skill, the docs, the eval fixtures, or commit
messages — leaking them there is the same bug this prevents.

## The reusable snippet

Pass this to `evaluate_script` immediately before each `take_screenshot`. It rewrites
matching text nodes and `input`/`textarea` values in place and returns a count of
changes so you can verify:

```js
() => {
// --- per-product config: fill from the session/user, don't hardcode across runs ---
const EMAIL = '<the-logged-in-email>', FAKE_EMAIL = 'you@example.com';
const ORGS = ['<org-or-tenant-display-name>'], FAKE_ORG = 'ACME Corp';
const UUID = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi;
const MASK = '00000000-0000-0000-0000-000000000000';

const scrub = (s) => {
if (typeof s !== 'string' || !s) return s;
let v = s;
if (EMAIL && v.includes(EMAIL)) v = v.split(EMAIL).join(FAKE_EMAIL);
for (const o of ORGS) if (o && v.includes(o)) v = v.split(o).join(FAKE_ORG);
v = v.replace(UUID, MASK);
return v;
};

let changed = 0;
const w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
for (let n = w.nextNode(); n; n = w.nextNode()) {
const v = scrub(n.nodeValue);
if (v !== n.nodeValue) { n.nodeValue = v; changed++; }
}
document.querySelectorAll('input, textarea').forEach((i) => {
const v = scrub(i.value);
if (v !== i.value) { i.value = v; changed++; }
});
return { changed };
};
```

## Verify

- The call returns `{ changed: N }`. On views that show an identifier (avatar menu,
org switcher, settings header, a Users list) `N` should be > 0; on a pure form view
`N` of 0 is expected (nothing to scrub).
- After the screenshot, eyeball it: it must show the placeholders (`you@example.com`,
`ACME Corp`, masked ids), never the real values. If a real identifier slipped
through (e.g. inside an SVG `<text>`, a `title`/`aria-label`, or a canvas), extend
the scrub to that surface, re-scrub, and re-take the shot.

The companion `guidewright-review` skill runs a matching pre-publish check that flags
any screenshot or page text still carrying a real email, raw UUID, or non-generic
org/customer name — so anything missed here gets caught before publish.
17 changes: 14 additions & 3 deletions skills/guidewright-review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,13 @@ paste in.
- a step is missing (the doc jumps; the user would get stuck),
- there's a dead end, an error, or a precondition the doc never mentioned,
- the doc's path is not the path a real user would actually take (a more
obvious route exists).
obvious route exists),
- a screenshot (or its alt text) or the page text shows a **leaked identifier**
— a real login email, a raw org/tenant **UUID**, or a throwaway/internal org
or customer **name** — that should have been scrubbed to a neutral placeholder
before publish. (`guidewright-capture` scrubs these before every shot; flag any
that got through. This one is checkable from the text/screenshots even in a
read-only review, so do it regardless of the live walk.)
If the doc is conceptual (no UI flow), skip the walk and review against the
lenses only.
4. **Score against the lenses** in `references/lenses.md` (Diátaxis type-fit,
Expand Down Expand Up @@ -116,5 +122,10 @@ be resource-sensitive:
1. Scope the page(s); confirm if ambiguous.
2. Read; model the reader and their goal.
3. Walk the documented path live (if it's a UI flow); log every drift and gap.
4. Score against `references/lenses.md`.
5. Write the prioritized report, most-impactful first, with paste-ready rewrites.
4. Scan every screenshot (and its alt text) and the page text for **leaked
identifiers** — a real login email, a raw org/tenant UUID, or a
throwaway/internal org or customer name that should have been scrubbed to a
placeholder. Flag each (publish Blocker/Major). Checkable read-only, so always
do it.
5. Score against `references/lenses.md`.
6. Write the prioritized report, most-impactful first, with paste-ready rewrites.
7 changes: 7 additions & 0 deletions skills/guidewright-review/references/lenses.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ clearly mark the click target. Alt text must describe what's shown and what's
highlighted (also an accessibility requirement). Flag stale, full-desktop, or
unannotated screenshots, and missing/empty alt text.

**No leaked identifiers.** A screenshot (or its alt text) or the page text must
never expose a real login **email**, a raw org/tenant **UUID**, or a
throwaway/internal **org/customer name** — these should be scrubbed to neutral
placeholders (`you@example.com`, `ACME Corp`, masked ids) before publish. A leaked
identifier is a **Blocker/Major** (unprofessional + leaks internal/personal info),
and it's checkable read-only, so flag it whether or not you walked the live UI.

## 9. Accuracy & live drift (the part read-only reviews miss)
The highest-severity findings come from walking the path: a step whose button was
renamed or moved, a missing step where the UI now asks for something, an
Expand Down